120 lines
3.5 KiB
TypeScript
120 lines
3.5 KiB
TypeScript
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
|
|
interface LegacyServerRow {
|
|
id: string;
|
|
channels: string | null;
|
|
}
|
|
|
|
interface LegacyServerChannel {
|
|
id: string;
|
|
name: string;
|
|
type: 'text' | 'voice';
|
|
position: number;
|
|
}
|
|
|
|
function normalizeLegacyChannels(raw: string | null): LegacyServerChannel[] {
|
|
try {
|
|
const parsed = JSON.parse(raw || '[]');
|
|
|
|
if (!Array.isArray(parsed)) {
|
|
return [];
|
|
}
|
|
|
|
const seenIds = new Set<string>();
|
|
const seenNames = new Set<string>();
|
|
|
|
return parsed
|
|
.filter((channel): channel is Record<string, unknown> => !!channel && typeof channel === 'object')
|
|
.map((channel, index) => {
|
|
const id = typeof channel.id === 'string' ? channel.id.trim() : '';
|
|
const name = typeof channel.name === 'string' ? channel.name.trim().replace(/\s+/g, ' ') : '';
|
|
const type = channel.type === 'text' || channel.type === 'voice' ? channel.type : null;
|
|
const position = typeof channel.position === 'number' ? channel.position : index;
|
|
const nameKey = type ? `${type}:${name.toLocaleLowerCase()}` : '';
|
|
|
|
if (!id || !name || !type || seenIds.has(id) || seenNames.has(nameKey)) {
|
|
return null;
|
|
}
|
|
|
|
seenIds.add(id);
|
|
seenNames.add(nameKey);
|
|
|
|
return {
|
|
id,
|
|
name,
|
|
type,
|
|
position
|
|
} satisfies LegacyServerChannel;
|
|
})
|
|
.filter((channel): channel is LegacyServerChannel => !!channel);
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function shouldRestoreLegacyVoiceGeneral(channels: LegacyServerChannel[]): boolean {
|
|
const hasTextGeneral = channels.some(
|
|
(channel) => channel.type === 'text' && (channel.id === 'general' || channel.name.toLocaleLowerCase() === 'general')
|
|
);
|
|
const hasVoiceAfk = channels.some(
|
|
(channel) => channel.type === 'voice' && (channel.id === 'vc-afk' || channel.name.toLocaleLowerCase() === 'afk')
|
|
);
|
|
const hasVoiceGeneral = channels.some(
|
|
(channel) => channel.type === 'voice' && (channel.id === 'vc-general' || channel.name.toLocaleLowerCase() === 'general')
|
|
);
|
|
|
|
return hasTextGeneral && hasVoiceAfk && !hasVoiceGeneral;
|
|
}
|
|
|
|
function repairLegacyVoiceChannels(channels: LegacyServerChannel[]): LegacyServerChannel[] {
|
|
if (!shouldRestoreLegacyVoiceGeneral(channels)) {
|
|
return channels;
|
|
}
|
|
|
|
const textChannels = channels.filter((channel) => channel.type === 'text');
|
|
const voiceChannels = channels.filter((channel) => channel.type === 'voice');
|
|
const repairedVoiceChannels = [
|
|
{
|
|
id: 'vc-general',
|
|
name: 'General',
|
|
type: 'voice' as const,
|
|
position: 0
|
|
},
|
|
...voiceChannels
|
|
].map((channel, index) => ({
|
|
...channel,
|
|
position: index
|
|
}));
|
|
|
|
return [
|
|
...textChannels,
|
|
...repairedVoiceChannels
|
|
];
|
|
}
|
|
|
|
export class RepairLegacyVoiceChannels1000000000003 implements MigrationInterface {
|
|
name = 'RepairLegacyVoiceChannels1000000000003';
|
|
|
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
const rows = await queryRunner.query(`SELECT "id", "channels" FROM "servers"`) as LegacyServerRow[];
|
|
|
|
for (const row of rows) {
|
|
const channels = normalizeLegacyChannels(row.channels);
|
|
const repaired = repairLegacyVoiceChannels(channels);
|
|
|
|
if (JSON.stringify(repaired) === JSON.stringify(channels)) {
|
|
continue;
|
|
}
|
|
|
|
await queryRunner.query(
|
|
`UPDATE "servers" SET "channels" = ? WHERE "id" = ?`,
|
|
[JSON.stringify(repaired), row.id]
|
|
);
|
|
}
|
|
}
|
|
|
|
public async down(_queryRunner: QueryRunner): Promise<void> {
|
|
// Forward-only data repair migration.
|
|
}
|
|
}
|