Implement 5-day plan

This commit is contained in:
gpt-engineer-app[bot]
2025-10-21 12:37:28 +00:00
parent 638d49c8d9
commit 12433e49e3
8 changed files with 490 additions and 73 deletions

View File

@@ -1,4 +1,5 @@
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4';
import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts';
const corsHeaders = { const corsHeaders = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
@@ -6,16 +7,28 @@ const corsHeaders = {
}; };
Deno.serve(async (req) => { Deno.serve(async (req) => {
const tracking = startRequest();
// Handle CORS preflight requests // Handle CORS preflight requests
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders }); return new Response(null, {
headers: {
...corsHeaders,
'X-Request-ID': tracking.requestId
}
});
} }
try { try {
// Get the user from the authorization header // Get the user from the authorization header
const authHeader = req.headers.get('Authorization'); const authHeader = req.headers.get('Authorization');
if (!authHeader) { if (!authHeader) {
console.error('Missing authorization header'); const duration = endRequest(tracking);
edgeLogger.error('Missing authorization header', {
action: 'cancel_email_change',
requestId: tracking.requestId,
duration
});
throw new Error('No authorization header provided. Please ensure you are logged in.'); throw new Error('No authorization header provided. Please ensure you are logged in.');
} }
@@ -49,12 +62,22 @@ Deno.serve(async (req) => {
const { data: { user }, error: authError } = await supabaseAdmin.auth.getUser(token); const { data: { user }, error: authError } = await supabaseAdmin.auth.getUser(token);
if (authError || !user) { if (authError || !user) {
console.error('Auth verification failed:', authError); const duration = endRequest(tracking);
edgeLogger.error('Auth verification failed', {
action: 'cancel_email_change',
requestId: tracking.requestId,
duration,
error: authError
});
throw new Error('Invalid session token. Please refresh the page and try again.'); throw new Error('Invalid session token. Please refresh the page and try again.');
} }
const userId = user.id; const userId = user.id;
console.log(`Cancelling email change for user ${userId}`); edgeLogger.info('Cancelling email change for user', {
action: 'cancel_email_change',
requestId: tracking.requestId,
userId
});
// Call the database function to clear email change fields // Call the database function to clear email change fields
// This function has SECURITY DEFINER privileges to access auth.users // This function has SECURITY DEFINER privileges to access auth.users
@@ -85,6 +108,14 @@ Deno.serve(async (req) => {
// Don't fail the request if audit logging fails // Don't fail the request if audit logging fails
} }
const duration = endRequest(tracking);
edgeLogger.info('Successfully cancelled email change', {
action: 'cancel_email_change',
requestId: tracking.requestId,
userId,
duration
});
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: true, success: true,
@@ -94,21 +125,37 @@ Deno.serve(async (req) => {
email: null, email: null,
new_email: null, new_email: null,
}, },
requestId: tracking.requestId,
}), }),
{ {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
status: 200, status: 200,
} }
); );
} catch (error) { } catch (error) {
console.error('Error in cancel-email-change function:', error); const duration = endRequest(tracking);
edgeLogger.error('Error in cancel-email-change function', {
action: 'cancel_email_change',
requestId: tracking.requestId,
duration,
error: error instanceof Error ? error.message : String(error)
});
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: false, success: false,
error: error instanceof Error ? error.message : 'An unknown error occurred', error: error instanceof Error ? error.message : 'An unknown error occurred',
requestId: tracking.requestId,
}), }),
{ {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
status: 400, status: 400,
} }
); );

View File

@@ -1,5 +1,6 @@
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'; import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts';
const corsHeaders = { const corsHeaders = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
@@ -7,8 +8,15 @@ const corsHeaders = {
}; };
serve(async (req) => { serve(async (req) => {
const tracking = startRequest();
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders }); return new Response(null, {
headers: {
...corsHeaders,
'X-Request-ID': tracking.requestId
}
});
} }
try { try {
@@ -35,10 +43,20 @@ serve(async (req) => {
} = await supabaseClient.auth.getUser(); } = await supabaseClient.auth.getUser();
if (userError || !user) { if (userError || !user) {
const duration = endRequest(tracking);
edgeLogger.error('Authentication failed', {
action: 'confirm_deletion',
requestId: tracking.requestId,
duration
});
throw new Error('Unauthorized'); throw new Error('Unauthorized');
} }
console.log(`Confirming deletion for user: ${user.id}`); edgeLogger.info('Confirming deletion for user', {
action: 'confirm_deletion',
requestId: tracking.requestId,
userId: user.id
});
// Find deletion request // Find deletion request
const { data: deletionRequest, error: requestError } = await supabaseClient const { data: deletionRequest, error: requestError } = await supabaseClient
@@ -133,26 +151,50 @@ serve(async (req) => {
} }
} }
console.log('Account deactivated and deletion confirmed'); const duration = endRequest(tracking);
edgeLogger.info('Account deactivated and deletion confirmed', {
action: 'confirm_deletion',
requestId: tracking.requestId,
userId: user.id,
duration
});
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: true, success: true,
message: 'Deletion confirmed. Account deactivated and scheduled for permanent deletion.', message: 'Deletion confirmed. Account deactivated and scheduled for permanent deletion.',
scheduled_deletion_at: deletionRequest.scheduled_deletion_at, scheduled_deletion_at: deletionRequest.scheduled_deletion_at,
requestId: tracking.requestId,
}), }),
{ {
status: 200, status: 200,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
} }
); );
} catch (error) { } catch (error) {
console.error('Error confirming deletion:', error); const duration = endRequest(tracking);
edgeLogger.error('Error confirming deletion', {
action: 'confirm_deletion',
requestId: tracking.requestId,
duration,
error: error.message
});
return new Response( return new Response(
JSON.stringify({ error: error.message }), JSON.stringify({
error: error.message,
requestId: tracking.requestId
}),
{ {
status: 400, status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
} }
); );
} }

View File

@@ -1,7 +1,7 @@
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'; 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 { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4';
import { sanitizeError } from '../_shared/errorSanitizer.ts'; import { sanitizeError } from '../_shared/errorSanitizer.ts';
import { edgeLogger } from '../_shared/logger.ts'; import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts';
const corsHeaders = { const corsHeaders = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
@@ -17,9 +17,16 @@ interface ExportOptions {
} }
serve(async (req) => { serve(async (req) => {
const tracking = startRequest();
// Handle CORS preflight requests // Handle CORS preflight requests
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders }); return new Response(null, {
headers: {
...corsHeaders,
'X-Request-ID': tracking.requestId
}
});
} }
try { try {
@@ -40,14 +47,34 @@ serve(async (req) => {
} = await supabaseClient.auth.getUser(); } = await supabaseClient.auth.getUser();
if (authError || !user) { if (authError || !user) {
edgeLogger.error('Authentication failed', { action: 'export_auth' }); const duration = endRequest(tracking);
edgeLogger.error('Authentication failed', {
action: 'export_auth',
requestId: tracking.requestId,
duration
});
return new Response( return new Response(
JSON.stringify({ error: 'Unauthorized', success: false }), JSON.stringify({
{ status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } error: 'Unauthorized',
success: false,
requestId: tracking.requestId
}),
{
status: 401,
headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
}
}
); );
} }
edgeLogger.info('Processing export request', { action: 'export_start', userId: user.id }); edgeLogger.info('Processing export request', {
action: 'export_start',
requestId: tracking.requestId,
userId: user.id
});
// Check rate limiting - max 1 export per hour // Check rate limiting - max 1 export per hour
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString(); const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
@@ -64,16 +91,31 @@ serve(async (req) => {
} }
if (recentExports && recentExports.length > 0) { if (recentExports && recentExports.length > 0) {
const duration = endRequest(tracking);
const nextAvailableAt = new Date(new Date(recentExports[0].created_at).getTime() + 60 * 60 * 1000).toISOString(); const nextAvailableAt = new Date(new Date(recentExports[0].created_at).getTime() + 60 * 60 * 1000).toISOString();
console.log(`[Export] Rate limited for user ${user.id}, next available: ${nextAvailableAt}`); edgeLogger.warn('Rate limit exceeded for export', {
action: 'export_rate_limit',
requestId: tracking.requestId,
userId: user.id,
duration,
nextAvailableAt
});
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: false, success: false,
error: 'Rate limited. You can export your data once per hour.', error: 'Rate limited. You can export your data once per hour.',
rate_limited: true, rate_limited: true,
next_available_at: nextAvailableAt next_available_at: nextAvailableAt,
requestId: tracking.requestId
}), }),
{ status: 429, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } {
status: 429,
headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
}
}
); );
} }
@@ -87,7 +129,11 @@ serve(async (req) => {
format: 'json' format: 'json'
}; };
edgeLogger.info('Export options', { action: 'export_options', userId: user.id }); edgeLogger.info('Export options', {
action: 'export_options',
requestId: tracking.requestId,
userId: user.id
});
// Fetch profile data // Fetch profile data
const { data: profile, error: profileError } = await supabaseClient const { data: profile, error: profileError } = await supabaseClient
@@ -97,7 +143,11 @@ serve(async (req) => {
.single(); .single();
if (profileError) { if (profileError) {
edgeLogger.error('Profile fetch failed', { action: 'export_profile' }); edgeLogger.error('Profile fetch failed', {
action: 'export_profile',
requestId: tracking.requestId,
userId: user.id
});
throw new Error('Failed to fetch profile data'); throw new Error('Failed to fetch profile data');
} }
@@ -260,33 +310,61 @@ serve(async (req) => {
changes: { changes: {
export_options: options, export_options: options,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
data_size: JSON.stringify(exportData).length data_size: JSON.stringify(exportData).length,
requestId: tracking.requestId
} }
}]); }]);
edgeLogger.info('Export completed successfully', { action: 'export_complete', userId: user.id }); const duration = endRequest(tracking);
edgeLogger.info('Export completed successfully', {
action: 'export_complete',
requestId: tracking.requestId,
traceId: tracking.traceId,
userId: user.id,
duration,
dataSize: JSON.stringify(exportData).length
});
return new Response( return new Response(
JSON.stringify({ success: true, data: exportData }), JSON.stringify({
success: true,
data: exportData,
requestId: tracking.requestId
}),
{ {
status: 200, status: 200,
headers: { headers: {
...corsHeaders, ...corsHeaders,
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Content-Disposition': `attachment; filename="thrillwiki-data-export-${new Date().toISOString().split('T')[0]}.json"` 'Content-Disposition': `attachment; filename="thrillwiki-data-export-${new Date().toISOString().split('T')[0]}.json"`,
'X-Request-ID': tracking.requestId
} }
} }
); );
} catch (error) { } catch (error) {
edgeLogger.error('Export error', { action: 'export_error', error: error instanceof Error ? error.message : String(error) }); const duration = endRequest(tracking);
edgeLogger.error('Export error', {
action: 'export_error',
requestId: tracking.requestId,
duration,
error: error instanceof Error ? error.message : String(error)
});
const sanitized = sanitizeError(error, 'export-user-data'); const sanitized = sanitizeError(error, 'export-user-data');
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
...sanitized, ...sanitized,
success: false success: false,
requestId: tracking.requestId
}), }),
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } {
status: 500,
headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
}
}
); );
} }
}); });

View File

@@ -1,5 +1,5 @@
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4';
import { edgeLogger } from '../_shared/logger.ts'; import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts';
const corsHeaders = { const corsHeaders = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
@@ -7,9 +7,16 @@ const corsHeaders = {
}; };
Deno.serve(async (req) => { Deno.serve(async (req) => {
const tracking = startRequest();
// Handle CORS preflight requests // Handle CORS preflight requests
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders }); return new Response(null, {
headers: {
...corsHeaders,
'X-Request-ID': tracking.requestId
}
});
} }
try { try {
@@ -23,14 +30,33 @@ Deno.serve(async (req) => {
// Get authenticated user // Get authenticated user
const { data: { user }, error: userError } = await supabaseClient.auth.getUser(); const { data: { user }, error: userError } = await supabaseClient.auth.getUser();
if (userError || !user) { if (userError || !user) {
edgeLogger.error('Authentication failed', { action: 'mfa_unenroll_auth' }); const duration = endRequest(tracking);
edgeLogger.error('Authentication failed', {
action: 'mfa_unenroll_auth',
requestId: tracking.requestId,
duration
});
return new Response( return new Response(
JSON.stringify({ error: 'Unauthorized' }), JSON.stringify({
{ status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } error: 'Unauthorized',
requestId: tracking.requestId
}),
{
status: 401,
headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
}
}
); );
} }
edgeLogger.info('Processing MFA unenroll', { action: 'mfa_unenroll', userId: user.id }); edgeLogger.info('Processing MFA unenroll', {
action: 'mfa_unenroll',
requestId: tracking.requestId,
userId: user.id
});
// Phase 1: Check AAL level // Phase 1: Check AAL level
const { data: { session } } = await supabaseClient.auth.getSession(); const { data: { session } } = await supabaseClient.auth.getSession();
@@ -130,18 +156,50 @@ Deno.serve(async (req) => {
edgeLogger.error('Notification failed', { action: 'mfa_unenroll_notification', userId: user.id }); edgeLogger.error('Notification failed', { action: 'mfa_unenroll_notification', userId: user.id });
} }
edgeLogger.info('MFA successfully disabled', { action: 'mfa_unenroll_success', userId: user.id }); const duration = endRequest(tracking);
edgeLogger.info('MFA successfully disabled', {
action: 'mfa_unenroll_success',
requestId: tracking.requestId,
userId: user.id,
duration
});
return new Response( return new Response(
JSON.stringify({ success: true }), JSON.stringify({
{ status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } success: true,
requestId: tracking.requestId
}),
{
status: 200,
headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
}
}
); );
} catch (error) { } catch (error) {
edgeLogger.error('Unexpected error', { action: 'mfa_unenroll_error', error: error instanceof Error ? error.message : String(error) }); const duration = endRequest(tracking);
edgeLogger.error('Unexpected error', {
action: 'mfa_unenroll_error',
requestId: tracking.requestId,
duration,
error: error instanceof Error ? error.message : String(error)
});
return new Response( return new Response(
JSON.stringify({ error: 'Internal server error' }), JSON.stringify({
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } error: 'Internal server error',
requestId: tracking.requestId
}),
{
status: 500,
headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
}
}
); );
} }
}); });

View File

@@ -1,5 +1,6 @@
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; 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 { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts';
const corsHeaders = { const corsHeaders = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
@@ -19,8 +20,15 @@ interface NotificationPayload {
} }
serve(async (req) => { serve(async (req) => {
const tracking = startRequest();
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders }); return new Response(null, {
headers: {
...corsHeaders,
'X-Request-ID': tracking.requestId
}
});
} }
try { try {
@@ -42,7 +50,9 @@ serve(async (req) => {
is_escalated is_escalated
} = payload; } = payload;
console.log('Notifying moderators about submission via topic:', { edgeLogger.info('Notifying moderators about submission via topic', {
action: 'notify_moderators',
requestId: tracking.requestId,
submission_id, submission_id,
submission_type, submission_type,
content_preview content_preview
@@ -78,14 +88,25 @@ serve(async (req) => {
.single(); .single();
if (workflowError || !workflow) { if (workflowError || !workflow) {
console.error('Error fetching workflow:', workflowError); const duration = endRequest(tracking);
edgeLogger.error('Error fetching workflow', {
action: 'notify_moderators',
requestId: tracking.requestId,
duration,
error: workflowError
});
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: false, success: false,
error: 'Workflow not found or not active' error: 'Workflow not found or not active',
requestId: tracking.requestId
}), }),
{ {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
status: 500, status: 500,
} }
); );
@@ -112,6 +133,10 @@ serve(async (req) => {
hasPhotos: has_photos, hasPhotos: has_photos,
itemCount: item_count, itemCount: item_count,
isEscalated: is_escalated, isEscalated: is_escalated,
// Request tracking
requestId: tracking.requestId,
traceId: tracking.traceId,
}; };
// Send ONE notification to the moderation-submissions topic // Send ONE notification to the moderation-submissions topic
@@ -125,21 +150,39 @@ serve(async (req) => {
}); });
if (error) { if (error) {
console.error('Failed to notify moderators via topic:', error); const duration = endRequest(tracking);
edgeLogger.error('Failed to notify moderators via topic', {
action: 'notify_moderators',
requestId: tracking.requestId,
duration,
error: error.message
});
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: false, success: false,
error: 'Failed to send notification to topic', error: 'Failed to send notification to topic',
details: error.message details: error.message,
requestId: tracking.requestId
}), }),
{ {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
status: 500, status: 500,
} }
); );
} }
console.log('Successfully notified all moderators via topic:', data); const duration = endRequest(tracking);
edgeLogger.info('Successfully notified all moderators via topic', {
action: 'notify_moderators',
requestId: tracking.requestId,
traceId: tracking.traceId,
duration,
transactionId: data?.transactionId
});
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
@@ -147,22 +190,38 @@ serve(async (req) => {
message: 'Moderator notifications sent via topic', message: 'Moderator notifications sent via topic',
topicKey: 'moderation-submissions', topicKey: 'moderation-submissions',
transactionId: data?.transactionId, transactionId: data?.transactionId,
requestId: tracking.requestId,
}), }),
{ {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
status: 200, status: 200,
} }
); );
} catch (error: any) { } catch (error: any) {
console.error('Error in notify-moderators-submission:', error); const duration = endRequest(tracking);
edgeLogger.error('Error in notify-moderators-submission', {
action: 'notify_moderators',
requestId: tracking.requestId,
duration,
error: error.message
});
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: false, success: false,
error: error.message, error: error.message,
requestId: tracking.requestId,
}), }),
{ {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
status: 500, status: 500,
} }
); );

View File

@@ -1,5 +1,6 @@
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'; import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts';
const corsHeaders = { const corsHeaders = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
@@ -7,8 +8,15 @@ const corsHeaders = {
}; };
serve(async (req) => { serve(async (req) => {
const tracking = startRequest();
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders }); return new Response(null, {
headers: {
...corsHeaders,
'X-Request-ID': tracking.requestId
}
});
} }
try { try {
@@ -29,10 +37,20 @@ serve(async (req) => {
} = await supabaseClient.auth.getUser(); } = await supabaseClient.auth.getUser();
if (userError || !user) { if (userError || !user) {
const duration = endRequest(tracking);
edgeLogger.error('Authentication failed', {
action: 'request_deletion',
requestId: tracking.requestId,
duration
});
throw new Error('Unauthorized'); throw new Error('Unauthorized');
} }
console.log(`Processing deletion request for user: ${user.id}`); edgeLogger.info('Processing deletion request', {
action: 'request_deletion',
requestId: tracking.requestId,
userId: user.id
});
// Check for existing pending deletion request // Check for existing pending deletion request
const { data: existingRequest } = await supabaseClient const { data: existingRequest } = await supabaseClient
@@ -142,25 +160,52 @@ serve(async (req) => {
} }
} }
const duration = endRequest(tracking);
edgeLogger.info('Deletion request created successfully', {
action: 'request_deletion',
requestId: tracking.requestId,
userId: user.id,
duration,
requestId: deletionRequest.id
});
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: true, success: true,
message: 'Account deletion request created successfully', message: 'Account deletion request created successfully',
scheduled_deletion_at: scheduledDeletionAt.toISOString(), scheduled_deletion_at: scheduledDeletionAt.toISOString(),
request_id: deletionRequest.id, request_id: deletionRequest.id,
requestId: tracking.requestId,
}), }),
{ {
status: 200, status: 200,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
} }
); );
} catch (error) { } catch (error) {
console.error('Error processing deletion request:', error); const duration = endRequest(tracking);
edgeLogger.error('Error processing deletion request', {
action: 'request_deletion',
requestId: tracking.requestId,
duration,
error: error.message
});
return new Response( return new Response(
JSON.stringify({ error: error.message }), JSON.stringify({
error: error.message,
requestId: tracking.requestId
}),
{ {
status: 400, status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
} }
); );
} }

View File

@@ -1,5 +1,6 @@
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'; import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts';
const corsHeaders = { const corsHeaders = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
@@ -7,8 +8,15 @@ const corsHeaders = {
}; };
serve(async (req) => { serve(async (req) => {
const tracking = startRequest();
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders }); return new Response(null, {
headers: {
...corsHeaders,
'X-Request-ID': tracking.requestId
}
});
} }
try { try {
@@ -29,10 +37,20 @@ serve(async (req) => {
} = await supabaseClient.auth.getUser(); } = await supabaseClient.auth.getUser();
if (userError || !user) { if (userError || !user) {
const duration = endRequest(tracking);
edgeLogger.error('Authentication failed', {
action: 'resend_deletion_code',
requestId: tracking.requestId,
duration
});
throw new Error('Unauthorized'); throw new Error('Unauthorized');
} }
console.log(`Resending deletion code for user: ${user.id}`); edgeLogger.info('Resending deletion code for user', {
action: 'resend_deletion_code',
requestId: tracking.requestId,
userId: user.id
});
// Find pending deletion request // Find pending deletion request
const { data: deletionRequest, error: requestError } = await supabaseClient const { data: deletionRequest, error: requestError } = await supabaseClient
@@ -114,23 +132,49 @@ serve(async (req) => {
} }
} }
const duration = endRequest(tracking);
edgeLogger.info('New confirmation code sent successfully', {
action: 'resend_deletion_code',
requestId: tracking.requestId,
userId: user.id,
duration
});
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: true, success: true,
message: 'New confirmation code sent successfully', message: 'New confirmation code sent successfully',
requestId: tracking.requestId,
}), }),
{ {
status: 200, status: 200,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
} }
); );
} catch (error) { } catch (error) {
console.error('Error resending code:', error); const duration = endRequest(tracking);
edgeLogger.error('Error resending code', {
action: 'resend_deletion_code',
requestId: tracking.requestId,
duration,
error: error.message
});
return new Response( return new Response(
JSON.stringify({ error: error.message }), JSON.stringify({
error: error.message,
requestId: tracking.requestId
}),
{ {
status: 400, status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
} }
); );
} }

View File

@@ -1,5 +1,6 @@
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'; import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts';
const corsHeaders = { const corsHeaders = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
@@ -13,8 +14,15 @@ interface EmailRequest {
} }
serve(async (req) => { serve(async (req) => {
const tracking = startRequest();
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders }); return new Response(null, {
headers: {
...corsHeaders,
'X-Request-ID': tracking.requestId
}
});
} }
try { try {
@@ -31,6 +39,12 @@ serve(async (req) => {
const { data: { user }, error: userError } = await supabaseClient.auth.getUser(); const { data: { user }, error: userError } = await supabaseClient.auth.getUser();
if (userError || !user) { if (userError || !user) {
const duration = endRequest(tracking);
edgeLogger.error('Authentication failed', {
action: 'send_password_email',
requestId: tracking.requestId,
duration
});
throw new Error('Unauthorized'); throw new Error('Unauthorized');
} }
@@ -40,6 +54,13 @@ serve(async (req) => {
throw new Error('Email is required'); throw new Error('Email is required');
} }
edgeLogger.info('Sending password added email', {
action: 'send_password_email',
requestId: tracking.requestId,
userId: user.id,
email
});
const recipientName = displayName || username || 'there'; const recipientName = displayName || username || 'there';
const siteUrl = Deno.env.get('SITE_URL') || 'https://thrillwiki.com'; const siteUrl = Deno.env.get('SITE_URL') || 'https://thrillwiki.com';
@@ -141,30 +162,53 @@ serve(async (req) => {
throw new Error(`Failed to send email: ${emailResponse.statusText}`); throw new Error(`Failed to send email: ${emailResponse.statusText}`);
} }
console.log(`Password addition email sent successfully to: ${email}`); const duration = endRequest(tracking);
edgeLogger.info('Password addition email sent successfully', {
action: 'send_password_email',
requestId: tracking.requestId,
userId: user.id,
email,
duration
});
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: true, success: true,
message: 'Password addition email sent successfully', message: 'Password addition email sent successfully',
requestId: tracking.requestId,
}), }),
{ {
status: 200, status: 200,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
} }
); );
} catch (error) { } catch (error) {
console.error('Error in send-password-added-email function:', error); const duration = endRequest(tracking);
edgeLogger.error('Error in send-password-added-email function', {
action: 'send_password_email',
requestId: tracking.requestId,
duration,
error: error instanceof Error ? error.message : 'Unknown error'
});
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: false, success: false,
error: error instanceof Error ? error.message : 'Unknown error', error: error instanceof Error ? error.message : 'Unknown error',
requestId: tracking.requestId,
}), }),
{ {
status: 500, status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
} }
); );
} }