feat(p10): archivage automatique des sessions terminées (rétention configurable)
- DB: migration #10 (sessions.archived_at + index), helpers archive/unarchive/archiveExpired (soft-archive, jamais de DELETE) - core/retention-settings.ts: session_retention_days (défaut 30, 0=jamais, 1-3650) + session_purge_days (off) - core/session-archive.ts: scheduler start/stop/sweep (calqué DiscoveryService), câblé dans runDaemon - pty-manager.list({includeArchived}) + SessionSummary.archived + emitHistoricalUpdate - routes: GET /sessions?includeArchived, POST/DELETE /sessions/:id/archive, POST /sessions/archive-now - protocole additif: message WS session_archived (relayé par la gateway, abonnés 'sessions') - settings: retentionDays/purgeDays exposés et validés dans PATCH /settings - web: store sessions (showArchived + archive/unarchive), SessionsListView (toggle/badge/actions), SettingsView (slider rétention), i18n EN+FR - tests: retention-settings + session-archive (vitest) + settings-routes étendu ; acceptance-p10.mjs (sweep, event WS, resume d'une session archivée → 201, rétention=0)
This commit is contained in:
48
packages/server/src/core/retention-settings.ts
Normal file
48
packages/server/src/core/retention-settings.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
// Réglages de rétention des sessions terminées (archivage auto), persistés dans `settings`.
|
||||
// Frontière de sécurité : clés NON sensibles, n'entrent dans l'allow-list du PATCH /api/v1/settings
|
||||
// que via les validateurs ci-dessous. Calqué sur scan-settings.ts.
|
||||
import { getSetting } from '../db/index.js';
|
||||
import type { Db } from '../db/index.js';
|
||||
|
||||
export const RETENTION_DAYS_KEY = 'session_retention_days';
|
||||
export const PURGE_DAYS_KEY = 'session_purge_days';
|
||||
|
||||
/** Au-delà de N jours après la fin, une session managée est auto-archivée. 0 = jamais. */
|
||||
export const DEFAULT_RETENTION_DAYS = 30;
|
||||
/** Purge définitive (DELETE) après N jours. 0 = désactivée (conçue mais OFF par défaut). */
|
||||
export const DEFAULT_PURGE_DAYS = 0;
|
||||
export const MIN_RETENTION_DAYS = 1;
|
||||
export const MAX_RETENTION_DAYS = 3650;
|
||||
|
||||
/** Rétention en jours (0 = jamais archiver). Défaut DEFAULT_RETENTION_DAYS. Lecture tolérante. */
|
||||
export function readRetentionDays(db: Db): number {
|
||||
return readDaysSetting(db, RETENTION_DAYS_KEY, DEFAULT_RETENTION_DAYS);
|
||||
}
|
||||
|
||||
/** Purge définitive en jours (0 = désactivée). Défaut DEFAULT_PURGE_DAYS. Lecture tolérante. */
|
||||
export function readPurgeDays(db: Db): number {
|
||||
return readDaysSetting(db, PURGE_DAYS_KEY, DEFAULT_PURGE_DAYS);
|
||||
}
|
||||
|
||||
function readDaysSetting(db: Db, key: string, fallback: number): number {
|
||||
const raw = getSetting(db, key);
|
||||
if (raw === null) return fallback;
|
||||
const n = Number(raw);
|
||||
return isValidDays(n) ? n : fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide un nombre de jours : entier `0` (= désactivé) OU dans [MIN, MAX]. Retourne null si invalide
|
||||
* (⇒ 400). Le même validateur sert pour la rétention et la purge (mêmes bornes, même sémantique de 0).
|
||||
*/
|
||||
export function normalizeRetentionDays(raw: unknown): number | null {
|
||||
return typeof raw === 'number' && isValidDays(raw) ? raw : null;
|
||||
}
|
||||
|
||||
/** Alias sémantique pour la purge (mêmes règles). */
|
||||
export const normalizePurgeDays = normalizeRetentionDays;
|
||||
|
||||
function isValidDays(n: number): boolean {
|
||||
if (!Number.isInteger(n)) return false;
|
||||
return n === 0 || (n >= MIN_RETENTION_DAYS && n <= MAX_RETENTION_DAYS);
|
||||
}
|
||||
Reference in New Issue
Block a user