Files
ssessesse-ki-battle/scripts/cleanup-legacy-data.ts
T

44 lines
3.3 KiB
TypeScript

import { mkdir, writeFile } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
import { BSON, MongoClient, type Db } from 'mongodb';
import { LEGACY_EVENT_TYPES } from '../constants/legacy-data.js';
// Run while the old app is stopped. Never called by server startup.
export async function cleanupLegacyData(db: Db, backupPath?: string) {
const matchesExist = (await db.listCollections({ name: 'matches' }).toArray()).length > 0;
const matches = await db.collection('matches').find().toArray();
const events = await db.collection('analytics_events').find({ source: { $ne: 'client' }, eventType: { $in: LEGACY_EVENT_TYPES } }).toArray();
const samples = await db.collection('analytics_presence_samples').find({ source: { $exists: false } }).toArray();
const players = await db.collection('players').find({ firstMatchAt: { $exists: true } }, { projection: { _id: 1, firstMatchAt: 1 } }).toArray();
const counts = { matches: matches.length, events: events.length, samples: samples.length, playerFields: players.length };
if (!backupPath) return { applied: false, database: db.databaseName, counts };
// Persist BSON types and indexes before the first mutation. Refuse overwriting a backup.
const backup = { database: db.databaseName, createdAt: new Date(), matches, events, samples, players,
matchIndexes: matchesExist ? await db.collection('matches').indexes() : [] };
await mkdir(dirname(backupPath), { recursive: true });
await writeFile(backupPath, BSON.EJSON.stringify(backup, { relaxed: false }), { flag: 'wx' });
if (matchesExist) {
if (await db.collection('matches').countDocuments() !== matches.length) throw new Error('MATCHES_CHANGED_STOP_OLD_SERVER');
await db.collection('matches').drop();
}
if (events.length) await db.collection('analytics_events').deleteMany({ _id: { $in: events.map(e => e._id) }, source: { $ne: 'client' }, eventType: { $in: LEGACY_EVENT_TYPES } });
if (samples.length) await db.collection('analytics_presence_samples').deleteMany({ _id: { $in: samples.map(s => s._id) }, source: { $exists: false } });
if (players.length) await db.collection('players').updateMany({ _id: { $in: players.map(p => p._id) } }, { $unset: { firstMatchAt: '' } });
return { applied: true, database: db.databaseName, counts, backupPath };
}
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
const args = process.argv.slice(2), apply = args.includes('--apply');
const database = args[args.indexOf('--database') + 1];
const configured = process.env.MONGODB_DATABASE || 'dragon_ball';
if (!args.includes('--database') || database !== configured || ['admin', 'config', 'local'].includes(database)) throw new Error('Specify --database matching MONGODB_DATABASE');
if (!process.env.MONGODB_URI) throw new Error('MONGODB_URI_REQUIRED');
const backup = args.includes('--backup') ? args[args.indexOf('--backup') + 1] : undefined;
if (apply && (!backup || backup.startsWith('--'))) throw new Error('--apply requires --backup <new-file>');
const client = new MongoClient(process.env.MONGODB_URI, { serverSelectionTimeoutMS: 5000 });
try { await client.connect(); console.log(JSON.stringify(await cleanupLegacyData(client.db(database), apply ? resolve(backup!) : undefined), null, 2)); }
finally { await client.close(); }
}