70 lines
2.1 KiB
JavaScript
70 lines
2.1 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}`);
|