feat(cleanup): add DRY_RUN guard + CLEANUP_ENABLED safety valve + pre-flight counts
This commit is contained in:
parent
00f0fc3f34
commit
70fe059142
1 changed files with 48 additions and 0 deletions
|
|
@ -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' })
|
||||
|
|
|
|||
Loading…
Reference in a new issue