summary refs log tree commit diff
path: root/util/src/entities/Member.ts
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--util/src/entities/Member.ts287
1 files changed, 287 insertions, 0 deletions
diff --git a/util/src/entities/Member.ts b/util/src/entities/Member.ts
new file mode 100644

index 00000000..d2d78bb9 --- /dev/null +++ b/util/src/entities/Member.ts
@@ -0,0 +1,287 @@ +import { PublicUser, User } from "./User"; +import { BaseClass } from "./BaseClass"; +import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm"; +import { Guild } from "./Guild"; +import { Config, emitEvent } from "../util"; +import { + GuildCreateEvent, + GuildDeleteEvent, + GuildMemberAddEvent, + GuildMemberRemoveEvent, + GuildMemberUpdateEvent, +} from "../interfaces"; +import { HTTPError } from "lambert-server"; +import { Role } from "./Role"; + +@Entity("members") +export class Member extends BaseClass { + @JoinColumn({ name: "id" }) + @ManyToOne(() => User) + user: User; + + @Column({ nullable: true }) + @RelationId((member: Member) => member.guild) + guild_id: string; + + @JoinColumn({ name: "guild_id" }) + @ManyToOne(() => Guild) + guild: Guild; + + @Column({ nullable: true }) + nick?: string; + + @JoinTable({ name: "member_roles" }) + @ManyToMany(() => Role) + roles: Role[]; + + @Column() + joined_at: Date; + + @Column({ nullable: true }) + premium_since?: number; + + @Column() + deaf: boolean; + + @Column() + mute: boolean; + + @Column() + pending: boolean; + + @Column({ type: "simple-json" }) + settings: UserGuildSettings; + + // TODO: update + @Column({ type: "simple-json" }) + read_state: Record<string, string | null>; + + static async IsInGuildOrFail(user_id: string, guild_id: string) { + if (await Member.count({ id: user_id, guild: { id: guild_id } })) return true; + throw new HTTPError("You are not member of this guild", 403); + } + + static async removeFromGuild(user_id: string, guild_id: string) { + const guild = await Guild.findOneOrFail({ select: ["owner_id"], where: { id: guild_id } }); + if (guild.owner_id === user_id) throw new Error("The owner cannot be removed of the guild"); + const member = await Member.findOneOrFail({ where: { id: user_id, guild_id }, relations: ["user"] }); + + // use promise all to execute all promises at the same time -> save time + return Promise.all([ + Member.delete({ + id: user_id, + guild_id: guild_id, + }), + Guild.decrement({ id: guild_id }, "member_count", -1), + + emitEvent({ + event: "GUILD_DELETE", + data: { + id: guild_id, + }, + user_id: user_id, + } as GuildDeleteEvent), + emitEvent({ + event: "GUILD_MEMBER_REMOVE", + data: { + guild_id: guild_id, + user: member.user, + }, + guild_id: guild_id, + } as GuildMemberRemoveEvent), + ]); + } + + static async addRole(user_id: string, guild_id: string, role_id: string) { + const [member] = await Promise.all([ + // @ts-ignore + Member.findOneOrFail({ + where: { id: user_id, guild_id: guild_id }, + relations: ["user", "roles"], // we don't want to load the role objects just the ids + select: ["roles.id"], + }), + await Role.findOneOrFail({ id: role_id, guild_id: guild_id }), + ]); + member.roles.push(new Role({ id: role_id })); + + await Promise.all([ + member.save(), + emitEvent({ + event: "GUILD_MEMBER_UPDATE", + data: { + guild_id: guild_id, + user: member.user, + roles: member.roles.map((x) => x.id), + }, + guild_id: guild_id, + } as GuildMemberUpdateEvent), + ]); + } + + static async removeRole(user_id: string, guild_id: string, role_id: string) { + const [member] = await Promise.all([ + // @ts-ignore + Member.findOneOrFail({ + where: { id: user_id, guild_id: guild_id }, + relations: ["user", "roles"], // we don't want to load the role objects just the ids + select: ["roles.id"], + }), + await Role.findOneOrFail({ id: role_id, guild_id: guild_id }), + ]); + member.roles = member.roles.filter((x) => x.id == role_id); + + await Promise.all([ + member.save(), + emitEvent({ + event: "GUILD_MEMBER_UPDATE", + data: { + guild_id: guild_id, + user: member.user, + roles: member.roles.map((x) => x.id), + }, + guild_id: guild_id, + } as GuildMemberUpdateEvent), + ]); + } + + static async changeNickname(user_id: string, guild_id: string, nickname: string) { + const member = await Member.findOneOrFail({ + where: { + id: user_id, + guild_id: guild_id, + }, + relations: ["user"], + }); + member.nick = nickname; + + await Promise.all([ + member.save(), + + emitEvent({ + event: "GUILD_MEMBER_UPDATE", + data: { + guild_id: guild_id, + user: member.user, + nick: nickname, + }, + guild_id: guild_id, + } as GuildMemberUpdateEvent), + ]); + } + + static async addToGuild(user_id: string, guild_id: string) { + const user = await User.getPublicUser(user_id); + + const { maxGuilds } = Config.get().limits.user; + const guild_count = await Member.count({ id: user_id }); + if (guild_count >= maxGuilds) { + throw new HTTPError(`You are at the ${maxGuilds} server limit.`, 403); + } + + const guild = await Guild.findOneOrFail({ + where: { + id: guild_id, + }, + relations: ["channels", "emojis", "members", "roles", "stickers"], + }); + + if (await Member.count({ id: user.id, guild: { id: guild_id } })) + throw new HTTPError("You are already a member of this guild", 400); + + const member = { + id: user_id, + guild_id: guild_id, + nick: undefined, + roles: [guild_id], // @everyone role + joined_at: new Date(), + premium_since: undefined, + deaf: false, + mute: false, + pending: false, + }; + // @ts-ignore + guild.joined_at = member.joined_at.toISOString(); + + await Promise.all([ + Member.insert({ + ...member, + roles: undefined, + read_state: {}, + settings: { + channel_overrides: [], + message_notifications: 0, + mobile_push: true, + muted: false, + suppress_everyone: false, + suppress_roles: false, + version: 0, + }, + }), + Guild.increment({ id: guild_id }, "member_count", 1), + emitEvent({ + event: "GUILD_MEMBER_ADD", + data: { + ...member, + user, + guild_id: guild_id, + }, + guild_id: guild_id, + } as GuildMemberAddEvent), + emitEvent({ + event: "GUILD_CREATE", + data: { ...guild, members: [...guild.members, member] }, + user_id, + } as GuildCreateEvent), + ]); + } +} + +export interface UserGuildSettings { + channel_overrides: { + channel_id: string; + message_notifications: number; + mute_config: MuteConfig; + muted: boolean; + }[]; + message_notifications: number; + mobile_push: boolean; + mute_config: MuteConfig; + muted: boolean; + suppress_everyone: boolean; + suppress_roles: boolean; + version: number; +} + +export interface MuteConfig { + end_time: number; + selected_time_window: number; +} + +export type PublicMemberKeys = + | "id" + | "guild_id" + | "nick" + | "roles" + | "joined_at" + | "pending" + | "deaf" + | "mute" + | "premium_since"; + +export const PublicMemberProjection: PublicMemberKeys[] = [ + "id", + "guild_id", + "nick", + "roles", + "joined_at", + "pending", + "deaf", + "mute", + "premium_since", +]; + +// @ts-ignore +export type PublicMember = Pick<Member, Omit<PublicMemberKeys, "roles">> & { + user: PublicUser; + roles: string[]; // only role ids not objects +};