mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 15:11:12 -05:00
Refactor account deletion flow
This commit is contained in:
@@ -57,117 +57,43 @@ serve(async (req) => {
|
||||
throw new Error('Invalid confirmation code');
|
||||
}
|
||||
|
||||
// Check if 14 days have passed
|
||||
const scheduledDate = new Date(deletionRequest.scheduled_deletion_at);
|
||||
// Verify code was entered within 24 hours
|
||||
const codeSentAt = new Date(deletionRequest.confirmation_code_sent_at);
|
||||
const now = new Date();
|
||||
const hoursSinceCodeSent = (now.getTime() - codeSentAt.getTime()) / (1000 * 60 * 60);
|
||||
|
||||
if (now < scheduledDate) {
|
||||
const daysRemaining = Math.ceil((scheduledDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
|
||||
throw new Error(`You must wait ${daysRemaining} more day(s) before confirming deletion`);
|
||||
if (hoursSinceCodeSent > 24) {
|
||||
throw new Error('Confirmation code has expired. Please request a new deletion code.');
|
||||
}
|
||||
|
||||
// Use service role client for admin operations
|
||||
const supabaseAdmin = createClient(
|
||||
Deno.env.get('SUPABASE_URL') ?? '',
|
||||
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
|
||||
);
|
||||
console.log('Deactivating account and confirming deletion request...');
|
||||
|
||||
console.log('Starting deletion process...');
|
||||
|
||||
// Delete reviews (CASCADE will handle review_photos)
|
||||
const { error: reviewsError } = await supabaseAdmin
|
||||
.from('reviews')
|
||||
.delete()
|
||||
.eq('user_id', user.id);
|
||||
|
||||
if (reviewsError) {
|
||||
console.error('Error deleting reviews:', reviewsError);
|
||||
}
|
||||
|
||||
// Anonymize submissions and photos
|
||||
const { error: anonymizeError } = await supabaseAdmin
|
||||
.rpc('anonymize_user_submissions', { target_user_id: user.id });
|
||||
|
||||
if (anonymizeError) {
|
||||
console.error('Error anonymizing submissions:', anonymizeError);
|
||||
}
|
||||
|
||||
// Delete user roles
|
||||
const { error: rolesError } = await supabaseAdmin
|
||||
.from('user_roles')
|
||||
.delete()
|
||||
.eq('user_id', user.id);
|
||||
|
||||
if (rolesError) {
|
||||
console.error('Error deleting user roles:', rolesError);
|
||||
}
|
||||
|
||||
// Get profile to check for avatar before deletion
|
||||
const { data: profile } = await supabaseAdmin
|
||||
// Deactivate profile
|
||||
const { error: profileError } = await supabaseClient
|
||||
.from('profiles')
|
||||
.select('avatar_image_id')
|
||||
.eq('user_id', user.id)
|
||||
.maybeSingle();
|
||||
|
||||
// Delete avatar from Cloudflare Images if it exists
|
||||
if (profile?.avatar_image_id) {
|
||||
const cloudflareAccountId = Deno.env.get('VITE_CLOUDFLARE_ACCOUNT_ID');
|
||||
const cloudflareApiToken = Deno.env.get('CLOUDFLARE_API_TOKEN');
|
||||
|
||||
if (cloudflareAccountId && cloudflareApiToken) {
|
||||
try {
|
||||
console.log(`Deleting avatar image: ${profile.avatar_image_id}`);
|
||||
const deleteResponse = await fetch(
|
||||
`https://api.cloudflare.com/client/v4/accounts/${cloudflareAccountId}/images/v1/${profile.avatar_image_id}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${cloudflareApiToken}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!deleteResponse.ok) {
|
||||
console.error('Failed to delete avatar from Cloudflare:', await deleteResponse.text());
|
||||
} else {
|
||||
console.log('Avatar deleted from Cloudflare successfully');
|
||||
}
|
||||
} catch (avatarError) {
|
||||
console.error('Error deleting avatar from Cloudflare:', avatarError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete profile
|
||||
const { error: profileError } = await supabaseAdmin
|
||||
.from('profiles')
|
||||
.delete()
|
||||
.update({
|
||||
deactivated: true,
|
||||
deactivated_at: new Date().toISOString(),
|
||||
deactivation_reason: 'User confirmed account deletion request',
|
||||
})
|
||||
.eq('user_id', user.id);
|
||||
|
||||
if (profileError) {
|
||||
console.error('Error deleting profile:', profileError);
|
||||
console.error('Error deactivating profile:', profileError);
|
||||
throw profileError;
|
||||
}
|
||||
|
||||
// Update deletion request status
|
||||
const { error: updateError } = await supabaseAdmin
|
||||
// Update deletion request status to 'confirmed'
|
||||
const { error: updateError } = await supabaseClient
|
||||
.from('account_deletion_requests')
|
||||
.update({
|
||||
status: 'completed',
|
||||
completed_at: new Date().toISOString(),
|
||||
status: 'confirmed',
|
||||
})
|
||||
.eq('id', deletionRequest.id);
|
||||
|
||||
if (updateError) {
|
||||
console.error('Error updating deletion request:', updateError);
|
||||
}
|
||||
|
||||
// Delete auth user (this should cascade delete the deletion request)
|
||||
const { error: authError } = await supabaseAdmin.auth.admin.deleteUser(user.id);
|
||||
|
||||
if (authError) {
|
||||
console.error('Error deleting auth user:', authError);
|
||||
throw authError;
|
||||
throw updateError;
|
||||
}
|
||||
|
||||
// Send confirmation email
|
||||
@@ -185,12 +111,19 @@ serve(async (req) => {
|
||||
body: JSON.stringify({
|
||||
from: fromEmail,
|
||||
to: user.email,
|
||||
subject: 'Account Deletion Confirmed',
|
||||
subject: 'Account Deletion Confirmed - 14 Days to Cancel',
|
||||
html: `
|
||||
<h2>Account Deletion Confirmed</h2>
|
||||
<p>Your account has been permanently deleted on ${new Date().toLocaleDateString()}.</p>
|
||||
<p>Your profile and reviews have been removed, but your contributions to the database remain preserved.</p>
|
||||
<p>Thank you for being part of our community.</p>
|
||||
<p>Your deletion request has been confirmed. Your account is now <strong>deactivated</strong> and will be permanently deleted on <strong>${new Date(deletionRequest.scheduled_deletion_at).toLocaleDateString()}</strong>.</p>
|
||||
|
||||
<h3>What happens now?</h3>
|
||||
<ul>
|
||||
<li>✓ Your account is deactivated immediately</li>
|
||||
<li>✓ You have 14 days to cancel before permanent deletion</li>
|
||||
<li>✓ To cancel, log in and visit your account settings</li>
|
||||
</ul>
|
||||
|
||||
<p>If you take no action, your account will be automatically deleted after the 14-day waiting period.</p>
|
||||
`,
|
||||
}),
|
||||
});
|
||||
@@ -200,12 +133,13 @@ serve(async (req) => {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Account deletion completed successfully');
|
||||
console.log('Account deactivated and deletion confirmed');
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: 'Account deleted successfully',
|
||||
message: 'Deletion confirmed. Account deactivated and scheduled for permanent deletion.',
|
||||
scheduled_deletion_at: deletionRequest.scheduled_deletion_at,
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
|
||||
@@ -20,11 +20,11 @@ serve(async (req) => {
|
||||
|
||||
console.log('Processing scheduled account deletions...');
|
||||
|
||||
// Find pending deletion requests that are past their scheduled date
|
||||
// Find confirmed deletion requests that are past their scheduled date
|
||||
const { data: pendingDeletions, error: fetchError } = await supabaseAdmin
|
||||
.from('account_deletion_requests')
|
||||
.select('*')
|
||||
.eq('status', 'pending')
|
||||
.eq('status', 'confirmed')
|
||||
.lt('scheduled_deletion_at', new Date().toISOString());
|
||||
|
||||
if (fetchError) {
|
||||
|
||||
@@ -84,20 +84,6 @@ serve(async (req) => {
|
||||
throw requestError;
|
||||
}
|
||||
|
||||
// Deactivate profile
|
||||
const { error: profileError } = await supabaseClient
|
||||
.from('profiles')
|
||||
.update({
|
||||
deactivated: true,
|
||||
deactivated_at: new Date().toISOString(),
|
||||
deactivation_reason: 'User requested account deletion',
|
||||
})
|
||||
.eq('user_id', user.id);
|
||||
|
||||
if (profileError) {
|
||||
throw profileError;
|
||||
}
|
||||
|
||||
// Send confirmation email
|
||||
const emailPayload = {
|
||||
to: user.email,
|
||||
@@ -108,7 +94,7 @@ serve(async (req) => {
|
||||
<p>We received a request to delete your account on ${new Date().toLocaleDateString()}.</p>
|
||||
|
||||
<h3>IMPORTANT INFORMATION:</h3>
|
||||
<p>Your account has been deactivated and will be permanently deleted on <strong>${scheduledDeletionAt.toLocaleDateString()}</strong> (14 days from now).</p>
|
||||
<p>You must enter the confirmation code within 24 hours. Once confirmed, your account will be deactivated and permanently deleted on <strong>${scheduledDeletionAt.toLocaleDateString()}</strong> (14 days from confirmation).</p>
|
||||
|
||||
<h4>What will be DELETED:</h4>
|
||||
<ul>
|
||||
@@ -125,9 +111,9 @@ serve(async (req) => {
|
||||
</ul>
|
||||
|
||||
<h3>CONFIRMATION CODE: <strong>${confirmationCode}</strong></h3>
|
||||
<p>To confirm deletion after the 14-day period, you'll need to enter this 6-digit code.</p>
|
||||
<p><strong>IMPORTANT:</strong> You have 24 hours to enter this code to confirm the deletion. After entering the code, your account will be deactivated and you'll have 14 days to cancel before permanent deletion.</p>
|
||||
|
||||
<p><strong>Need to cancel?</strong> Log in and visit your account settings to reactivate your account at any time during the next 14 days.</p>
|
||||
<p><strong>Need to cancel?</strong> You can cancel at any time during the 14-day period after confirming.</p>
|
||||
`,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user