59 lines
2.0 KiB
JavaScript
59 lines
2.0 KiB
JavaScript
import { connect } from '@tursodatabase/database';
|
|
import { readFile } from 'node:fs/promises';
|
|
import { resolve } from 'node:path';
|
|
import process from 'node:process';
|
|
|
|
const backupArgument = process.argv[2];
|
|
if (!backupArgument) {
|
|
console.error('Usage: npm run db:restore -- <backup-file>');
|
|
process.exit(2);
|
|
}
|
|
|
|
const backupPath = resolve(backupArgument);
|
|
const databasePath = resolve(process.env.TDR_DATABASE_PATH || './data/shd-build-development.db');
|
|
const backup = JSON.parse(await readFile(backupPath, 'utf8'));
|
|
if (backup?.format !== 'shd-build-database-backup' || backup?.version !== 1 || !Array.isArray(backup.tables)) {
|
|
throw new Error('The selected file is not a supported SHD.build database backup.');
|
|
}
|
|
|
|
const quoteIdentifier = (identifier) => `"${String(identifier).replaceAll('"', '""')}"`;
|
|
const database = await connect(databasePath);
|
|
|
|
// Restores are deliberately limited to empty databases. This prevents an
|
|
// accidental merge from overwriting contributor-entered records.
|
|
for (const table of backup.tables) {
|
|
const existing = await (
|
|
await database.prepare(`SELECT COUNT(*) AS count FROM ${quoteIdentifier(table.name)}`)
|
|
).get();
|
|
if (Number(existing?.count) > 0) {
|
|
throw new Error(
|
|
`Restore refused: table "${table.name}" is not empty. Use a new migrated database.`
|
|
);
|
|
}
|
|
}
|
|
|
|
let restoredRows = 0;
|
|
await database.exec('BEGIN');
|
|
try {
|
|
for (const table of backup.tables) {
|
|
for (const row of table.rows) {
|
|
const columns = Object.keys(row);
|
|
if (!columns.length) continue;
|
|
const columnSql = columns.map(quoteIdentifier).join(', ');
|
|
const placeholders = columns.map(() => '?').join(', ');
|
|
const insert = await database.prepare(
|
|
`INSERT INTO ${quoteIdentifier(table.name)} (${columnSql}) VALUES (${placeholders})`
|
|
);
|
|
await insert.run(...columns.map((column) => row[column]));
|
|
restoredRows++;
|
|
}
|
|
}
|
|
await database.exec('COMMIT');
|
|
} catch (error) {
|
|
await database.exec('ROLLBACK');
|
|
throw error;
|
|
}
|
|
|
|
console.log(`Restored ${restoredRows} rows into ${databasePath}.`);
|
|
console.log(`Source backup: ${backupPath}`);
|