mirror of
https://github.com/spacebarchat/server.git
synced 2026-03-30 13:55:39 +00:00
Thread members ported from #876
This commit is contained in:
195
src/util/entities/ThreadMember.ts
Normal file
195
src/util/entities/ThreadMember.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2026 Spacebar and Spacebar Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryGeneratedColumn, RelationId } from "typeorm";
|
||||
import { ThreadMembersUpdateEvent } from "../interfaces";
|
||||
import { emitEvent } from "../util";
|
||||
import { BaseClassWithoutId } from "./BaseClass";
|
||||
import { Channel } from "./Channel";
|
||||
import { HTTPError } from "lambert-server";
|
||||
import { Member } from "./Member";
|
||||
|
||||
// TODO: move
|
||||
interface ThreadMemberMuteConfig {
|
||||
end_time?: Date;
|
||||
selected_time_window?: number;
|
||||
}
|
||||
|
||||
// TODO: move
|
||||
export enum ThreadMemberFlags {
|
||||
NONE = 0,
|
||||
HAS_INTERACTED = 1 << 0,
|
||||
ALL_MESSAGES = 1 << 1,
|
||||
ONLY_MENTIONS = 1 << 2,
|
||||
NO_MESSAGES = 1 << 3,
|
||||
}
|
||||
|
||||
@Entity("thread_members")
|
||||
@Index(["id", "member_idx"], { unique: true })
|
||||
export class ThreadMember extends BaseClassWithoutId {
|
||||
@PrimaryGeneratedColumn()
|
||||
index: string;
|
||||
|
||||
@Column()
|
||||
@RelationId((member: ThreadMember) => member.channel)
|
||||
id: string;
|
||||
|
||||
@JoinColumn({ name: "id" })
|
||||
@ManyToOne(() => Channel, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
channel: Channel;
|
||||
|
||||
@Column()
|
||||
@RelationId((member: ThreadMember) => member.member)
|
||||
member_idx: string;
|
||||
|
||||
@JoinColumn({ name: "member_idx" })
|
||||
@ManyToOne(() => Member, {
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
member: Member;
|
||||
|
||||
@Column()
|
||||
join_timestamp: Date;
|
||||
|
||||
@Column()
|
||||
muted: boolean;
|
||||
|
||||
@Column({ nullable: true, type: "simple-json" })
|
||||
mute_config?: ThreadMemberMuteConfig;
|
||||
|
||||
@Column()
|
||||
flags: ThreadMemberFlags;
|
||||
|
||||
static async IsInThreadOrFail(member_id: string, thread_id: string) {
|
||||
if (await ThreadMember.count({ where: { id: thread_id, member_idx: member_id } })) return true;
|
||||
throw new HTTPError("You are not member of this thread", 403);
|
||||
}
|
||||
|
||||
static async removeFromThread(member_id: string, thread_id: string) {
|
||||
const channel = await Channel.findOneOrFail({ where: { id: thread_id } });
|
||||
if (
|
||||
!(await ThreadMember.count({
|
||||
where: {
|
||||
id: thread_id,
|
||||
member_idx: member_id,
|
||||
},
|
||||
}))
|
||||
)
|
||||
throw new HTTPError("You are not member of this thread", 403);
|
||||
// // use promise all to execute all promises at the same time -> save time
|
||||
// TODO: check for bugs
|
||||
if (channel.member_count) channel.member_count--;
|
||||
return Promise.all([
|
||||
ThreadMember.delete({
|
||||
id: thread_id,
|
||||
member_idx: member_id,
|
||||
}),
|
||||
// //Guild.decrement({ id: guild_id }, "member_count", -1),
|
||||
|
||||
emitEvent({
|
||||
event: "THREAD_MEMBERS_UPDATE",
|
||||
data: {
|
||||
guild_id: channel.guild_id,
|
||||
id: channel.id,
|
||||
member_count: channel.member_count,
|
||||
removed_member_ids: [member_id],
|
||||
},
|
||||
channel_id: thread_id,
|
||||
} as ThreadMembersUpdateEvent),
|
||||
]);
|
||||
}
|
||||
|
||||
// static async addRole(user_id: string, guild_id: string, role_id: string) {
|
||||
// const [member, role] = await Promise.all([
|
||||
// // @ts-ignore
|
||||
// Member.findOneOrFail({
|
||||
// where: { id: user_id, guild_id },
|
||||
// relations: ["user", "roles"], // we don't want to load the role objects just the ids
|
||||
// select: ["index"]
|
||||
// }),
|
||||
// Role.findOneOrFail({ where: { id: role_id, guild_id }, select: ["id"] })
|
||||
// ]);
|
||||
// member.roles.push(OrmUtils.mergeDeep(new Role(), { id: role_id }));
|
||||
|
||||
// await Promise.all([
|
||||
// member.save(),
|
||||
// emitEvent({
|
||||
// event: "GUILD_MEMBER_UPDATE",
|
||||
// data: {
|
||||
// guild_id,
|
||||
// user: member.user,
|
||||
// roles: member.roles.map((x) => x.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 },
|
||||
// relations: ["user", "roles"], // we don't want to load the role objects just the ids
|
||||
// select: ["index"]
|
||||
// }),
|
||||
// await Role.findOneOrFail({ where: { id: role_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,
|
||||
// user: member.user,
|
||||
// roles: member.roles.map((x) => x.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
|
||||
// },
|
||||
// relations: ["user"]
|
||||
// });
|
||||
// member.nick = nickname;
|
||||
|
||||
// await Promise.all([
|
||||
// member.save(),
|
||||
|
||||
// emitEvent({
|
||||
// event: "GUILD_MEMBER_UPDATE",
|
||||
// data: {
|
||||
// guild_id,
|
||||
// user: member.user,
|
||||
// nick: nickname
|
||||
// },
|
||||
// guild_id
|
||||
// } as GuildMemberUpdateEvent)
|
||||
// ]);
|
||||
// }
|
||||
}
|
||||
@@ -56,6 +56,7 @@ export * from "./StreamSession";
|
||||
export * from "./Team";
|
||||
export * from "./TeamMember";
|
||||
export * from "./Template";
|
||||
export * from "./ThreadMember";
|
||||
export * from "./User";
|
||||
export * from "./UserSettings";
|
||||
export * from "./UserSettingsProtos";
|
||||
|
||||
25
src/util/migration/postgres/1769653303971-ThreadMembers.ts
Normal file
25
src/util/migration/postgres/1769653303971-ThreadMembers.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class ThreadMembers1769653303971 implements MigrationInterface {
|
||||
name = "ThreadMembers1769653303971";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "thread_members" ("index" SERIAL NOT NULL, "id" character varying NOT NULL, "member_id" integer NOT NULL, "join_timestamp" TIMESTAMP NOT NULL, "muted" boolean NOT NULL, "mute_config" text, "flags" integer NOT NULL, CONSTRAINT "PK_22232a9f7a08fb9967a9c78da53" PRIMARY KEY ("index"))`,
|
||||
);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_bde0970b6a26bdbd83508addd2" ON "thread_members" ("id", "member_id") `);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "thread_members" ADD CONSTRAINT "FK_cf20e37d71b0e1bf1ab633861c8" FOREIGN KEY ("id") REFERENCES "channels"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "thread_members" ADD CONSTRAINT "FK_606ac45e8756d3440c584477f4e" FOREIGN KEY ("member_id") REFERENCES "members"("index") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "thread_members" DROP CONSTRAINT "FK_606ac45e8756d3440c584477f4e"`);
|
||||
await queryRunner.query(`ALTER TABLE "thread_members" DROP CONSTRAINT "FK_cf20e37d71b0e1bf1ab633861c8"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_bde0970b6a26bdbd83508addd2"`);
|
||||
await queryRunner.query(`DROP TABLE "thread_members"`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class ThreadMembersIdx1769653303972 implements MigrationInterface {
|
||||
name = "ThreadMembersIdx1769653303972";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE public.thread_members RENAME COLUMN member_id TO member_idx;`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE public.thread_members RENAME COLUMN member_idx TO member_id;`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user