mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-11 17:27:31 +02:00
perf(serveur): réponses API allégées, compression, cache des assets et index
- Fichiers statiques servis avant la session (plus de requêtes SQL par asset), /assets en cache immuable 1 an, index.html en no-cache, et compression gzip/brotli des réponses (dépendance compression, externe dans le bundle esbuild du Dockerfile). - req.user (déjà chargé par deserializeUser) réutilisé dans les handlers au lieu de relire l'utilisateur et ses magasins à chaque appel ; GET /api/user ne refait plus de requête. - Listes : le magasin joint est réduit aux champs lus par l'interface (plus de logo base64 ni de configuration SMTP/NocoDB dans chaque ligne), plus aucune empreinte de mot de passe dans les créateurs/auteurs ni dans /api/users. - N+1 supprimés (/api/users, annonces, historique SAV, caches de vérification des factures), requêtes indépendantes en parallèle (stats, analytics, météo, getDelivery, getUserWithGroups), jointure multiplicative des statistiques par magasin corrigée. - Échéancier limité au magasin demandé ; filtre status sur GET /api/deliveries. - Index de performance créés en arrière-plan au démarrage (CREATE INDEX CONCURRENTLY, reliquats invalides purgés sans verrou exclusif). - La connexion n'attend plus la sauvegarde quotidienne ; purge du cache des factures active en production ; logs volumineux retirés des chemins chauds ; NODE_ENV fixé dans l'image. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MsDJjQrAggcJwbbBtKhgyb
This commit is contained in:
13 files changed
+1113
-1116
No files matched your search
+30
-4
@@ -3,7 +3,7 @@ import { promisify } from 'util';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { eq, desc } from "drizzle-orm";
|
||||
import { eq, desc, sql } from "drizzle-orm";
|
||||
import { db } from "./db";
|
||||
import { databaseBackups, utilities, users } from "@shared/schema";
|
||||
import type { DatabaseBackup, InsertDatabaseBackup } from "@shared/schema";
|
||||
@@ -14,6 +14,8 @@ export class BackupService {
|
||||
private backupDir: string;
|
||||
private maxBackups = 10;
|
||||
private lastAutomaticBackupDate: string | null = null;
|
||||
// Vérification quotidienne en cours, partagée par les connexions simultanées
|
||||
private dailyBackupCheck: Promise<{ backupPerformed: boolean; message: string }> | null = null;
|
||||
|
||||
constructor() {
|
||||
// Use /app/backups in production (with proper permissions), or use env variable
|
||||
@@ -130,9 +132,10 @@ export class BackupService {
|
||||
await execAsync(command, { env });
|
||||
|
||||
// Get file stats and count tables
|
||||
const stats = fs.statSync(filepath);
|
||||
const sqlContent = fs.readFileSync(filepath, 'utf8');
|
||||
const tablesCount = (sqlContent.match(/CREATE TABLE/g) || []).length;
|
||||
// Tables comptées en base plutôt qu'en relisant tout le dump en mémoire :
|
||||
// la lecture synchrone et la regex gelaient le serveur sur les grosses bases
|
||||
const stats = await fs.promises.stat(filepath);
|
||||
const tablesCount = await this.countDatabaseTables();
|
||||
|
||||
// Update database record with completion details
|
||||
const [updatedBackup] = await db.update(databaseBackups)
|
||||
@@ -156,6 +159,18 @@ export class BackupService {
|
||||
}
|
||||
}
|
||||
|
||||
// Nombre de tables utilisateur (hors schémas système), proche de ce que
|
||||
// pg_dump inclut dans la sauvegarde
|
||||
private async countDatabaseTables(): Promise<number> {
|
||||
const result = await db.execute(sql`
|
||||
SELECT count(*)::int AS count
|
||||
FROM information_schema.tables
|
||||
WHERE table_type = 'BASE TABLE'
|
||||
AND table_schema NOT IN ('pg_catalog', 'information_schema')
|
||||
`);
|
||||
return Number(result.rows[0]?.count ?? 0);
|
||||
}
|
||||
|
||||
async getBackupList(): Promise<DatabaseBackup[]> {
|
||||
try {
|
||||
const backups = await db.select()
|
||||
@@ -238,6 +253,17 @@ export class BackupService {
|
||||
|
||||
// Nouvelle méthode : Vérifier et effectuer une sauvegarde quotidienne si nécessaire
|
||||
async checkAndPerformDailyBackup(userId: string = 'system'): Promise<{ backupPerformed: boolean; message: string }> {
|
||||
// Plusieurs connexions simultanées (ouverture des magasins) partagent la
|
||||
// même vérification : un seul pg_dump est lancé
|
||||
if (!this.dailyBackupCheck) {
|
||||
this.dailyBackupCheck = this.performDailyBackupCheck(userId).finally(() => {
|
||||
this.dailyBackupCheck = null;
|
||||
});
|
||||
}
|
||||
return this.dailyBackupCheck;
|
||||
}
|
||||
|
||||
private async performDailyBackupCheck(userId: string): Promise<{ backupPerformed: boolean; message: string }> {
|
||||
try {
|
||||
// Vérifier si les backups automatiques sont activés
|
||||
const [config] = await db.select()
|
||||
|
||||
Reference in new issue
Block a user