mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 14:06:27 +00:00
0aa5b36661
Backup, restore and the demo reset each handled the SQLite file themselves: backup.impl closed and reinitialized the connection, built MaintenanceRepository with new, and assumed the database was data/travel.db. That tied the whole chain to one local file and hid two bugs. They now go through DATABASE_BACKUP, a port declared in the shared database kernel (snapshot, verify, keep a copy before a restore, replace). SqliteDatabaseBackup implements it with what the code did before: VACUUM INTO for a consistent copy, and a close, copy, sidecar drop, rename and reopen through DatabaseLifecycle for a restore. BackupService injects it and hands it to backup.impl. The demo reset and the baseline save use the same port; the first baseline is saved by DemoResetJob once the app is up, because the boot-time seed runs before anything can be injected. Another engine can plug in a logical dump behind the port. Fixes on the way: - The backup and the restore ignored TREK_DB_FILE. The backup looked for data/travel.db and, with the database elsewhere, silently left it out of the archive; the restore wrote data/travel.db and never replaced the live database. Both now use DatabaseLifecycle.file, the path the connection runs on, and the pre-restore copy sits next to it. - A failed VACUUM INTO fell back to archiving the live WAL-mode file without a word, the torn copy the snapshot exists to prevent. The backup now fails: nothing is archived or committed, the existing backups are untouched, a partial snapshot is removed, and the log says why. A missing database file and a failed checkpoint are logged too.
42 lines
2.0 KiB
TypeScript
42 lines
2.0 KiB
TypeScript
import { Inject, Injectable } from '@nestjs/common';
|
|
import type { Response } from 'express';
|
|
import { StorageService } from '../storage/storage.service';
|
|
import { DATABASE_BACKUP, type DatabaseBackupStrategy } from '../database/database-backup.interface';
|
|
import * as svc from './backup.impl';
|
|
|
|
/**
|
|
* The backup domain's injectable face.
|
|
*
|
|
* The implementation moved here from src/services/backupService.ts with the code
|
|
* unchanged, and stays a module rather than becoming methods on this class. What
|
|
* made that the cautious choice was that the zip packing and the restore path
|
|
* closed and reinitialized the core DB handle themselves. They no longer do:
|
|
* every database step goes through the injected backup port
|
|
* (`DATABASE_BACKUP`, `SqliteDatabaseBackup` today), which owns the snapshot and
|
|
* the swap, and through it `DatabaseLifecycle`, which owns the connection.
|
|
*/
|
|
@Injectable()
|
|
export class BackupService {
|
|
private readonly deps: svc.BackupDeps;
|
|
|
|
constructor(
|
|
private readonly storage: StorageService,
|
|
@Inject(DATABASE_BACKUP) database: DatabaseBackupStrategy,
|
|
) {
|
|
this.deps = { storage, database };
|
|
}
|
|
|
|
listBackups() { return svc.listBackups(this.storage); }
|
|
createBackup(prefix?: 'backup' | 'auto-backup') { return svc.createBackup(this.deps, prefix); }
|
|
restoreFromZip(zipPath: string) { return svc.restoreFromZip(this.deps, zipPath); }
|
|
restoreBackup(filename: string) { return svc.restoreBackup(this.deps, filename); }
|
|
deleteBackup(filename: string) { return svc.deleteBackup(this.storage, filename); }
|
|
|
|
isValidBackupFilename(filename: string) { return svc.isValidBackupFilename(filename); }
|
|
backupFileExists(filename: string) { return svc.backupFileExists(this.storage, filename); }
|
|
sendBackupToResponse(filename: string, res: Response) { return svc.sendBackupToResponse(this.storage, filename, res); }
|
|
checkRateLimit(key: string, maxAttempts: number, windowMs: number) { return svc.checkRateLimit(key, maxAttempts, windowMs); }
|
|
|
|
get rateWindow() { return svc.BACKUP_RATE_WINDOW; }
|
|
}
|