feat(cleanup): add DRY_RUN guard + CLEANUP_ENABLED safety valve + pre-flight counts

This commit is contained in:
admin-valentin 2026-07-31 14:41:08 +00:00
parent 00f0fc3f34
commit 70fe059142

View file

@ -2,13 +2,35 @@ import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { supabaseAdmin } from '../supabase/supabase.client';
/**
* DRY_RUN=true: logs what would be deleted without executing DELETE.
* CLEANUP_ENABLED=false: disables all cleanup crons (staging/dev safety valve).
*/
@Injectable()
export class CleanupService {
private readonly logger = new Logger(CleanupService.name);
private readonly dryRun = process.env.DRY_RUN === 'true';
private readonly enabled = process.env.CLEANUP_ENABLED !== 'false';
private log(msg: string) {
this.logger.log(this.dryRun ? `[DRY_RUN] ${msg}` : msg);
}
@Cron('0 4 * * *')
async cleanTerminalSagas() {
if (!this.enabled) return;
const cutoff = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
// Pre-flight count
const { count: preview } = await supabaseAdmin
.from('saga_instances')
.select('id', { count: 'exact', head: true })
.in('status', ['completed', 'failed', 'compensated'])
.lt('updated_at', cutoff);
this.log(`saga cleanup: ${preview ?? 0} terminal instances eligible`);
if (this.dryRun) return;
const { error, count } = await supabaseAdmin
.from('saga_instances')
.delete({ count: 'exact' })
@ -20,8 +42,23 @@ export class CleanupService {
@Cron('0 3 * * *')
async cleanExpiredNotifications() {
if (!this.enabled) return;
const now = new Date().toISOString();
const cutoff90 = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString();
const { count: expiredCount } = await supabaseAdmin
.from('notifications')
.select('id', { count: 'exact', head: true })
.not('expires_at', 'is', null)
.lt('expires_at', now);
const { count: oldCount } = await supabaseAdmin
.from('notifications')
.select('id', { count: 'exact', head: true })
.lt('created_at', cutoff90);
this.log(`notification cleanup: ${(expiredCount ?? 0) + (oldCount ?? 0)} eligible`);
if (this.dryRun) return;
const { error: e1, count: c1 } = await supabaseAdmin
.from('notifications')
.delete({ count: 'exact' })
@ -37,7 +74,18 @@ export class CleanupService {
@Cron('0 3 1 * *')
async cleanProcessedOutbox() {
if (!this.enabled) return;
const cutoff = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
const { count: preview } = await supabaseAdmin
.from('outbox_events')
.select('id', { count: 'exact', head: true })
.not('processed_at', 'is', null)
.lt('processed_at', cutoff);
this.log(`outbox cleanup: ${preview ?? 0} processed events eligible`);
if (this.dryRun) return;
const { error, count } = await supabaseAdmin
.from('outbox_events')
.delete({ count: 'exact' })