/** * Run Cleanup Jobs Edge Function * * Executes all automated cleanup tasks for the Sacred Pipeline: * - Expired idempotency keys * - Stale temporary references * - Abandoned locks (deleted/banned users, expired locks) * - Old approved/rejected submissions (90 day retention) * * Designed to be called daily via pg_cron */ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger } from '../_shared/logger.ts'; interface CleanupResult { idempotency_keys?: { deleted: number; success: boolean; error?: string; }; temp_refs?: { deleted: number; oldest_date: string | null; success: boolean; error?: string; }; locks?: { released: number; details: { deleted_user_locks: number; banned_user_locks: number; expired_locks: number; }; success: boolean; error?: string; }; old_submissions?: { deleted: number; by_status: Record; oldest_date: string | null; success: boolean; error?: string; }; execution: { started_at: string; completed_at: string; duration_ms: number; }; } Deno.serve(async (req) => { // Handle CORS preflight if (req.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); } const startTime = Date.now(); try { edgeLogger.info('Starting automated cleanup jobs', { timestamp: new Date().toISOString(), }); // Create Supabase client with service role const supabaseUrl = Deno.env.get('SUPABASE_URL')!; const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!; const supabase = createClient(supabaseUrl, supabaseServiceKey, { auth: { autoRefreshToken: false, persistSession: false, }, }); // Execute the master cleanup function const { data, error } = await supabase.rpc('run_all_cleanup_jobs'); if (error) { edgeLogger.error('Cleanup jobs failed', { error: error.message, code: error.code, duration_ms: Date.now() - startTime, }); return new Response( JSON.stringify({ success: false, error: error.message, duration_ms: Date.now() - startTime, }), { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, } ); } const result = data as CleanupResult; // Log detailed results edgeLogger.info('Cleanup jobs completed successfully', { idempotency_keys_deleted: result.idempotency_keys?.deleted || 0, temp_refs_deleted: result.temp_refs?.deleted || 0, locks_released: result.locks?.released || 0, submissions_deleted: result.old_submissions?.deleted || 0, duration_ms: result.execution.duration_ms, }); // Log any individual task failures if (!result.idempotency_keys?.success) { edgeLogger.warn('Idempotency keys cleanup failed', { error: result.idempotency_keys?.error, }); } if (!result.temp_refs?.success) { edgeLogger.warn('Temp refs cleanup failed', { error: result.temp_refs?.error, }); } if (!result.locks?.success) { edgeLogger.warn('Locks cleanup failed', { error: result.locks?.error, }); } if (!result.old_submissions?.success) { edgeLogger.warn('Old submissions cleanup failed', { error: result.old_submissions?.error, }); } return new Response( JSON.stringify({ success: true, results: result, total_duration_ms: Date.now() - startTime, }), { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, } ); } catch (error) { edgeLogger.error('Unexpected error in cleanup jobs', { error: error instanceof Error ? error.message : 'Unknown error', duration_ms: Date.now() - startTime, }); return new Response( JSON.stringify({ success: false, error: error instanceof Error ? error.message : 'Unknown error', duration_ms: Date.now() - startTime, }), { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, } ); } });