Files
Apiflow-gateway/sync/index.js
T
MichaelandClaude Opus 5.5 ea084034b9 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>
2026-10-17 11:41:29 +02:00

60 lines
2.2 KiB
JavaScript

'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 { config } = require('./config');
const log = require('./logger');
const { closeAll } = require('./db');
const { runSync } = require('./sync');
const { startDashboard, registerSyncFn, registerNextRunFn } = require('./server');
const args = process.argv.slice(2);
const force = args.includes('--force');
const once = force || args.includes('--once');
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 {
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'));
}