mirror of
https://github.com/R0m1k3/Apiflow-gateway.git
synced 2026-10-21 05:58:15 +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
+44
-24
@@ -1,41 +1,61 @@
|
||||
'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 = new Pool({
|
||||
host: process.env.SRC_HOST,
|
||||
port: parseInt(process.env.SRC_PORT) || 5432,
|
||||
database: process.env.SRC_DB,
|
||||
user: process.env.SRC_USER,
|
||||
password: process.env.SRC_PASS,
|
||||
max: 3,
|
||||
connectionTimeoutMillis: 10000,
|
||||
});
|
||||
}
|
||||
if (!srcPool) srcPool = makePool('SRC', 3);
|
||||
return srcPool;
|
||||
}
|
||||
|
||||
/** Destination : PostgreSQL Hostinger (container postgres_db). */
|
||||
function getDst() {
|
||||
if (!dstPool) {
|
||||
dstPool = new Pool({
|
||||
host: process.env.DST_HOST,
|
||||
port: parseInt(process.env.DST_PORT) || 5432,
|
||||
database: process.env.DST_DB,
|
||||
user: process.env.DST_USER,
|
||||
password: process.env.DST_PASS,
|
||||
max: 5,
|
||||
});
|
||||
}
|
||||
if (!dstPool) dstPool = makePool('DST', 6);
|
||||
return dstPool;
|
||||
}
|
||||
|
||||
async function closeAll() {
|
||||
if (srcPool) { await srcPool.end(); srcPool = null; }
|
||||
if (dstPool) { await dstPool.end(); dstPool = null; }
|
||||
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 };
|
||||
module.exports = { getSrc, getDst, closeAll, SESSION_OPTIONS };
|
||||
Reference in new issue
Block a user