diff --git a/backend/src/jobs/autoExport.js b/backend/src/jobs/autoExport.js index 48f3902..3ad613a 100644 --- a/backend/src/jobs/autoExport.js +++ b/backend/src/jobs/autoExport.js @@ -51,7 +51,7 @@ export async function runAutoExport() { console.log('[autoExport] Démarrage de l\'export automatique…'); try { - await db.backup(tmpDb); + db.exec(`VACUUM INTO '${tmpDb.replace(/'/g, "''")}'`); const dbData = fs.readFileSync(tmpDb); const now = new Date(); diff --git a/backend/src/routes/admin.js b/backend/src/routes/admin.js index 8883975..7215f49 100644 --- a/backend/src/routes/admin.js +++ b/backend/src/routes/admin.js @@ -6,6 +6,7 @@ import path from 'node:path'; import os from 'node:os'; import { fileURLToPath } from 'node:url'; import db from '../db/index.js'; +import Database from 'better-sqlite3'; import { HttpError } from '../middleware/errorHandler.js'; import { checkStatutsRetard } from '../jobs/autoStatut.js'; import { runAutoExport } from '../jobs/autoExport.js'; @@ -453,10 +454,10 @@ function purgeOldExports() { * GET /api/admin/export-full * Génère un export ZIP, le sauvegarde sur disque, et le retourne au navigateur. */ -router.get('/export-full', async (req, res, next) => { +router.get('/export-full', (req, res, next) => { const tmpDb = path.join(os.tmpdir(), `cl-backup-${Date.now()}.db`); try { - await db.backup(tmpDb); + db.exec(`VACUUM INTO '${tmpDb.replace(/'/g, "''")}'`); const dbData = fs.readFileSync(tmpDb); const now = new Date(); @@ -617,7 +618,7 @@ router.post('/exports/:filename/restore', async (req, res, next) => { if (!dbEntry) throw new HttpError(400, 'crowdlending.db introuvable dans l\'archive'); // 1. Sauvegarde de sécurité de l'état courant - await db.backup(tmpDb); + db.exec(`VACUUM INTO '${tmpDb.replace(/'/g, "''")}'`); const backupEntries = []; backupEntries.push({ name: 'manifest.json', @@ -656,7 +657,25 @@ router.post('/exports/:filename/restore', async (req, res, next) => { } } - // 3. Écriture du pending-restore (appliqué par db/index.js au prochain démarrage) + // 3. Validation + écriture du pending-restore + { + const tmpValidate = path.join(os.tmpdir(), `cl-validate-${Date.now()}.db`); + let testDb; + try { + fs.writeFileSync(tmpValidate, dbEntry.data); + testDb = new Database(tmpValidate, { readonly: true }); + const check = testDb.pragma('integrity_check'); + if (!check || check[0]?.integrity_check !== 'ok') { + throw new HttpError(500, `Base de données corrompue dans l'archive — integrity_check: ${JSON.stringify(check?.[0])}`); + } + } catch (e) { + if (e instanceof HttpError) throw e; + throw new HttpError(500, `Base de données invalide dans l'archive : ${e.message}`); + } finally { + if (testDb) try { testDb.close(); } catch {} + if (fs.existsSync(tmpValidate)) try { fs.unlinkSync(tmpValidate); } catch {} + } + } fs.writeFileSync(dbPath + '.pending-restore', dbEntry.data); // 4. Réponse puis redémarrage