Maj export

This commit is contained in:
2026-06-16 16:47:06 +02:00
parent 80506ca2dc
commit c23430d915
7 changed files with 818 additions and 5 deletions
+125
View File
@@ -0,0 +1,125 @@
/**
* autoExport.js — Job planifié à 3h00 du matin.
* Génère un export complet (DB + assets) et le conserve dans DATA_DIR/exports/.
* Résultat tracé dans job_logs (job_name = 'auto_export').
*/
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import { fileURLToPath } from 'node:url';
import db from '../db/index.js';
import { createZip } from '../utils/zip.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const dataDir = process.env.DATA_DIR
? path.resolve(process.env.DATA_DIR)
: path.resolve(__dirname, '../../../data');
const exportsDir = path.join(dataDir, 'exports');
const MAX_EXPORTS = 10;
const JOB_NAME = 'auto_export';
function writeLog({ status, nbChanges = 0, details = null, errorMsg = null }) {
try {
db.prepare(`
INSERT INTO job_logs (job_name, status, nb_changes, details, error_msg)
VALUES (?, ?, ?, ?, ?)
`).run(JOB_NAME, status, nbChanges, details, errorMsg);
} catch (e) {
console.error('[autoExport] Impossible d\'écrire dans job_logs :', e.message);
}
}
function purgeOldExports() {
try {
fs.mkdirSync(exportsDir, { recursive: true });
const files = fs.readdirSync(exportsDir)
.filter(f => f.endsWith('.zip'))
.map(f => ({ f, mtime: fs.statSync(path.join(exportsDir, f)).mtimeMs }))
.sort((a, b) => b.mtime - a.mtime);
for (const { f } of files.slice(MAX_EXPORTS)) {
fs.unlinkSync(path.join(exportsDir, f));
}
} catch (e) {
console.error('[autoExport] Erreur purge :', e.message);
}
}
export async function runAutoExport() {
const tmpDb = path.join(os.tmpdir(), `cl-auto-export-${Date.now()}.db`);
const startedAt = Date.now();
console.log('[autoExport] Démarrage de l\'export automatique…');
try {
await db.backup(tmpDb);
const dbData = fs.readFileSync(tmpDb);
const now = new Date();
const pad = n => String(n).padStart(2, '0');
const ts = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`
+ `_${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
const filename = `crowdlending-export-${ts}.zip`;
const entries = [];
entries.push({
name: 'manifest.json',
data: JSON.stringify({
version: '1.0', app: 'crowdlending',
exported_at: now.toISOString(),
type: 'full-export',
source: 'auto_export_job',
}, null, 2),
});
entries.push({ name: 'crowdlending.db', data: dbData });
let assetCount = 0;
for (const subdir of ['logos', 'icons']) {
const dir = path.join(dataDir, subdir);
if (!fs.existsSync(dir)) continue;
for (const f of fs.readdirSync(dir)) {
const fpath = path.join(dir, f);
if (fs.statSync(fpath).isFile()) {
entries.push({ name: `${subdir}/${f}`, data: fs.readFileSync(fpath) });
assetCount++;
}
}
}
const zipBuf = createZip(entries);
fs.mkdirSync(exportsDir, { recursive: true });
fs.writeFileSync(path.join(exportsDir, filename), zipBuf);
purgeOldExports();
const elapsed = ((Date.now() - startedAt) / 1000).toFixed(1);
const details = `Fichier : ${filename} | Taille : ${(zipBuf.length / 1024).toFixed(0)} Ko | Assets : ${assetCount} | Durée : ${elapsed}s`;
writeLog({ status: 'ok', nbChanges: 1, details });
console.log(`[autoExport] Export terminé en ${elapsed}s → ${filename}`);
} catch (e) {
console.error('[autoExport] Erreur :', e.message);
writeLog({ status: 'error', nbChanges: 0, errorMsg: e.message });
} finally {
if (fs.existsSync(tmpDb)) fs.unlinkSync(tmpDb);
}
}
/** Planifie runAutoExport() tous les jours à 3h00 locale (sans dérive). */
export function startAutoExportJob() {
function msUntil3am() {
const now = new Date();
const next = new Date(now);
next.setHours(3, 0, 0, 0);
if (next <= now) next.setDate(next.getDate() + 1);
return next.getTime() - now.getTime();
}
function scheduleDailyRun() {
const delay = msUntil3am();
setTimeout(async () => {
await runAutoExport();
scheduleDailyRun();
}, delay);
}
scheduleDailyRun();
console.log(`[autoExport] Job planifié — prochaine exécution dans ${Math.round(msUntil3am() / 60000)} min (3h00)`);
}