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 { Cron } from '@nestjs/schedule';
|
||||||
import { supabaseAdmin } from '../supabase/supabase.client';
|
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()
|
@Injectable()
|
||||||
export class CleanupService {
|
export class CleanupService {
|
||||||
private readonly logger = new Logger(CleanupService.name);
|
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 * * *')
|
@Cron('0 4 * * *')
|
||||||
async cleanTerminalSagas() {
|
async cleanTerminalSagas() {
|
||||||
|
if (!this.enabled) return;
|
||||||
const cutoff = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
|
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
|
const { error, count } = await supabaseAdmin
|
||||||
.from('saga_instances')
|
.from('saga_instances')
|
||||||
.delete({ count: 'exact' })
|
.delete({ count: 'exact' })
|
||||||
|
|
@ -20,8 +42,23 @@ export class CleanupService {
|
||||||
|
|
||||||
@Cron('0 3 * * *')
|
@Cron('0 3 * * *')
|
||||||
async cleanExpiredNotifications() {
|
async cleanExpiredNotifications() {
|
||||||
|
if (!this.enabled) return;
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
const cutoff90 = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).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
|
const { error: e1, count: c1 } = await supabaseAdmin
|
||||||
.from('notifications')
|
.from('notifications')
|
||||||
.delete({ count: 'exact' })
|
.delete({ count: 'exact' })
|
||||||
|
|
@ -37,7 +74,18 @@ export class CleanupService {
|
||||||
|
|
||||||
@Cron('0 3 1 * *')
|
@Cron('0 3 1 * *')
|
||||||
async cleanProcessedOutbox() {
|
async cleanProcessedOutbox() {
|
||||||
|
if (!this.enabled) return;
|
||||||
const cutoff = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
|
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
|
const { error, count } = await supabaseAdmin
|
||||||
.from('outbox_events')
|
.from('outbox_events')
|
||||||
.delete({ count: 'exact' })
|
.delete({ count: 'exact' })
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue