From 16a1fa756df55a5a6ec1b90dd55bd20b4c0ccdea Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 03:36:40 +0000 Subject: [PATCH] Continue Phase 2 Batch 2 and Batch 3 Migrate 6 background jobs to use wrapEdgeFunction: cleanup-old-versions, process-scheduled-deletions, data-retention-cleanup, run-cleanup-jobs, scheduled-maintenance, process-expired-bans. Replace old server routines with edgeFunction wrapper, add centralized logging, tracing, and standardized error handling, and adjust for batch-wise deployment. --- .../functions/cleanup-old-versions/index.ts | 69 +++---- .../functions/data-retention-cleanup/index.ts | 48 ++--- .../functions/process-expired-bans/index.ts | 90 +++++---- .../process-scheduled-deletions/index.ts | 171 +++++++++--------- supabase/functions/run-cleanup-jobs/index.ts | 69 ++----- .../functions/scheduled-maintenance/index.ts | 71 +++----- 6 files changed, 210 insertions(+), 308 deletions(-) diff --git a/supabase/functions/cleanup-old-versions/index.ts b/supabase/functions/cleanup-old-versions/index.ts index 67a3ff00..9b417f69 100644 --- a/supabase/functions/cleanup-old-versions/index.ts +++ b/supabase/functions/cleanup-old-versions/index.ts @@ -1,5 +1,5 @@ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; -import { corsHeaders } from '../_shared/cors.ts'; +import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts'; import { edgeLogger } from '../_shared/logger.ts'; import { formatEdgeError } from '../_shared/errorFormatter.ts'; @@ -10,24 +10,12 @@ interface CleanupStats { errors: string[]; } -Deno.serve(async (req) => { - // Handle CORS preflight - if (req.method === 'OPTIONS') { - return new Response(null, { headers: corsHeaders }); - } - - const supabaseClient = createClient( - Deno.env.get('SUPABASE_URL') ?? '', - Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '', - { - auth: { - autoRefreshToken: false, - persistSession: false - } - } - ); - - try { +export default createEdgeFunction( + { + name: 'cleanup-old-versions', + requireAuth: false, + }, + async (req, context, supabase) => { const startTime = Date.now(); const stats: CleanupStats = { item_edit_history_deleted: 0, @@ -36,10 +24,12 @@ Deno.serve(async (req) => { errors: [], }; - edgeLogger.info('Starting version cleanup job'); + edgeLogger.info('Starting version cleanup job', { + requestId: context.requestId + }); // Get retention settings from admin_settings - const { data: retentionSetting, error: settingsError } = await supabaseClient + const { data: retentionSetting, error: settingsError } = await supabase .from('admin_settings') .select('setting_value') .eq('setting_key', 'version_retention_days') @@ -56,7 +46,7 @@ Deno.serve(async (req) => { edgeLogger.info('Cleanup configuration', { retentionDays, cutoff: cutoffDate.toISOString() }); // Step 1: Delete orphaned edit history (where submission_item no longer exists) - const { data: orphanedRecords, error: orphanError } = await supabaseClient + const { data: orphanedRecords, error: orphanError } = await supabase .rpc('get_orphaned_edit_history'); if (orphanError) { @@ -66,7 +56,7 @@ Deno.serve(async (req) => { const orphanedIds = orphanedRecords.map((r: { id: string }) => r.id); edgeLogger.info('Found orphaned edit history records', { count: orphanedIds.length }); - const { error: deleteOrphanError } = await supabaseClient + const { error: deleteOrphanError } = await supabase .from('item_edit_history') .delete() .in('id', orphanedIds); @@ -81,7 +71,7 @@ Deno.serve(async (req) => { } // Step 2: For each item, keep most recent 10 versions, delete older ones beyond retention - const { data: items, error: itemsError } = await supabaseClient + const { data: items, error: itemsError } = await supabase .from('submission_items') .select('id'); @@ -95,7 +85,7 @@ Deno.serve(async (req) => { for (const item of items) { try { // Get all versions for this item, ordered by date (newest first) - const { data: versions, error: versionsError } = await supabaseClient + const { data: versions, error: versionsError } = await supabase .from('item_edit_history') .select('id, edited_at') .eq('item_id', item.id) @@ -114,7 +104,7 @@ Deno.serve(async (req) => { .map(v => v.id); if (versionsToDelete.length > 0) { - const { error: deleteError } = await supabaseClient + const { error: deleteError } = await supabase .from('item_edit_history') .delete() .in('id', versionsToDelete); @@ -134,7 +124,7 @@ Deno.serve(async (req) => { // Step 3: Update last cleanup timestamp const cleanupTimestamp = new Date().toISOString(); - const { error: updateError } = await supabaseClient + const { error: updateError } = await supabase .from('admin_settings') .update({ setting_value: `"${cleanupTimestamp}"` }) .eq('setting_key', 'last_version_cleanup'); @@ -144,7 +134,7 @@ Deno.serve(async (req) => { } // Step 4: Log cleanup statistics to audit log - await supabaseClient + await supabase .from('admin_audit_log') .insert({ admin_user_id: null, @@ -153,7 +143,7 @@ Deno.serve(async (req) => { details: { stats: { ...stats, - errors: undefined, // Don't log errors array in details + errors: undefined, }, retention_days: retentionDays, executed_at: cleanupTimestamp, @@ -166,6 +156,7 @@ Deno.serve(async (req) => { edgeLogger.info('Cleanup completed successfully', { ...stats, errors: stats.errors.length > 0 ? stats.errors : undefined, + requestId: context.requestId, }); return new Response( @@ -174,23 +165,7 @@ Deno.serve(async (req) => { stats, message: `Cleaned up ${stats.item_edit_history_deleted + stats.orphaned_records_deleted} version records`, }), - { - headers: { ...corsHeaders, 'Content-Type': 'application/json' }, - status: 200, - } - ); - } catch (error) { - edgeLogger.error('Cleanup job failed', { error: formatEdgeError(error) }); - - return new Response( - JSON.stringify({ - success: false, - error: error instanceof Error ? error.message : 'Unknown error', - }), - { - headers: { ...corsHeaders, 'Content-Type': 'application/json' }, - status: 500, - } + { headers: { 'Content-Type': 'application/json' } } ); } -}); +); diff --git a/supabase/functions/data-retention-cleanup/index.ts b/supabase/functions/data-retention-cleanup/index.ts index d52319aa..01ce778f 100644 --- a/supabase/functions/data-retention-cleanup/index.ts +++ b/supabase/functions/data-retention-cleanup/index.ts @@ -1,31 +1,30 @@ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; +import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts'; +import { edgeLogger } from '../_shared/logger.ts'; -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', -}; - -Deno.serve(async (req) => { - if (req.method === 'OPTIONS') { - return new Response(null, { headers: corsHeaders }); - } - - try { - const supabaseUrl = Deno.env.get('SUPABASE_URL')!; - const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!; - const supabase = createClient(supabaseUrl, supabaseKey); - - console.log('Starting data retention cleanup...'); +export default createEdgeFunction( + { + name: 'data-retention-cleanup', + requireAuth: false, + }, + async (req, context, supabase) => { + edgeLogger.info('Starting data retention cleanup', { requestId: context.requestId }); // Call the master cleanup function const { data, error } = await supabase.rpc('run_data_retention_cleanup'); if (error) { - console.error('Error running data retention cleanup:', error); + edgeLogger.error('Error running data retention cleanup', { + error, + requestId: context.requestId + }); throw error; } - console.log('Data retention cleanup completed:', data); + edgeLogger.info('Data retention cleanup completed', { + results: data, + requestId: context.requestId + }); return new Response( JSON.stringify({ @@ -33,16 +32,7 @@ Deno.serve(async (req) => { cleanup_results: data.cleanup_results, timestamp: data.timestamp, }), - { headers: { ...corsHeaders, 'Content-Type': 'application/json' } } - ); - } catch (error) { - console.error('Error in data-retention-cleanup function:', error); - return new Response( - JSON.stringify({ error: error.message }), - { - status: 500, - headers: { ...corsHeaders, 'Content-Type': 'application/json' }, - } + { headers: { 'Content-Type': 'application/json' } } ); } -}); +); diff --git a/supabase/functions/process-expired-bans/index.ts b/supabase/functions/process-expired-bans/index.ts index 53b739d2..cfd2b027 100644 --- a/supabase/functions/process-expired-bans/index.ts +++ b/supabase/functions/process-expired-bans/index.ts @@ -1,30 +1,21 @@ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; -import { corsHeaders } from '../_shared/cors.ts'; +import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts'; import { edgeLogger } from '../_shared/logger.ts'; -Deno.serve(async (req) => { - // Handle CORS preflight - if (req.method === 'OPTIONS') { - return new Response(null, { headers: corsHeaders }); - } - - try { - // Create admin client - const supabaseAdmin = createClient( - Deno.env.get('SUPABASE_URL')!, - Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!, - { - auth: { - autoRefreshToken: false, - persistSession: false - } - } - ); +export default createEdgeFunction( + { + name: 'process-expired-bans', + requireAuth: false, + }, + async (req, context, supabase) => { + edgeLogger.info('Processing expired bans', { + requestId: context.requestId + }); const now = new Date().toISOString(); // Find expired bans - const { data: expiredBans, error: fetchError } = await supabaseAdmin + const { data: expiredBans, error: fetchError } = await supabase .from('profiles') .select('user_id, username, ban_reason, ban_expires_at') .eq('banned', true) @@ -32,18 +23,28 @@ Deno.serve(async (req) => { .lte('ban_expires_at', now); if (fetchError) { - edgeLogger.error('Error fetching expired bans', { action: 'process_expired_bans', error: fetchError }); + edgeLogger.error('Error fetching expired bans', { + error: fetchError, + requestId: context.requestId + }); throw fetchError; } - edgeLogger.info(`Found ${expiredBans?.length || 0} expired bans to process`, { action: 'process_expired_bans', count: expiredBans?.length || 0 }); + edgeLogger.info('Found expired bans to process', { + count: expiredBans?.length || 0, + requestId: context.requestId + }); // Unban users with expired bans const unbannedUsers: string[] = []; for (const profile of expiredBans || []) { - edgeLogger.info('Unbanning user', { action: 'process_expired_bans', username: profile.username, userId: profile.user_id }); + edgeLogger.info('Unbanning user', { + username: profile.username, + userId: profile.user_id, + requestId: context.requestId + }); - const { error: unbanError } = await supabaseAdmin + const { error: unbanError } = await supabase .from('profiles') .update({ banned: false, @@ -53,14 +54,18 @@ Deno.serve(async (req) => { .eq('user_id', profile.user_id); if (unbanError) { - edgeLogger.error('Failed to unban user', { action: 'process_expired_bans', username: profile.username, error: unbanError }); + edgeLogger.error('Failed to unban user', { + username: profile.username, + error: unbanError, + requestId: context.requestId + }); continue; } // Log the automatic unban - const { error: logError } = await supabaseAdmin + const { error: logError } = await supabase .rpc('log_admin_action', { - _admin_user_id: '00000000-0000-0000-0000-000000000000', // System user ID + _admin_user_id: '00000000-0000-0000-0000-000000000000', _target_user_id: profile.user_id, _action: 'auto_unban', _details: { @@ -71,13 +76,20 @@ Deno.serve(async (req) => { }); if (logError) { - edgeLogger.error('Failed to log auto-unban', { action: 'process_expired_bans', username: profile.username, error: logError }); + edgeLogger.error('Failed to log auto-unban', { + username: profile.username, + error: logError, + requestId: context.requestId + }); } unbannedUsers.push(profile.username); } - edgeLogger.info('Successfully unbanned users', { action: 'process_expired_bans', count: unbannedUsers.length }); + edgeLogger.info('Successfully unbanned users', { + count: unbannedUsers.length, + requestId: context.requestId + }); return new Response( JSON.stringify({ @@ -86,23 +98,7 @@ Deno.serve(async (req) => { unbanned_users: unbannedUsers, processed_at: now }), - { - headers: { ...corsHeaders, 'Content-Type': 'application/json' }, - status: 200 - } - ); - - } catch (error) { - edgeLogger.error('Error in process-expired-bans', { action: 'process_expired_bans', error }); - return new Response( - JSON.stringify({ - error: error instanceof Error ? error.message : 'Unknown error', - success: false - }), - { - headers: { ...corsHeaders, 'Content-Type': 'application/json' }, - status: 500 - } + { headers: { 'Content-Type': 'application/json' } } ); } -}); +); diff --git a/supabase/functions/process-scheduled-deletions/index.ts b/supabase/functions/process-scheduled-deletions/index.ts index f1b54010..b065d167 100644 --- a/supabase/functions/process-scheduled-deletions/index.ts +++ b/supabase/functions/process-scheduled-deletions/index.ts @@ -1,26 +1,19 @@ -import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; -import { corsHeadersWithTracing as corsHeaders } from '../_shared/cors.ts'; -import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts"; +import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts'; +import { edgeLogger } from '../_shared/logger.ts'; -serve(async (req) => { - if (req.method === 'OPTIONS') { - return new Response(null, { headers: corsHeaders }); - } - - const tracking = startRequest('process-scheduled-deletions'); - - try { - // Use service role for admin operations - const supabaseAdmin = createClient( - Deno.env.get('SUPABASE_URL') ?? '', - Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '' - ); - - edgeLogger.info('Processing scheduled account deletions', { action: 'scheduled_deletions', requestId: tracking.requestId }); +export default createEdgeFunction( + { + name: 'process-scheduled-deletions', + requireAuth: false, + }, + async (req, context, supabase) => { + edgeLogger.info('Processing scheduled account deletions', { + requestId: context.requestId + }); // Find confirmed deletion requests that are past their scheduled date - const { data: pendingDeletions, error: fetchError } = await supabaseAdmin + const { data: pendingDeletions, error: fetchError } = await supabase .from('account_deletion_requests') .select('*') .eq('status', 'confirmed') @@ -31,59 +24,57 @@ serve(async (req) => { } if (!pendingDeletions || pendingDeletions.length === 0) { - edgeLogger.info('No deletions to process', { action: 'scheduled_deletions', requestId: tracking.requestId }); - - endRequest(tracking, 200); + edgeLogger.info('No deletions to process', { + requestId: context.requestId + }); return new Response( JSON.stringify({ success: true, message: 'No deletions to process', processed: 0, - requestId: tracking.requestId }), - { - status: 200, - headers: { - ...corsHeaders, - 'Content-Type': 'application/json', - 'X-Request-ID': tracking.requestId - }, - } + { headers: { 'Content-Type': 'application/json' } } ); } - edgeLogger.info('Found deletions to process', { action: 'scheduled_deletions', count: pendingDeletions.length, requestId: tracking.requestId }); + edgeLogger.info('Found deletions to process', { + count: pendingDeletions.length, + requestId: context.requestId + }); let successCount = 0; let errorCount = 0; for (const deletion of pendingDeletions) { try { - edgeLogger.info('Processing deletion for user', { action: 'scheduled_deletions', userId: deletion.user_id }); + edgeLogger.info('Processing deletion for user', { + userId: deletion.user_id, + requestId: context.requestId + }); // Get user email for confirmation email - const { data: userData } = await supabaseAdmin.auth.admin.getUserById(deletion.user_id); + const { data: userData } = await supabase.auth.admin.getUserById(deletion.user_id); const userEmail = userData?.user?.email; // Delete reviews (CASCADE will handle review_photos) - await supabaseAdmin + await supabase .from('reviews') .delete() .eq('user_id', deletion.user_id); // Anonymize submissions and photos - await supabaseAdmin + await supabase .rpc('anonymize_user_submissions', { target_user_id: deletion.user_id }); // Delete user roles - await supabaseAdmin + await supabase .from('user_roles') .delete() .eq('user_id', deletion.user_id); // Get profile to check for avatar before deletion - const { data: profile } = await supabaseAdmin + const { data: profile } = await supabase .from('profiles') .select('avatar_image_id') .eq('user_id', deletion.user_id) @@ -96,7 +87,10 @@ serve(async (req) => { if (cloudflareAccountId && cloudflareApiToken) { try { - edgeLogger.info('Deleting avatar image', { action: 'scheduled_deletions', avatarId: profile.avatar_image_id }); + edgeLogger.info('Deleting avatar image', { + avatarId: profile.avatar_image_id, + requestId: context.requestId + }); const deleteResponse = await fetch( `https://api.cloudflare.com/client/v4/accounts/${cloudflareAccountId}/images/v1/${profile.avatar_image_id}`, { @@ -108,48 +102,66 @@ serve(async (req) => { ); if (!deleteResponse.ok) { - edgeLogger.error('Failed to delete avatar from Cloudflare', { action: 'scheduled_deletions', error: await deleteResponse.text() }); + edgeLogger.error('Failed to delete avatar from Cloudflare', { + error: await deleteResponse.text(), + requestId: context.requestId + }); } else { - edgeLogger.info('Avatar deleted from Cloudflare successfully', { action: 'scheduled_deletions' }); + edgeLogger.info('Avatar deleted from Cloudflare successfully', { + requestId: context.requestId + }); } } catch (avatarError) { - edgeLogger.error('Error deleting avatar from Cloudflare', { action: 'scheduled_deletions', error: avatarError }); + edgeLogger.error('Error deleting avatar from Cloudflare', { + error: avatarError, + requestId: context.requestId + }); } } } // Delete profile - await supabaseAdmin + await supabase .from('profiles') .delete() .eq('user_id', deletion.user_id); // Remove from Novu before deleting auth user try { - edgeLogger.info('Removing Novu subscriber', { action: 'scheduled_deletions', userId: deletion.user_id }); + edgeLogger.info('Removing Novu subscriber', { + userId: deletion.user_id, + requestId: context.requestId + }); - const { error: novuError } = await supabaseAdmin.functions.invoke( + const { error: novuError } = await supabase.functions.invoke( 'remove-novu-subscriber', { body: { subscriberId: deletion.user_id, - deleteSubscriber: true // Also delete the subscriber entirely + deleteSubscriber: true } } ); if (novuError) { - edgeLogger.error('Failed to remove Novu subscriber', { action: 'scheduled_deletions', error: novuError }); + edgeLogger.error('Failed to remove Novu subscriber', { + error: novuError, + requestId: context.requestId + }); } else { - edgeLogger.info('Novu subscriber removed successfully', { action: 'scheduled_deletions' }); + edgeLogger.info('Novu subscriber removed successfully', { + requestId: context.requestId + }); } } catch (novuError) { - // Non-blocking - log but continue with deletion - edgeLogger.error('Error removing Novu subscriber', { action: 'scheduled_deletions', error: novuError }); + edgeLogger.error('Error removing Novu subscriber', { + error: novuError, + requestId: context.requestId + }); } // Update deletion request status - await supabaseAdmin + await supabase .from('account_deletion_requests') .update({ status: 'completed', @@ -158,7 +170,7 @@ serve(async (req) => { .eq('id', deletion.id); // Delete auth user - await supabaseAdmin.auth.admin.deleteUser(deletion.user_id); + await supabase.auth.admin.deleteUser(deletion.user_id); // Send final confirmation email if we have the email if (userEmail) { @@ -186,22 +198,34 @@ serve(async (req) => { }), }); } catch (emailError) { - edgeLogger.error('Failed to send confirmation email', { action: 'scheduled_deletions', error: emailError }); + edgeLogger.error('Failed to send confirmation email', { + error: emailError, + requestId: context.requestId + }); } } } successCount++; - edgeLogger.info('Successfully deleted account for user', { action: 'scheduled_deletions', userId: deletion.user_id }); + edgeLogger.info('Successfully deleted account for user', { + userId: deletion.user_id, + requestId: context.requestId + }); } catch (error) { errorCount++; - edgeLogger.error('Failed to delete account for user', { action: 'scheduled_deletions', userId: deletion.user_id, error }); + edgeLogger.error('Failed to delete account for user', { + userId: deletion.user_id, + error, + requestId: context.requestId + }); } } - edgeLogger.info('Processed deletions', { action: 'scheduled_deletions', successCount, errorCount, requestId: tracking.requestId }); - - endRequest(tracking, 200); + edgeLogger.info('Processed deletions', { + successCount, + errorCount, + requestId: context.requestId + }); return new Response( JSON.stringify({ @@ -209,35 +233,8 @@ serve(async (req) => { message: `Processed ${successCount} deletion(s)`, processed: successCount, errors: errorCount, - requestId: tracking.requestId }), - { - status: 200, - headers: { - ...corsHeaders, - 'Content-Type': 'application/json', - 'X-Request-ID': tracking.requestId - }, - } - ); - } catch (error) { - edgeLogger.error('Error processing scheduled deletions', { action: 'scheduled_deletions', error: error.message, requestId: tracking.requestId }); - - endRequest(tracking, 500, error.message); - - return new Response( - JSON.stringify({ - error: error.message, - requestId: tracking.requestId - }), - { - status: 500, - headers: { - ...corsHeaders, - 'Content-Type': 'application/json', - 'X-Request-ID': tracking.requestId - }, - } + { headers: { 'Content-Type': 'application/json' } } ); } -}); +); diff --git a/supabase/functions/run-cleanup-jobs/index.ts b/supabase/functions/run-cleanup-jobs/index.ts index 92dd8169..2f344d19 100644 --- a/supabase/functions/run-cleanup-jobs/index.ts +++ b/supabase/functions/run-cleanup-jobs/index.ts @@ -11,7 +11,7 @@ */ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; -import { corsHeaders } from '../_shared/cors.ts'; +import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts'; import { edgeLogger } from '../_shared/logger.ts'; interface CleanupResult { @@ -50,28 +50,16 @@ interface CleanupResult { }; } -Deno.serve(async (req) => { - // Handle CORS preflight - if (req.method === 'OPTIONS') { - return new Response(null, { headers: corsHeaders }); - } - - const startTime = Date.now(); - - try { +export default createEdgeFunction( + { + name: 'run-cleanup-jobs', + requireAuth: false, + }, + async (req, context, supabase) => { + const startTime = Date.now(); 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, - }, + requestId: context.requestId, }); // Execute the master cleanup function @@ -82,19 +70,9 @@ Deno.serve(async (req) => { error: error.message, code: error.code, duration_ms: Date.now() - startTime, + requestId: context.requestId, }); - - return new Response( - JSON.stringify({ - success: false, - error: error.message, - duration_ms: Date.now() - startTime, - }), - { - status: 500, - headers: { ...corsHeaders, 'Content-Type': 'application/json' }, - } - ); + throw error; } const result = data as CleanupResult; @@ -106,6 +84,7 @@ Deno.serve(async (req) => { locks_released: result.locks?.released || 0, submissions_deleted: result.old_submissions?.deleted || 0, duration_ms: result.execution.duration_ms, + requestId: context.requestId, }); // Log any individual task failures @@ -136,27 +115,7 @@ Deno.serve(async (req) => { 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' }, - } + { headers: { 'Content-Type': 'application/json' } } ); } -}); +); diff --git a/supabase/functions/scheduled-maintenance/index.ts b/supabase/functions/scheduled-maintenance/index.ts index 49fb31f1..f38d280c 100644 --- a/supabase/functions/scheduled-maintenance/index.ts +++ b/supabase/functions/scheduled-maintenance/index.ts @@ -1,40 +1,46 @@ -import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; -import { corsHeaders } from '../_shared/cors.ts'; +import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts'; import { edgeLogger } from '../_shared/logger.ts'; import { formatEdgeError } from '../_shared/errorFormatter.ts'; -serve(async (req: Request) => { - if (req.method === 'OPTIONS') { - return new Response(null, { headers: corsHeaders }); - } - - const requestId = crypto.randomUUID(); - - try { - edgeLogger.info('Starting scheduled maintenance', { requestId }); - - const supabase = createClient( - Deno.env.get('SUPABASE_URL')!, - Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! - ); +export default createEdgeFunction( + { + name: 'scheduled-maintenance', + requireAuth: false, + }, + async (req, context, supabase) => { + edgeLogger.info('Starting scheduled maintenance', { + requestId: context.requestId + }); // Run system maintenance (orphaned image cleanup) const { data: maintenanceData, error: maintenanceError } = await supabase.rpc('run_system_maintenance'); if (maintenanceError) { - edgeLogger.error('Maintenance failed', { requestId, error: maintenanceError.message }); + edgeLogger.error('Maintenance failed', { + error: maintenanceError.message, + requestId: context.requestId + }); } else { - edgeLogger.info('Maintenance completed', { requestId, result: maintenanceData }); + edgeLogger.info('Maintenance completed', { + result: maintenanceData, + requestId: context.requestId + }); } // Run pipeline monitoring checks const { data: monitoringData, error: monitoringError } = await supabase.rpc('run_pipeline_monitoring'); if (monitoringError) { - edgeLogger.error('Pipeline monitoring failed', { requestId, error: monitoringError.message }); + edgeLogger.error('Pipeline monitoring failed', { + error: monitoringError.message, + requestId: context.requestId + }); } else { - edgeLogger.info('Pipeline monitoring completed', { requestId, result: monitoringData }); + edgeLogger.info('Pipeline monitoring completed', { + result: monitoringData, + requestId: context.requestId + }); } return new Response( @@ -42,29 +48,8 @@ serve(async (req: Request) => { success: true, maintenance: maintenanceData, monitoring: monitoringData, - requestId }), - { - status: 200, - headers: { ...corsHeaders, 'Content-Type': 'application/json' } - } - ); - } catch (error) { - edgeLogger.error('Maintenance exception', { - requestId, - error: formatEdgeError(error) - }); - - return new Response( - JSON.stringify({ - success: false, - error: 'Internal server error', - requestId - }), - { - status: 500, - headers: { ...corsHeaders, 'Content-Type': 'application/json' } - } + { headers: { 'Content-Type': 'application/json' } } ); } -}); +);