Files
Apiflow-gateway/sync/db.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

62 lines
1.9 KiB
JavaScript

'use strict';
const { Pool } = require('pg');
/**
* Paramètres de session identiques des deux côtés : indispensables pour que
* ROW(...)::text (signatures par mois) produise le même texte sur la source et la
* destination (fuseau, format des dates/intervalles, précision des flottants).
*/
const SESSION_OPTIONS = [
'-c TimeZone=UTC',
'-c DateStyle=ISO',
'-c IntervalStyle=postgres',
'-c extra_float_digits=3',
'-c bytea_output=hex',
].join(' ');
let srcPool = null;
let dstPool = null;
function makePool(prefix, max) {
const required = ['HOST', 'DB', 'USER', 'PASS'];
const missing = required.filter(k => !process.env[`${prefix}_${k}`]);
if (missing.length) throw new Error(`Variables manquantes : ${missing.map(k => `${prefix}_${k}`).join(', ')}`);
const pool = new Pool({
host: process.env[`${prefix}_HOST`],
port: parseInt(process.env[`${prefix}_PORT`], 10) || 5432,
database: process.env[`${prefix}_DB`],
user: process.env[`${prefix}_USER`],
password: process.env[`${prefix}_PASS`],
max,
connectionTimeoutMillis: 15000,
idleTimeoutMillis: 60000,
keepAlive: true,
application_name: 'ff_sync_hostinger',
options: SESSION_OPTIONS,
});
// Une connexion inactive qui tombe (tunnel frp coupé…) ne doit pas tuer le process
pool.on('error', err => console.error(`[pool ${prefix}] ${err.message}`));
return pool;
}
/** Source : PostgreSQL Debian via le tunnel frps:15432 (lecture seule). */
function getSrc() {
if (!srcPool) srcPool = makePool('SRC', 3);
return srcPool;
}
/** Destination : PostgreSQL Hostinger (container postgres_db). */
function getDst() {
if (!dstPool) dstPool = makePool('DST', 6);
return dstPool;
}
async function closeAll() {
const p = [];
if (srcPool) { p.push(srcPool.end()); srcPool = null; }
if (dstPool) { p.push(dstPool.end()); dstPool = null; }
await Promise.allSettled(p);
}
module.exports = { getSrc, getDst, closeAll, SESSION_OPTIONS };