mirror of
https://github.com/R0m1k3/Apiflow-gateway.git
synced 2026-10-22 22:48:12 +02:00
feat: gateway sécurisé, miroir de schéma automatique et sync incrémentale
- Schéma Hostinger recopié automatiquement depuis PG Debian (colonnes, PK, index) : plus d'init.sql maintenu à la main (corrige articles.artcentrale, mvtart.no_id et les 12 tables manquantes) - Stratégies : full transactionnel (DELETE + INSERT, garde-fou de volume) et window (signature mensuelle + 10 derniers jours) pour les grosses tables - Attente de la fin de la sync Debian (_run), pg_try_advisory_lock, cron 07:30 Europe/Paris, force honoré, alertes webhook - Dashboard : basic auth, CSP sans script inline, plus d'innerHTML, port 3002 non publié (accès via Nginx Proxy Manager) - Secrets hors du compose (.env), frps épinglé 0.71.0 avec TLS forcé, allowPorts et additionalScopes - Suppression de sync.sh, tables.js, utils.js, init.sql ; README de déploiement L'ancien mot de passe de la base `post` figure dans l'historique git : il doit être changé sur le serveur (voir README). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f8575b9ff2
commit
ea084034b9
26 files changed
+2767
-922
No files matched your search
+51
-76
@@ -1,84 +1,59 @@
|
||||
'use strict';
|
||||
/**
|
||||
* Point d'entrée du service ff_sync_hostinger.
|
||||
*
|
||||
* node index.js → dashboard + cron (SYNC_CRON, fuseau SYNC_TZ)
|
||||
* node index.js --once → une exécution normale puis sortie
|
||||
* node index.js --force → une exécution forcée (garde-fou volume ignoré,
|
||||
* tables "fenêtre" rechargées entièrement) puis sortie
|
||||
*
|
||||
* Le verrou consultatif PostgreSQL empêche toute exécution concurrente,
|
||||
* y compris entre le service et une commande `docker exec … node index.js --once`.
|
||||
*/
|
||||
const cron = require('node-cron');
|
||||
const { getSrc, getDst, closeAll } = require('./db');
|
||||
const { batchUpsert, fullRefresh, logSync } = require('./utils');
|
||||
const { TABLES } = require('./tables');
|
||||
const {
|
||||
startDashboard, registerSyncFn,
|
||||
setSyncRunning, isSyncRunning,
|
||||
setProgress, clearProgress,
|
||||
} = require('./server');
|
||||
const { config } = require('./config');
|
||||
const log = require('./logger');
|
||||
const { closeAll } = require('./db');
|
||||
const { runSync } = require('./sync');
|
||||
const { startDashboard, registerSyncFn, registerNextRunFn } = require('./server');
|
||||
|
||||
const forceMode = process.argv.includes('--force');
|
||||
const args = process.argv.slice(2);
|
||||
const force = args.includes('--force');
|
||||
const once = force || args.includes('--once');
|
||||
|
||||
async function syncTable(src, dst, table, pk) {
|
||||
const { rows } = await src.query(`SELECT * FROM ${table}`);
|
||||
if (!rows.length) {
|
||||
await logSync(dst, table, 0, 'ok');
|
||||
console.log(`[${table}] 0 lignes (table vide)`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
let count;
|
||||
if (pk.length === 0) {
|
||||
count = await fullRefresh(dst, table, rows);
|
||||
} else {
|
||||
count = await batchUpsert(dst, table, rows, pk);
|
||||
}
|
||||
await logSync(dst, table, count, 'ok');
|
||||
console.log(`[${table}] ${count} lignes`);
|
||||
return count;
|
||||
}
|
||||
|
||||
async function syncAll(force = false) {
|
||||
if (isSyncRunning()) {
|
||||
console.log('Sync déjà en cours — ignorée.');
|
||||
return;
|
||||
}
|
||||
setSyncRunning(true);
|
||||
clearProgress();
|
||||
const start = Date.now();
|
||||
console.log(`\n=== SYNC START ${new Date().toISOString()} ===`);
|
||||
|
||||
const src = getSrc();
|
||||
const dst = getDst();
|
||||
|
||||
try {
|
||||
for (let i = 0; i < TABLES.length; i++) {
|
||||
const { name, pk } = TABLES[i];
|
||||
setProgress(name, i + 1);
|
||||
console.log(`[${i + 1}/${TABLES.length}] Sync ${name}…`);
|
||||
try {
|
||||
await syncTable(src, dst, name, pk);
|
||||
} catch (err) {
|
||||
await logSync(dst, name, 0, 'error', err.message).catch(() => {});
|
||||
console.error(`[${name}] ERREUR: ${err.message}`);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
setSyncRunning(false);
|
||||
clearProgress();
|
||||
}
|
||||
|
||||
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
|
||||
console.log(`=== SYNC DONE en ${elapsed}s ===\n`);
|
||||
}
|
||||
|
||||
registerSyncFn(syncAll);
|
||||
|
||||
if (forceMode) {
|
||||
console.log('Mode force : sync complète en cours...');
|
||||
syncAll(true)
|
||||
.then(() => closeAll())
|
||||
.then(() => process.exit(0))
|
||||
.catch(err => {
|
||||
console.error('Erreur fatale sync:', err.message);
|
||||
if (once) {
|
||||
runSync({ force, trigger: 'cli' })
|
||||
.then(async r => {
|
||||
await closeAll();
|
||||
process.exit(r.skipped || r.status === 'ok' ? 0 : 1);
|
||||
})
|
||||
.catch(async err => {
|
||||
log.error(`Erreur fatale : ${err.message}`);
|
||||
await closeAll();
|
||||
process.exit(1);
|
||||
});
|
||||
} else {
|
||||
startDashboard();
|
||||
console.log('Service sync Hostinger démarré — cron: 0 7 * * * (7h00 chaque nuit)');
|
||||
cron.schedule('0 7 * * *', () => {
|
||||
syncAll(false).catch(err => console.error('Erreur cron sync:', err.message));
|
||||
});
|
||||
if (!cron.validate(config.cron)) {
|
||||
log.error(`SYNC_CRON invalide : "${config.cron}"`);
|
||||
process.exit(1);
|
||||
}
|
||||
registerSyncFn(runSync);
|
||||
const task = cron.schedule(
|
||||
config.cron,
|
||||
() => runSync({ force: false, trigger: 'cron' }).catch(err => log.error(`Sync cron : ${err.message}`)),
|
||||
{ timezone: config.timeZone, name: 'ff-sync', noOverlap: true },
|
||||
);
|
||||
registerNextRunFn(() => task.getNextRun());
|
||||
const server = startDashboard();
|
||||
log.info(`Service sync Hostinger démarré — cron "${config.cron}" (${config.timeZone}), prochaine exécution ${task.getNextRun()?.toISOString() ?? '?'}`);
|
||||
|
||||
const shutdown = sig => {
|
||||
log.info(`${sig} reçu — arrêt (une transaction en cours est annulée par PostgreSQL)`);
|
||||
task.stop();
|
||||
server.close();
|
||||
closeAll().finally(() => process.exit(0));
|
||||
setTimeout(() => process.exit(0), 5000).unref();
|
||||
};
|
||||
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
||||
process.on('SIGINT', () => shutdown('SIGINT'));
|
||||
}
|
||||
Reference in new issue
Block a user