import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; import { corsHeaders } from '../_shared/cors.ts'; import { edgeLogger } from '../_shared/logger.ts'; import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts'; export default createEdgeFunction( { name: 'cancel-account-deletion', requireAuth: true, corsHeaders: corsHeaders }, async (req, context) => { const { cancellation_reason } = await req.json(); const supabaseClient = createClient( Deno.env.get('SUPABASE_URL') ?? '', Deno.env.get('SUPABASE_ANON_KEY') ?? '', { global: { headers: { Authorization: req.headers.get('Authorization')! }, }, } ); context.span.setAttribute('action', 'cancel_deletion'); edgeLogger.info('Cancelling deletion request', { action: 'cancel_deletion', userId: context.userId, requestId: context.requestId }); // Find pending or confirmed deletion request const { data: deletionRequest, error: requestError } = await supabaseClient .from('account_deletion_requests') .select('*') .eq('user_id', context.userId) .in('status', ['pending', 'confirmed']) .maybeSingle(); if (requestError || !deletionRequest) { throw new Error('No active deletion request found'); } // Validate that deletion hasn't already been processed if (deletionRequest.status === 'completed') { throw new Error('This deletion request has already been completed'); } // Validate scheduled deletion hasn't passed const scheduledDate = new Date(deletionRequest.scheduled_deletion_at); if (scheduledDate < new Date()) { throw new Error('Cannot cancel - deletion has already been processed'); } // Cancel deletion request const { error: updateError } = await supabaseClient .from('account_deletion_requests') .update({ status: 'cancelled', cancelled_at: new Date().toISOString(), cancellation_reason: cancellation_reason || 'User cancelled', }) .eq('id', deletionRequest.id); if (updateError) { throw updateError; } // Reactivate profile const { error: profileError } = await supabaseClient .from('profiles') .update({ deactivated: false, deactivated_at: null, deactivation_reason: null, }) .eq('user_id', context.userId); if (profileError) { throw profileError; } // Log to system activity log await supabaseClient.rpc('log_system_activity', { _user_id: context.userId, _action: 'account_deletion_cancelled', _details: { request_id: deletionRequest.id, cancellation_reason: cancellation_reason || 'User cancelled', account_reactivated: true, } }); // Send cancellation email const forwardEmailKey = Deno.env.get('FORWARDEMAIL_API_KEY'); const fromEmail = Deno.env.get('FROM_EMAIL_ADDRESS') || 'noreply@thrillwiki.com'; const userEmail = (await supabaseClient.auth.getUser()).data.user?.email; if (forwardEmailKey && userEmail) { try { await fetch('https://api.forwardemail.net/v1/emails', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Basic ${btoa(forwardEmailKey + ':')}`, }, body: JSON.stringify({ from: fromEmail, to: userEmail, subject: 'Account Deletion Cancelled', html: `
Your account deletion request has been cancelled on ${new Date().toLocaleDateString()}.
Your account has been reactivated and you can continue using all features.
If you did not cancel this request, please contact support immediately.
Welcome back!
`, }), }); edgeLogger.info('Cancellation confirmation email sent', { action: 'cancel_deletion_email', userId: context.userId, requestId: context.requestId }); } catch (emailError) { edgeLogger.error('Failed to send email', { action: 'cancel_deletion_email', userId: context.userId, requestId: context.requestId }); } } edgeLogger.info('Deletion cancelled successfully', { action: 'cancel_deletion_success', userId: context.userId, requestId: context.requestId }); return new Response( JSON.stringify({ success: true, message: 'Account deletion cancelled successfully', }), { status: 200, headers: { 'Content-Type': 'application/json' }, } ); } );