Files
movie-select/scripts/migrate-data.ts
2026-03-03 15:54:32 +01:00

113 lines
3.8 KiB
TypeScript

/**
* One-time migration: MariaDB → PostgreSQL
*
* Reads all data from the existing MariaDB database and inserts into PostgreSQL.
* Requires both DATABASE_URL (postgres) and MARIA_* env vars.
*
* Usage: node --env-file=.env scripts/migrate-data.ts
*/
import mysql from "mysql2/promise";
import postgres from "postgres";
const MARIA_HOST = process.env.MARIA_HOST ?? "127.0.0.1";
const MARIA_PORT = Number(process.env.MARIA_PORT ?? 3306);
const MARIA_USER = process.env.MARIA_USER ?? "serve";
const MARIA_PASS = process.env.MARIA_PASS ?? "";
const MARIA_DB = process.env.MARIA_DB ?? "serve";
const PG_URL = process.env.DATABASE_URL;
if (!PG_URL) {
console.error("DATABASE_URL is required");
process.exit(1);
}
const maria = await mysql.createConnection({
host: MARIA_HOST,
port: MARIA_PORT,
user: MARIA_USER,
password: MARIA_PASS,
database: MARIA_DB,
timezone: "+00:00",
});
const pgUrl = new URL(PG_URL);
const socketHost = pgUrl.searchParams.get("host");
const pg = postgres({
host: socketHost ?? pgUrl.hostname,
port: Number(pgUrl.port) || 5432,
database: pgUrl.pathname.slice(1),
username: pgUrl.username,
password: pgUrl.password,
});
// Order: rounds → round_users → movies → votes → round_history (FK constraint order)
console.log("Migrating rounds...");
const [roundRows] = await maria.execute("SELECT * FROM rounds");
for (const r of roundRows as mysql.RowDataPacket[]) {
await pg`
INSERT INTO rounds (uuid, phase, setup_done, created_at, updated_at)
VALUES (${r.uuid}, ${r.phase}, ${Boolean(r.setup_done)}, ${r.created_at}, ${r.updated_at})
ON CONFLICT (uuid) DO NOTHING
`;
}
console.log(` ${(roundRows as mysql.RowDataPacket[]).length} rounds`);
console.log("Migrating round_users...");
const [userRows] = await maria.execute("SELECT * FROM round_users");
for (const u of userRows as mysql.RowDataPacket[]) {
await pg`
INSERT INTO round_users (round_uuid, name, done_phase1, done_phase2, sort_order)
VALUES (${u.round_uuid}, ${u.name}, ${Boolean(u.done_phase1)}, ${Boolean(u.done_phase2)}, ${u.sort_order})
ON CONFLICT (round_uuid, name) DO NOTHING
`;
}
console.log(` ${(userRows as mysql.RowDataPacket[]).length} users`);
console.log("Migrating movies...");
const [movieRows] = await maria.execute("SELECT * FROM movies ORDER BY id");
for (const m of movieRows as mysql.RowDataPacket[]) {
await pg`
INSERT INTO movies (id, round_uuid, title, added_by, added_at)
VALUES (${m.id}, ${m.round_uuid}, ${m.title}, ${m.added_by}, ${m.added_at})
ON CONFLICT DO NOTHING
`;
}
console.log(` ${(movieRows as mysql.RowDataPacket[]).length} movies`);
// Reset the serial sequence for movies.id
await pg`SELECT setval('movies_id_seq', COALESCE((SELECT MAX(id) FROM movies), 0) + 1)`;
console.log("Migrating votes...");
const [voteRows] = await maria.execute("SELECT * FROM votes");
for (const v of voteRows as mysql.RowDataPacket[]) {
await pg`
INSERT INTO votes (round_uuid, user_name, movie_title, rating)
VALUES (${v.round_uuid}, ${v.user_name}, ${v.movie_title}, ${v.rating})
ON CONFLICT (round_uuid, user_name, movie_title) DO NOTHING
`;
}
console.log(` ${(voteRows as mysql.RowDataPacket[]).length} votes`);
console.log("Migrating round_history...");
const [historyRows] = await maria.execute(
"SELECT * FROM round_history ORDER BY id",
);
for (const h of historyRows as mysql.RowDataPacket[]) {
await pg`
INSERT INTO round_history (id, round_uuid, winner, movies_json, created_at)
VALUES (${h.id}, ${h.round_uuid}, ${h.winner}, ${h.movies_json}, ${h.created_at})
ON CONFLICT DO NOTHING
`;
}
console.log(
` ${(historyRows as mysql.RowDataPacket[]).length} history entries`,
);
// Reset the serial sequence for round_history.id
await pg`SELECT setval('round_history_id_seq', COALESCE((SELECT MAX(id) FROM round_history), 0) + 1)`;
console.log("Migration complete!");
await maria.end();
await pg.end();