47fe952240
- service dbBackup : createBackup (VACUUM INTO → archive .db cohérente), validateSqlite (header + integrity_check + schéma), prepareRestore (sauvegarde de sécurité auto + dépôt <db>.incoming) - swap hors-ligne au démarrage (db/client.ts) : aucune corruption d'une base ouverte ; restauration appliquée au redémarrage - routes GET /system/db/info|backup, POST /system/db/restore - lib api : dbInfo / dbBackup (download navigateur) / dbRestore (upload) - SettingsModal : onglet « Base de données » (taille, télécharger, restaurer avec confirmation Popup), icônes database/upload, styles DS variables only Testé end-to-end : backup 184 Ko valide, restore + safety .bak + swap au boot, fichier invalide rejeté. tsc 0 erreur · 91 tests · build OK. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
28 lines
961 B
TypeScript
28 lines
961 B
TypeScript
// server/db/client.ts
|
|
import Database from "better-sqlite3";
|
|
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
import { mkdirSync, existsSync, rmSync, renameSync } from "node:fs";
|
|
import { dirname } from "node:path";
|
|
import { env } from "../env.js";
|
|
import * as schema from "./schema.js";
|
|
|
|
mkdirSync(dirname(env.dbPath), { recursive: true });
|
|
|
|
// Restauration en attente : un fichier `<db>.incoming` déposé par /system/db/restore
|
|
// est appliqué au démarrage (swap hors-ligne = aucune corruption d'une base ouverte).
|
|
const incoming = `${env.dbPath}.incoming`;
|
|
if (existsSync(incoming)) {
|
|
for (const ext of ["", "-wal", "-shm"]) {
|
|
const p = `${env.dbPath}${ext}`;
|
|
if (existsSync(p)) rmSync(p, { force: true });
|
|
}
|
|
renameSync(incoming, env.dbPath);
|
|
}
|
|
|
|
const sqlite = new Database(env.dbPath);
|
|
sqlite.pragma("journal_mode = WAL");
|
|
sqlite.pragma("foreign_keys = ON");
|
|
|
|
export const db = drizzle(sqlite, { schema });
|
|
export { schema, sqlite };
|