Améliorations
This commit is contained in:
1 parent
8d104f2058
commit
bab7a79b63
4 files changed
+147
-5
No files matched your search
@@ -14,6 +14,39 @@ fs.mkdirSync(path.dirname(DB_PATH), { recursive: true });
|
||||
// ── Pending restore (déclenché par POST /api/admin/exports/:filename/restore) ─
|
||||
// Si un fichier .pending-restore existe, on l'applique avant d'ouvrir la DB.
|
||||
const PENDING_RESTORE = DB_PATH + '.pending-restore';
|
||||
|
||||
// Tables locales à CET environnement, à préserver à travers une restauration
|
||||
// (cf. feedback_restore_preserve_env_local.md) : une restauration remplace le
|
||||
// fichier DB dans son intégralité, y compris ces tables — sans ce mécanisme,
|
||||
// restaurer un export prod dans dev invalide silencieusement les clés API
|
||||
// générées côté dev (api_keys, y compris les clés internes auto-provisionnées
|
||||
// pour l'assistant IA), écrase la configuration des fournisseurs IA propres
|
||||
// à dev (ai_providers/ai_config) par celle de la source restaurée, et
|
||||
// écraserait de la même façon l'étiquette d'environnement de cette instance
|
||||
// (parametres_environnement — Admin > Général, cf. 05/09/26) par celle de la
|
||||
// source restaurée. Ordre important : ai_config référence ai_providers (FK
|
||||
// provider_defaut_id).
|
||||
const ENV_LOCAL_TABLES = ['api_keys', 'ai_providers', 'ai_config', 'parametres_environnement'];
|
||||
let pendingRestorePreserve = null;
|
||||
if (fs.existsSync(PENDING_RESTORE) && fs.existsSync(DB_PATH)) {
|
||||
try {
|
||||
const oldDb = new Database(DB_PATH, { readonly: true });
|
||||
const existingTables = new Set(
|
||||
oldDb.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().map(r => r.name)
|
||||
);
|
||||
pendingRestorePreserve = {};
|
||||
for (const table of ENV_LOCAL_TABLES) {
|
||||
if (existingTables.has(table)) {
|
||||
pendingRestorePreserve[table] = oldDb.prepare(`SELECT * FROM ${table}`).all();
|
||||
}
|
||||
}
|
||||
oldDb.close();
|
||||
} catch (e) {
|
||||
console.error('[restore] Échec de la sauvegarde des tables locales (api_keys/ai_providers/ai_config/parametres_environnement) avant restauration :', e.message);
|
||||
pendingRestorePreserve = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (fs.existsSync(PENDING_RESTORE)) {
|
||||
try {
|
||||
fs.copyFileSync(PENDING_RESTORE, DB_PATH);
|
||||
@@ -1794,6 +1827,26 @@ db.exec(`
|
||||
)
|
||||
`);
|
||||
|
||||
// ── Migration : identification de l'environnement (Admin > Général) ─────────
|
||||
// Demande Olivier 05/09/26 : chaque instance (prod / qualification / dev)
|
||||
// affiche et enregistre son propre environnement, choisi manuellement par un
|
||||
// admin — aucune inférence automatique possible ici (contrairement à
|
||||
// appUrl/mcpUrl en dev via import.meta.env.DEV, cf. devOverrides.js : une
|
||||
// instance de qualification tourne sur un build comme la prod, le mode DEV de
|
||||
// Vite ne permet donc pas de la distinguer). Table dédiée (plutôt qu'une
|
||||
// colonne de plus dans smtp_config) et ajoutée à ENV_LOCAL_TABLES ci-dessus :
|
||||
// une restauration de base copiée depuis un autre environnement ne doit
|
||||
// jamais réécrire l'étiquette de CET environnement-ci.
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS parametres_environnement (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
environnement TEXT NOT NULL DEFAULT 'developpement'
|
||||
CHECK (environnement IN ('production', 'qualification', 'developpement')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
)
|
||||
`);
|
||||
db.exec(`INSERT OR IGNORE INTO parametres_environnement (id) VALUES (1)`);
|
||||
|
||||
// ── Migration : email_verified sur users ─────────────────────────────────────
|
||||
{
|
||||
const userCols = db.prepare('PRAGMA table_info(users)').all().map(c => c.name);
|
||||
@@ -2803,6 +2856,38 @@ db.exec('CREATE INDEX IF NOT EXISTS idx_ai_usage_date ON ai_usage_log(created_at
|
||||
}
|
||||
}
|
||||
|
||||
// ── Réapplication des tables locales préservées avant une restauration ──
|
||||
// (cf. feedback_restore_preserve_env_local.md). Placé ici, après toutes les
|
||||
// migrations api_keys/ai_providers/ai_config ci-dessus, pour que le schéma
|
||||
// de la base fraîchement restaurée soit à jour avant qu'on y réinjecte le
|
||||
// contenu propre à CET environnement (sinon une colonne migrée seulement en
|
||||
// aval de ce point serait silencieusement perdue lors de la réinsertion).
|
||||
if (pendingRestorePreserve) {
|
||||
try {
|
||||
const restoreEnvLocal = db.transaction(() => {
|
||||
for (const table of ENV_LOCAL_TABLES) {
|
||||
const rows = pendingRestorePreserve[table];
|
||||
if (!rows) continue; // table absente de l'environnement d'avant restauration
|
||||
db.exec(`DELETE FROM ${table}`);
|
||||
if (!rows.length) continue;
|
||||
const currentCols = new Set(db.prepare(`PRAGMA table_info(${table})`).all().map(c => c.name));
|
||||
// Ne garder que les colonnes existant réellement dans le schéma courant :
|
||||
// un export source plus ancien ou plus récent peut avoir un jeu de
|
||||
// colonnes légèrement différent.
|
||||
const usableCols = Object.keys(rows[0]).filter(c => currentCols.has(c));
|
||||
const placeholders = usableCols.map(() => '?').join(',');
|
||||
const insert = db.prepare(`INSERT INTO ${table} (${usableCols.join(',')}) VALUES (${placeholders})`);
|
||||
for (const row of rows) insert.run(...usableCols.map(c => row[c]));
|
||||
}
|
||||
});
|
||||
restoreEnvLocal();
|
||||
console.log('[restore] Tables locales (api_keys/ai_providers/ai_config/parametres_environnement) préservées et réappliquées après restauration.');
|
||||
} catch (e) {
|
||||
console.error('[restore] Échec de la réapplication des tables locales préservées :', e.message);
|
||||
}
|
||||
pendingRestorePreserve = null;
|
||||
}
|
||||
|
||||
// Conversations de l'assistant IA — plusieurs fils nommés par couple
|
||||
// user/investisseur (même granularité de scope que le reste de l'appli, cf.
|
||||
// requireInvestisseur), consultables et supprimables individuellement
|
||||
|
||||
@@ -27,6 +27,9 @@ function ensureRow() {
|
||||
// colonne applique automatiquement son DEFAULT (5 Go / 500 documents,
|
||||
// cf. migration dans db/index.js) quand elle est omise d'un INSERT.
|
||||
}
|
||||
// parametres_environnement : normalement déjà créée avec sa ligne par défaut
|
||||
// par la migration dans db/index.js (INSERT OR IGNORE) — filet de sécurité ici.
|
||||
db.prepare('INSERT OR IGNORE INTO parametres_environnement (id) VALUES (1)').run();
|
||||
}
|
||||
|
||||
// 1 Go = 1024^3 octets (convention déjà utilisée par fmtOctets côté frontend).
|
||||
@@ -36,9 +39,11 @@ router.get('/', (_req, res, next) => {
|
||||
try {
|
||||
ensureRow();
|
||||
const row = db.prepare('SELECT app_name, app_url, mcp_url, allow_registration, min_password_length, documents_quota_bytes, documents_quota_count FROM smtp_config WHERE id = 1').get();
|
||||
const envRow = db.prepare('SELECT environnement FROM parametres_environnement WHERE id = 1').get();
|
||||
res.json({
|
||||
appName: row.app_name || 'Crowdlending Tracker',
|
||||
appUrl: row.app_url || '',
|
||||
environnement: envRow?.environnement || 'developpement',
|
||||
mcpUrl: row.mcp_url || '',
|
||||
allowRegistration: row.allow_registration !== 0,
|
||||
minPasswordLength: row.min_password_length || 8,
|
||||
@@ -51,6 +56,7 @@ router.get('/', (_req, res, next) => {
|
||||
const PatchSchema = z.object({
|
||||
appName: z.string().min(1).max(100).optional(),
|
||||
appUrl: z.string().max(500).optional(),
|
||||
environnement: z.enum(['production', 'qualification', 'developpement']).optional(),
|
||||
mcpUrl: z.string().max(500).optional(),
|
||||
allowRegistration: z.boolean().optional(),
|
||||
minPasswordLength: z.number().int().min(6).max(64).optional(),
|
||||
@@ -84,6 +90,10 @@ router.patch('/', (req, res, next) => {
|
||||
body.documentsQuotaCount !== undefined ? body.documentsQuotaCount : (row.documents_quota_count || 500),
|
||||
);
|
||||
|
||||
if (body.environnement !== undefined) {
|
||||
db.prepare('UPDATE parametres_environnement SET environnement = ? WHERE id = 1').run(body.environnement);
|
||||
}
|
||||
|
||||
res.json({ ok: true });
|
||||
} catch (e) { next(e); }
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user