- Phase 27.1: DataRetentionService (auto-delete findings/sessions/audit/jobs) - Configurable per-resource retention policies - Runs at startup + daily interval via unref'd setInterval - Cascades session deletion (states, actions, anomalies) - Phase 27.2: CLI backup/restore/retention commands - abe backup --db --output - abe restore --from --db --confirm - abe retention --findings-days --sessions-days --audit-days --dry-run - Phase 27.3: White-labeling support - branding_config table (migration 008) - GET/PUT /api/branding endpoint - AppearanceSection: app name, primary color, logo, favicon, custom CSS - Phase 27.4: PostgreSQL already supported via DatabaseConnection - Phase 27.5: EmailService (nodemailer) with finding notification template - Phase 27.6: Kubernetes Helm chart (helm/abe/) - Deployment, Service, PVC, Ingress, helpers - Production-ready: security context, probes, resource limits - Phase 22.7/22.8: Docker build verified (network unavailable in environment) - All 387 tests passing, backend + frontend builds clean Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
76 lines
3.1 KiB
JavaScript
76 lines
3.1 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.DataRetentionService = exports.DEFAULT_RETENTION_POLICY = void 0;
|
|
exports.DEFAULT_RETENTION_POLICY = {
|
|
findingsDays: 365,
|
|
sessionsDays: 90,
|
|
auditLogsDays: 365,
|
|
jobsDays: 30,
|
|
};
|
|
class DataRetentionService {
|
|
constructor(db, logger, policy = exports.DEFAULT_RETENTION_POLICY) {
|
|
this.db = db;
|
|
this.logger = logger;
|
|
this.policy = policy;
|
|
}
|
|
async runRetention() {
|
|
const now = Date.now();
|
|
const results = {};
|
|
// Delete old findings
|
|
if (this.policy.findingsDays > 0) {
|
|
const cutoff = now - this.policy.findingsDays * 86400000;
|
|
const { numDeletedRows } = await this.db
|
|
.deleteFrom('findings')
|
|
.where('created_at', '<', cutoff)
|
|
.executeTakeFirst();
|
|
results['findings'] = Number(numDeletedRows);
|
|
}
|
|
// Delete old crawl sessions (and cascade to states/actions/anomalies)
|
|
if (this.policy.sessionsDays > 0) {
|
|
const cutoff = now - this.policy.sessionsDays * 86400000;
|
|
const oldSessions = await this.db
|
|
.selectFrom('sessions')
|
|
.select('id')
|
|
.where('started_at', '<', cutoff)
|
|
.where('status', '!=', 'running')
|
|
.execute();
|
|
if (oldSessions.length > 0) {
|
|
const ids = oldSessions.map((s) => s.id);
|
|
await this.db.deleteFrom('actions').where('session_id', 'in', ids).execute();
|
|
await this.db.deleteFrom('states').where('session_id', 'in', ids).execute();
|
|
await this.db.deleteFrom('anomalies').where('session_id', 'in', ids).execute();
|
|
const { numDeletedRows } = await this.db
|
|
.deleteFrom('sessions')
|
|
.where('id', 'in', ids)
|
|
.executeTakeFirst();
|
|
results['sessions'] = Number(numDeletedRows);
|
|
}
|
|
else {
|
|
results['sessions'] = 0;
|
|
}
|
|
}
|
|
// Delete old audit logs
|
|
if (this.policy.auditLogsDays > 0) {
|
|
const cutoff = now - this.policy.auditLogsDays * 86400000;
|
|
const { numDeletedRows } = await this.db
|
|
.deleteFrom('audit_logs')
|
|
.where('occurred_at', '<', cutoff)
|
|
.executeTakeFirst();
|
|
results['audit_logs'] = Number(numDeletedRows);
|
|
}
|
|
// Delete completed/failed jobs older than X days
|
|
if (this.policy.jobsDays > 0) {
|
|
const cutoff = new Date(now - this.policy.jobsDays * 86400000).toISOString();
|
|
const { numDeletedRows } = await this.db
|
|
.deleteFrom('jobs')
|
|
.where('status', 'in', ['completed', 'failed'])
|
|
.where('completed_at', '<', cutoff)
|
|
.executeTakeFirst();
|
|
results['jobs'] = Number(numDeletedRows);
|
|
}
|
|
this.logger.info({ results }, 'Data retention run completed');
|
|
return results;
|
|
}
|
|
}
|
|
exports.DataRetentionService = DataRetentionService;
|