143 lines
4.8 KiB
TypeScript
143 lines
4.8 KiB
TypeScript
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
|
|
type LegacyServerRow = {
|
|
id: string;
|
|
tags: string | null;
|
|
channels: string | null;
|
|
};
|
|
|
|
type LegacyServerChannel = {
|
|
id?: unknown;
|
|
name?: unknown;
|
|
type?: unknown;
|
|
position?: unknown;
|
|
};
|
|
|
|
function parseArray<T>(raw: string | null): T[] {
|
|
try {
|
|
const parsed = JSON.parse(raw || '[]');
|
|
|
|
return Array.isArray(parsed) ? parsed as T[] : [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function normalizeChannelName(name: string): string {
|
|
return name.trim().replace(/\s+/g, ' ');
|
|
}
|
|
|
|
function channelNameKey(type: 'text' | 'voice', name: string): string {
|
|
return `${type}:${normalizeChannelName(name).toLocaleLowerCase()}`;
|
|
}
|
|
|
|
function isFiniteNumber(value: unknown): value is number {
|
|
return typeof value === 'number' && Number.isFinite(value);
|
|
}
|
|
|
|
function normalizeServerTags(raw: string | null): string[] {
|
|
return parseArray<unknown>(raw).filter((tag): tag is string => typeof tag === 'string');
|
|
}
|
|
|
|
function normalizeServerChannels(raw: string | null) {
|
|
const channels = parseArray<LegacyServerChannel>(raw);
|
|
const seenIds = new Set<string>();
|
|
const seenNames = new Set<string>();
|
|
|
|
return channels.flatMap((channel, index) => {
|
|
const id = typeof channel.id === 'string' ? channel.id.trim() : '';
|
|
const name = typeof channel.name === 'string' ? normalizeChannelName(channel.name) : '';
|
|
const type = channel.type === 'text' || channel.type === 'voice' ? channel.type : null;
|
|
const position = isFiniteNumber(channel.position) ? channel.position : index;
|
|
const nameKey = type ? channelNameKey(type, name) : '';
|
|
|
|
if (!id || !name || !type || seenIds.has(id) || seenNames.has(nameKey)) {
|
|
return [];
|
|
}
|
|
|
|
seenIds.add(id);
|
|
seenNames.add(nameKey);
|
|
|
|
return [{
|
|
channelId: id,
|
|
name,
|
|
type,
|
|
position
|
|
}];
|
|
});
|
|
}
|
|
|
|
export class NormalizeServerArrays1000000000004 implements MigrationInterface {
|
|
name = 'NormalizeServerArrays1000000000004';
|
|
|
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
await queryRunner.query(`
|
|
CREATE TABLE IF NOT EXISTS "server_tags" (
|
|
"serverId" TEXT NOT NULL,
|
|
"position" INTEGER NOT NULL,
|
|
"value" TEXT NOT NULL,
|
|
PRIMARY KEY ("serverId", "position")
|
|
)
|
|
`);
|
|
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "idx_server_tags_serverId" ON "server_tags" ("serverId")`);
|
|
|
|
await queryRunner.query(`
|
|
CREATE TABLE IF NOT EXISTS "server_channels" (
|
|
"serverId" TEXT NOT NULL,
|
|
"channelId" TEXT NOT NULL,
|
|
"name" TEXT NOT NULL,
|
|
"type" TEXT NOT NULL,
|
|
"position" INTEGER NOT NULL,
|
|
PRIMARY KEY ("serverId", "channelId")
|
|
)
|
|
`);
|
|
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "idx_server_channels_serverId" ON "server_channels" ("serverId")`);
|
|
|
|
const rows = await queryRunner.query(`SELECT "id", "tags", "channels" FROM "servers"`) as LegacyServerRow[];
|
|
|
|
for (const row of rows) {
|
|
for (const [position, tag] of normalizeServerTags(row.tags).entries()) {
|
|
await queryRunner.query(
|
|
`INSERT OR REPLACE INTO "server_tags" ("serverId", "position", "value") VALUES (?, ?, ?)`,
|
|
[row.id, position, tag]
|
|
);
|
|
}
|
|
|
|
for (const channel of normalizeServerChannels(row.channels)) {
|
|
await queryRunner.query(
|
|
`INSERT OR REPLACE INTO "server_channels" ("serverId", "channelId", "name", "type", "position") VALUES (?, ?, ?, ?, ?)`,
|
|
[row.id, channel.channelId, channel.name, channel.type, channel.position]
|
|
);
|
|
}
|
|
}
|
|
|
|
await queryRunner.query(`
|
|
CREATE TABLE "servers_next" (
|
|
"id" TEXT PRIMARY KEY NOT NULL,
|
|
"name" TEXT NOT NULL,
|
|
"description" TEXT,
|
|
"ownerId" TEXT NOT NULL,
|
|
"ownerPublicKey" TEXT NOT NULL,
|
|
"isPrivate" INTEGER NOT NULL DEFAULT 0,
|
|
"maxUsers" INTEGER NOT NULL DEFAULT 0,
|
|
"currentUsers" INTEGER NOT NULL DEFAULT 0,
|
|
"createdAt" INTEGER NOT NULL,
|
|
"lastSeen" INTEGER NOT NULL,
|
|
"passwordHash" TEXT
|
|
)
|
|
`);
|
|
await queryRunner.query(`
|
|
INSERT INTO "servers_next" ("id", "name", "description", "ownerId", "ownerPublicKey", "isPrivate", "maxUsers", "currentUsers", "createdAt", "lastSeen", "passwordHash")
|
|
SELECT "id", "name", "description", "ownerId", "ownerPublicKey", "isPrivate", "maxUsers", "currentUsers", "createdAt", "lastSeen", "passwordHash"
|
|
FROM "servers"
|
|
`);
|
|
await queryRunner.query(`DROP TABLE "servers"`);
|
|
await queryRunner.query(`ALTER TABLE "servers_next" RENAME TO "servers"`);
|
|
}
|
|
|
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
await queryRunner.query(`DROP TABLE IF EXISTS "server_channels"`);
|
|
await queryRunner.query(`DROP TABLE IF EXISTS "server_tags"`);
|
|
}
|
|
}
|