Files
debian af66d926e7 fase(27): advanced enterprise features complete
- 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>
2026-03-08 13:49:14 -04:00

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;