Files
system_update/server/routes/db.ts
T
gilles 47fe952240 feat(settings): backup/restore de la base de données (amelioration #4)
- 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>
2026-06-06 06:13:03 +02:00

38 lines
1.2 KiB
TypeScript

// server/routes/db.ts
import { Hono } from "hono";
import { createBackup, prepareRestore, dbInfo } from "../services/dbBackup.js";
export const dbRoutes = new Hono();
// Métadonnées de la base (taille, date, restauration en attente).
dbRoutes.get("/info", (c) => c.json(dbInfo()));
// Télécharge une archive cohérente de la base courante.
dbRoutes.get("/backup", () => {
const { buffer, filename } = createBackup();
return new Response(new Uint8Array(buffer), {
headers: {
"Content-Type": "application/octet-stream",
"Content-Disposition": `attachment; filename="${filename}"`,
"Content-Length": String(buffer.length),
},
});
});
// Restaure depuis une archive uploadée (corps brut). Appliquée au prochain démarrage.
dbRoutes.post("/restore", async (c) => {
try {
const ab = await c.req.arrayBuffer();
if (!ab.byteLength) return c.json({ error: "Archive vide" }, 400);
const { safetyBackup } = prepareRestore(Buffer.from(ab));
return c.json({
ok: true,
restartRequired: true,
safetyBackup,
message: "Restauration préparée. Redémarrez le serveur pour l'appliquer.",
});
} catch (err) {
return c.json({ error: (err as Error).message }, 400);
}
});