Files
arboretum/packages/server/src/core/retention-settings.ts
Johan LEROY c8dd539571 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)
2026-06-27 13:31:28 +02:00

49 lines
2.1 KiB
TypeScript

// 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);
}