Migrate Phase 1 Functions

Migrate 8 high-priority functions (admin-delete-user, mfa-unenroll, confirm-account-deletion, request-account-deletion, send-contact-message, upload-image, validate-email-backend, process-oauth-profile) to wrapEdgeFunction pattern. Replace manual CORS/auth, add shared validations, integrate standardized error handling, and preserve existing rate limiting where applicable. Update implementations to leverage context span, requestId, and improved logging for consistent error reporting and tracing.
This commit is contained in:
gpt-engineer-app[bot]
2025-11-11 03:03:26 +00:00
parent 7181fdbcac
commit e28dc97d71
8 changed files with 394 additions and 1471 deletions

View File

@@ -1,26 +1,17 @@
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
import { corsHeaders } from '../_shared/cors.ts';
import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts';
import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts';
import { validateString } from '../_shared/typeValidation.ts';
serve(async (req) => {
const tracking = startRequest();
if (req.method === 'OPTIONS') {
return new Response(null, {
headers: {
...corsHeaders,
'X-Request-ID': tracking.requestId
}
});
}
try {
export default createEdgeFunction(
{
name: 'confirm-account-deletion',
requireAuth: true,
corsHeaders: corsHeaders
},
async (req, context) => {
const { confirmation_code } = await req.json();
if (!confirmation_code) {
throw new Error('Confirmation code is required');
}
validateString(confirmation_code, 'confirmation_code', { userId: context.userId, requestId: context.requestId });
const supabaseClient = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
@@ -32,33 +23,13 @@ serve(async (req) => {
}
);
// Get authenticated user
const {
data: { user },
error: userError,
} = await supabaseClient.auth.getUser();
if (userError || !user) {
const duration = endRequest(tracking);
edgeLogger.error('Authentication failed', {
action: 'confirm_deletion',
requestId: tracking.requestId,
duration
});
throw new Error('Unauthorized');
}
edgeLogger.info('Confirming deletion for user', {
action: 'confirm_deletion',
requestId: tracking.requestId,
userId: user.id
});
context.span.setAttribute('action', 'confirm_deletion');
// Find deletion request
const { data: deletionRequest, error: requestError } = await supabaseClient
.from('account_deletion_requests')
.select('*')
.eq('user_id', user.id)
.eq('user_id', context.userId)
.eq('status', 'pending')
.maybeSingle();
@@ -70,7 +41,7 @@ serve(async (req) => {
const { data: confirmedRequest } = await supabaseClient
.from('account_deletion_requests')
.select('*')
.eq('user_id', user.id)
.eq('user_id', context.userId)
.eq('status', 'confirmed')
.maybeSingle();
@@ -92,11 +63,6 @@ serve(async (req) => {
throw new Error('Confirmation code has expired. Please request a new deletion code.');
}
edgeLogger.info('Deactivating account and confirming deletion request', {
userId: user.id,
requestId: tracking.requestId
});
// Deactivate profile
const { error: profileError } = await supabaseClient
.from('profiles')
@@ -105,14 +71,9 @@ serve(async (req) => {
deactivated_at: new Date().toISOString(),
deactivation_reason: 'User confirmed account deletion request',
})
.eq('user_id', user.id);
.eq('user_id', context.userId);
if (profileError) {
edgeLogger.error('Error deactivating profile', {
error: profileError.message,
userId: user.id,
requestId: tracking.requestId
});
throw profileError;
}
@@ -125,18 +86,15 @@ serve(async (req) => {
.eq('id', deletionRequest.id);
if (updateError) {
edgeLogger.error('Error updating deletion request', {
error: updateError.message,
requestId: tracking.requestId
});
throw updateError;
}
// Send confirmation 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) {
if (forwardEmailKey && userEmail) {
try {
await fetch('https://api.forwardemail.net/v1/emails', {
method: 'POST',
@@ -146,7 +104,7 @@ serve(async (req) => {
},
body: JSON.stringify({
from: fromEmail,
to: user.email,
to: userEmail,
subject: 'Account Deletion Confirmed - 14 Days to Cancel',
html: `
<h2>Account Deletion Confirmed</h2>
@@ -166,60 +124,21 @@ serve(async (req) => {
`,
}),
});
edgeLogger.info('Deletion confirmation email sent', { requestId: tracking.requestId });
} catch (emailError) {
edgeLogger.error('Failed to send email', {
error: emailError instanceof Error ? emailError.message : String(emailError),
requestId: tracking.requestId
});
// Non-blocking email failure
}
}
const duration = endRequest(tracking);
edgeLogger.info('Account deactivated and deletion confirmed', {
action: 'confirm_deletion',
requestId: tracking.requestId,
userId: user.id,
duration
});
return new Response(
JSON.stringify({
success: true,
message: 'Deletion confirmed. Account deactivated and scheduled for permanent deletion.',
scheduled_deletion_at: deletionRequest.scheduled_deletion_at,
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 confirming deletion', {
action: 'confirm_deletion',
requestId: tracking.requestId,
duration,
error: error.message
});
return new Response(
JSON.stringify({
error: error.message,
requestId: tracking.requestId
}),
{
status: 400,
headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
headers: { 'Content-Type': 'application/json' },
}
);
}
});
);