Some checks failed
CI / Build & test (Node 24) (push) Has been cancelled
CI / Pack & boot smoke (Node 22) (push) Has been cancelled
CI / No em/en dashes (push) Has been cancelled
CI / Build & test (Node 22) (push) Has been cancelled
Deploy site (production) / build-and-deploy (push) Successful in 20s
Remplace les 547 tirets cadratins (U+2014) et demi-cadratins (U+2013) des fichiers versionnés par la ponctuation contextuelle adaptée (point médian, deux-points, virgule, parenthèses ; tiret simple pour les plages), sur 122 fichiers (appli, vitrine, doc, tests, workflows, scripts). Ajoute le job CI « lint-dashes » (git grep -P) qui échoue si un tiret cadratin/demi-cadratin réapparaît, hors logo binaire et captures brutes du terminal (fidélité des fixtures de détection de dialogue).
76 lines
2.8 KiB
TypeScript
76 lines
2.8 KiB
TypeScript
// Archivage automatique des sessions terminées (P10). Scheduler calqué sur DiscoveryService :
|
|
// start()/stop() + setInterval().unref(). Soft-archive uniquement (jamais de DELETE dans cette
|
|
// itération ; la purge définitive est conçue mais désactivée par défaut, cf. retention-settings).
|
|
// Démarré dans runDaemon (jamais dans buildApp → ne tourne pas pendant les tests unitaires).
|
|
import { EventEmitter } from 'node:events';
|
|
import { archiveExpiredSessions, type Db } from '../db/index.js';
|
|
import { recordAudit } from './audit-log.js';
|
|
import { readRetentionDays } from './retention-settings.js';
|
|
|
|
/** Balayage toutes les 6 h : la rétention se compte en jours, pas besoin de plus fréquent. */
|
|
const DEFAULT_SWEEP_INTERVAL_MS = 6 * 60 * 60 * 1000;
|
|
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
|
|
export interface SessionArchiveEvents {
|
|
/** une session vient d'être archivée (relayée en `session_archived` par la gateway). */
|
|
session_archived: [{ id: string; archivedAt: string }];
|
|
}
|
|
|
|
export interface SessionArchiveOptions {
|
|
db: Db;
|
|
intervalMs?: number;
|
|
}
|
|
|
|
export class SessionArchiveService extends EventEmitter<SessionArchiveEvents> {
|
|
private readonly db: Db;
|
|
private readonly intervalMs: number;
|
|
private timer: NodeJS.Timeout | null = null;
|
|
|
|
constructor(opts: SessionArchiveOptions) {
|
|
super();
|
|
this.db = opts.db;
|
|
this.intervalMs = opts.intervalMs ?? DEFAULT_SWEEP_INTERVAL_MS;
|
|
}
|
|
|
|
start(): void {
|
|
if (this.timer) return;
|
|
this.sweep(); // passage immédiat au boot (synchrone, bon marché : un seul UPDATE borné)
|
|
this.timer = setInterval(() => this.sweep(), this.intervalMs);
|
|
this.timer.unref(); // ne maintient pas le process en vie
|
|
}
|
|
|
|
stop(): void {
|
|
if (this.timer) {
|
|
clearInterval(this.timer);
|
|
this.timer = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Archive les sessions terminées plus anciennes que la rétention configurée. Retourne le nombre
|
|
* archivé. Tolérant : ne lève jamais (une erreur ne doit pas tuer le scheduler). `0 jour` = OFF.
|
|
*/
|
|
sweep(): number {
|
|
try {
|
|
const days = readRetentionDays(this.db);
|
|
if (days <= 0) return 0; // rétention désactivée
|
|
const now = new Date();
|
|
const archivedAt = now.toISOString();
|
|
const cutoffIso = new Date(now.getTime() - days * MS_PER_DAY).toISOString();
|
|
const ids = archiveExpiredSessions(this.db, archivedAt, cutoffIso);
|
|
for (const id of ids) {
|
|
this.emit('session_archived', { id, archivedAt });
|
|
recordAudit(this.db, { actor: 'system', action: 'session.autoArchive', resourceId: id });
|
|
}
|
|
return ids.length;
|
|
} catch {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/** Diffuse l'archivage d'une session (déjà archivée + auditée par l'appelant, ex. route manuelle). */
|
|
emitArchived(id: string, archivedAt: string): void {
|
|
this.emit('session_archived', { id, archivedAt });
|
|
}
|
|
}
|