Files
thrilltrack-explorer/supabase/functions/cancel-account-deletion/index.ts
gpt-engineer-app[bot] 2d65f13b85 Connect to Lovable Cloud
Add centralized errorFormatter to convert various error types into readable messages, and apply it across edge functions. Replace String(error) usage with formatEdgeError, update relevant imports, fix a throw to use toError, and enhance logger to log formatted errors. Includes new errorFormatter.ts and widespread updates to 18+ edge functions plus logger integration.
2025-11-10 18:09:15 +00:00

151 lines
5.1 KiB
TypeScript

import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts';
import { formatEdgeError } from '../_shared/errorFormatter.ts';
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};
serve(async (req) => {
const tracking = startRequest();
if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders });
}
try {
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')! },
},
}
);
// Get authenticated user
const {
data: { user },
error: userError,
} = await supabaseClient.auth.getUser();
if (userError || !user) {
throw new Error('Unauthorized');
}
edgeLogger.info('Cancelling deletion request', { action: 'cancel_deletion', userId: user.id, requestId: tracking.requestId });
// Find pending or confirmed deletion request
const { data: deletionRequest, error: requestError } = await supabaseClient
.from('account_deletion_requests')
.select('*')
.eq('user_id', user.id)
.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', user.id);
if (profileError) {
throw profileError;
}
// Send cancellation email
const forwardEmailKey = Deno.env.get('FORWARDEMAIL_API_KEY');
const fromEmail = Deno.env.get('FROM_EMAIL_ADDRESS') || 'noreply@thrillwiki.com';
if (forwardEmailKey) {
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: user.email,
subject: 'Account Deletion Cancelled',
html: `
<h2>Account Deletion Cancelled</h2>
<p>Your account deletion request has been cancelled on ${new Date().toLocaleDateString()}.</p>
<p>Your account has been reactivated and you can continue using all features.</p>
<p>If you did not cancel this request, please contact support immediately.</p>
<p>Welcome back!</p>
`,
}),
});
edgeLogger.info('Cancellation confirmation email sent', { action: 'cancel_deletion_email', userId: user.id, requestId: tracking.requestId });
} catch (emailError) {
edgeLogger.error('Failed to send email', { action: 'cancel_deletion_email', userId: user.id, requestId: tracking.requestId });
}
}
const duration = endRequest(tracking);
edgeLogger.info('Deletion cancelled successfully', { action: 'cancel_deletion_success', userId: user.id, requestId: tracking.requestId, duration });
return new Response(
JSON.stringify({
success: true,
message: 'Account deletion cancelled successfully',
requestId: tracking.requestId
}),
{
status: 200,
headers: { ...corsHeaders, 'Content-Type': 'application/json', 'X-Request-ID': tracking.requestId },
}
);
} catch (error) {
const duration = endRequest(tracking);
edgeLogger.error('Error cancelling deletion', { action: 'cancel_deletion_error', error: formatEdgeError(error), requestId: tracking.requestId, duration });
return new Response(
JSON.stringify({ error: error.message, requestId: tracking.requestId }),
{
status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json', 'X-Request-ID': tracking.requestId },
}
);
}
});