311 lines
10 KiB
TypeScript
311 lines
10 KiB
TypeScript
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
|
|
type LegacyRoomRow = {
|
|
id: string;
|
|
name: string;
|
|
description: string | null;
|
|
topic: string | null;
|
|
hostId: string;
|
|
password: string | null;
|
|
hasPassword: number;
|
|
isPrivate: number;
|
|
createdAt: number;
|
|
userCount: number;
|
|
maxUsers: number | null;
|
|
icon: string | null;
|
|
iconUpdatedAt: number | null;
|
|
permissions: string | null;
|
|
sourceId: string | null;
|
|
sourceName: string | null;
|
|
sourceUrl: string | null;
|
|
};
|
|
|
|
type RoomMemberRow = {
|
|
roomId: string;
|
|
memberKey: string;
|
|
id: string;
|
|
oderId: string | null;
|
|
role: string;
|
|
};
|
|
|
|
type LegacyRoomPermissions = {
|
|
adminsManageRooms?: boolean;
|
|
moderatorsManageRooms?: boolean;
|
|
adminsManageIcon?: boolean;
|
|
moderatorsManageIcon?: boolean;
|
|
allowVoice?: boolean;
|
|
allowScreenShare?: boolean;
|
|
allowFileUploads?: boolean;
|
|
slowModeInterval?: number;
|
|
};
|
|
|
|
const SYSTEM_ROLE_IDS = {
|
|
everyone: 'system-everyone',
|
|
moderator: 'system-moderator',
|
|
admin: 'system-admin'
|
|
} as const;
|
|
|
|
function parseLegacyPermissions(rawPermissions: string | null): LegacyRoomPermissions {
|
|
try {
|
|
const parsed = JSON.parse(rawPermissions || '{}') as Record<string, unknown>;
|
|
|
|
return {
|
|
adminsManageRooms: parsed['adminsManageRooms'] === true,
|
|
moderatorsManageRooms: parsed['moderatorsManageRooms'] === true,
|
|
adminsManageIcon: parsed['adminsManageIcon'] === true,
|
|
moderatorsManageIcon: parsed['moderatorsManageIcon'] === true,
|
|
allowVoice: parsed['allowVoice'] !== false,
|
|
allowScreenShare: parsed['allowScreenShare'] !== false,
|
|
allowFileUploads: parsed['allowFileUploads'] !== false,
|
|
slowModeInterval: typeof parsed['slowModeInterval'] === 'number' && Number.isFinite(parsed['slowModeInterval'])
|
|
? parsed['slowModeInterval']
|
|
: 0
|
|
};
|
|
} catch {
|
|
return {
|
|
allowVoice: true,
|
|
allowScreenShare: true,
|
|
allowFileUploads: true,
|
|
slowModeInterval: 0
|
|
};
|
|
}
|
|
}
|
|
|
|
function buildDefaultRoomRoles(legacyPermissions: LegacyRoomPermissions) {
|
|
return [
|
|
{
|
|
roleId: SYSTEM_ROLE_IDS.everyone,
|
|
name: '@everyone',
|
|
color: '#6b7280',
|
|
position: 0,
|
|
isSystem: 1,
|
|
manageServer: 'inherit',
|
|
manageRoles: 'inherit',
|
|
manageChannels: 'inherit',
|
|
manageIcon: 'inherit',
|
|
kickMembers: 'inherit',
|
|
banMembers: 'inherit',
|
|
manageBans: 'inherit',
|
|
deleteMessages: 'inherit',
|
|
joinVoice: legacyPermissions.allowVoice === false ? 'deny' : 'allow',
|
|
shareScreen: legacyPermissions.allowScreenShare === false ? 'deny' : 'allow',
|
|
uploadFiles: legacyPermissions.allowFileUploads === false ? 'deny' : 'allow'
|
|
},
|
|
{
|
|
roleId: SYSTEM_ROLE_IDS.moderator,
|
|
name: 'Moderator',
|
|
color: '#10b981',
|
|
position: 200,
|
|
isSystem: 1,
|
|
manageServer: 'inherit',
|
|
manageRoles: 'inherit',
|
|
manageChannels: legacyPermissions.moderatorsManageRooms ? 'allow' : 'inherit',
|
|
manageIcon: legacyPermissions.moderatorsManageIcon ? 'allow' : 'inherit',
|
|
kickMembers: 'allow',
|
|
banMembers: 'inherit',
|
|
manageBans: 'inherit',
|
|
deleteMessages: 'allow',
|
|
joinVoice: 'inherit',
|
|
shareScreen: 'inherit',
|
|
uploadFiles: 'inherit'
|
|
},
|
|
{
|
|
roleId: SYSTEM_ROLE_IDS.admin,
|
|
name: 'Admin',
|
|
color: '#60a5fa',
|
|
position: 300,
|
|
isSystem: 1,
|
|
manageServer: 'inherit',
|
|
manageRoles: 'inherit',
|
|
manageChannels: legacyPermissions.adminsManageRooms ? 'allow' : 'inherit',
|
|
manageIcon: legacyPermissions.adminsManageIcon ? 'allow' : 'inherit',
|
|
kickMembers: 'allow',
|
|
banMembers: 'allow',
|
|
manageBans: 'allow',
|
|
deleteMessages: 'allow',
|
|
joinVoice: 'inherit',
|
|
shareScreen: 'inherit',
|
|
uploadFiles: 'inherit'
|
|
}
|
|
];
|
|
}
|
|
|
|
function roleIdsForMemberRole(role: string): string[] {
|
|
if (role === 'admin') {
|
|
return [SYSTEM_ROLE_IDS.admin];
|
|
}
|
|
|
|
if (role === 'moderator') {
|
|
return [SYSTEM_ROLE_IDS.moderator];
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
export class NormalizeRoomAccessControl1000000000004 implements MigrationInterface {
|
|
name = 'NormalizeRoomAccessControl1000000000004';
|
|
|
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
await queryRunner.query(`
|
|
CREATE TABLE IF NOT EXISTS "room_roles" (
|
|
"roomId" TEXT NOT NULL,
|
|
"roleId" TEXT NOT NULL,
|
|
"name" TEXT NOT NULL,
|
|
"color" TEXT,
|
|
"position" INTEGER NOT NULL,
|
|
"isSystem" INTEGER NOT NULL DEFAULT 0,
|
|
"manageServer" TEXT NOT NULL DEFAULT 'inherit',
|
|
"manageRoles" TEXT NOT NULL DEFAULT 'inherit',
|
|
"manageChannels" TEXT NOT NULL DEFAULT 'inherit',
|
|
"manageIcon" TEXT NOT NULL DEFAULT 'inherit',
|
|
"kickMembers" TEXT NOT NULL DEFAULT 'inherit',
|
|
"banMembers" TEXT NOT NULL DEFAULT 'inherit',
|
|
"manageBans" TEXT NOT NULL DEFAULT 'inherit',
|
|
"deleteMessages" TEXT NOT NULL DEFAULT 'inherit',
|
|
"joinVoice" TEXT NOT NULL DEFAULT 'inherit',
|
|
"shareScreen" TEXT NOT NULL DEFAULT 'inherit',
|
|
"uploadFiles" TEXT NOT NULL DEFAULT 'inherit',
|
|
PRIMARY KEY ("roomId", "roleId")
|
|
)
|
|
`);
|
|
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "idx_room_roles_roomId" ON "room_roles" ("roomId")`);
|
|
|
|
await queryRunner.query(`
|
|
CREATE TABLE IF NOT EXISTS "room_user_roles" (
|
|
"roomId" TEXT NOT NULL,
|
|
"userKey" TEXT NOT NULL,
|
|
"roleId" TEXT NOT NULL,
|
|
"userId" TEXT NOT NULL,
|
|
"oderId" TEXT,
|
|
PRIMARY KEY ("roomId", "userKey", "roleId")
|
|
)
|
|
`);
|
|
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "idx_room_user_roles_roomId" ON "room_user_roles" ("roomId")`);
|
|
|
|
await queryRunner.query(`
|
|
CREATE TABLE IF NOT EXISTS "room_channel_permissions" (
|
|
"roomId" TEXT NOT NULL,
|
|
"channelId" TEXT NOT NULL,
|
|
"targetType" TEXT NOT NULL,
|
|
"targetId" TEXT NOT NULL,
|
|
"permission" TEXT NOT NULL,
|
|
"value" TEXT NOT NULL,
|
|
PRIMARY KEY ("roomId", "channelId", "targetType", "targetId", "permission")
|
|
)
|
|
`);
|
|
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "idx_room_channel_permissions_roomId" ON "room_channel_permissions" ("roomId")`);
|
|
|
|
const rooms = await queryRunner.query(`
|
|
SELECT "id", "name", "description", "topic", "hostId", "password", "hasPassword", "isPrivate", "createdAt", "userCount", "maxUsers", "icon", "iconUpdatedAt", "permissions", "sourceId", "sourceName", "sourceUrl"
|
|
FROM "rooms"
|
|
`) as LegacyRoomRow[];
|
|
const members = await queryRunner.query(`
|
|
SELECT "roomId", "memberKey", "id", "oderId", "role"
|
|
FROM "room_members"
|
|
`) as RoomMemberRow[];
|
|
|
|
for (const room of rooms) {
|
|
const legacyPermissions = parseLegacyPermissions(room.permissions);
|
|
const roles = buildDefaultRoomRoles(legacyPermissions);
|
|
|
|
for (const role of roles) {
|
|
await queryRunner.query(
|
|
`INSERT OR REPLACE INTO "room_roles" ("roomId", "roleId", "name", "color", "position", "isSystem", "manageServer", "manageRoles", "manageChannels", "manageIcon", "kickMembers", "banMembers", "manageBans", "deleteMessages", "joinVoice", "shareScreen", "uploadFiles") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
[
|
|
room.id,
|
|
role.roleId,
|
|
role.name,
|
|
role.color,
|
|
role.position,
|
|
role.isSystem,
|
|
role.manageServer,
|
|
role.manageRoles,
|
|
role.manageChannels,
|
|
role.manageIcon,
|
|
role.kickMembers,
|
|
role.banMembers,
|
|
role.manageBans,
|
|
role.deleteMessages,
|
|
role.joinVoice,
|
|
role.shareScreen,
|
|
role.uploadFiles
|
|
]
|
|
);
|
|
}
|
|
|
|
for (const member of members.filter((candidateMember) => candidateMember.roomId === room.id)) {
|
|
for (const roleId of roleIdsForMemberRole(member.role)) {
|
|
await queryRunner.query(
|
|
`INSERT OR REPLACE INTO "room_user_roles" ("roomId", "userKey", "roleId", "userId", "oderId") VALUES (?, ?, ?, ?, ?)`,
|
|
[
|
|
room.id,
|
|
member.memberKey,
|
|
roleId,
|
|
member.id,
|
|
member.oderId
|
|
]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
await queryRunner.query(`
|
|
CREATE TABLE "rooms_next" (
|
|
"id" TEXT PRIMARY KEY NOT NULL,
|
|
"name" TEXT NOT NULL,
|
|
"description" TEXT,
|
|
"topic" TEXT,
|
|
"hostId" TEXT NOT NULL,
|
|
"password" TEXT,
|
|
"hasPassword" INTEGER NOT NULL DEFAULT 0,
|
|
"isPrivate" INTEGER NOT NULL DEFAULT 0,
|
|
"createdAt" INTEGER NOT NULL,
|
|
"userCount" INTEGER NOT NULL DEFAULT 0,
|
|
"maxUsers" INTEGER,
|
|
"icon" TEXT,
|
|
"iconUpdatedAt" INTEGER,
|
|
"slowModeInterval" INTEGER NOT NULL DEFAULT 0,
|
|
"sourceId" TEXT,
|
|
"sourceName" TEXT,
|
|
"sourceUrl" TEXT
|
|
)
|
|
`);
|
|
|
|
for (const room of rooms) {
|
|
const legacyPermissions = parseLegacyPermissions(room.permissions);
|
|
|
|
await queryRunner.query(
|
|
`INSERT INTO "rooms_next" ("id", "name", "description", "topic", "hostId", "password", "hasPassword", "isPrivate", "createdAt", "userCount", "maxUsers", "icon", "iconUpdatedAt", "slowModeInterval", "sourceId", "sourceName", "sourceUrl") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
[
|
|
room.id,
|
|
room.name,
|
|
room.description,
|
|
room.topic,
|
|
room.hostId,
|
|
room.password,
|
|
room.hasPassword,
|
|
room.isPrivate,
|
|
room.createdAt,
|
|
room.userCount,
|
|
room.maxUsers,
|
|
room.icon,
|
|
room.iconUpdatedAt,
|
|
legacyPermissions.slowModeInterval ?? 0,
|
|
room.sourceId,
|
|
room.sourceName,
|
|
room.sourceUrl
|
|
]
|
|
);
|
|
}
|
|
|
|
await queryRunner.query(`DROP TABLE "rooms"`);
|
|
await queryRunner.query(`ALTER TABLE "rooms_next" RENAME TO "rooms"`);
|
|
}
|
|
|
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
await queryRunner.query(`DROP TABLE IF EXISTS "room_channel_permissions"`);
|
|
await queryRunner.query(`DROP TABLE IF EXISTS "room_user_roles"`);
|
|
await queryRunner.query(`DROP TABLE IF EXISTS "room_roles"`);
|
|
}
|
|
}
|