Files
thrilltrack-explorer/supabase/functions/send-escalation-notification/index.ts
pac7 0b57cba16f Improve security and error handling in backend functions
Update Supabase functions for cancel-email-change, detect-location, send-escalation-notification, and upload-image to enhance security and implement robust error handling.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: a46bc7a0-bbf8-43ab-97c0-a58c66c2e365
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
2025-10-08 12:06:35 +00:00

223 lines
7.9 KiB
TypeScript

import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};
interface EscalationRequest {
submissionId: string;
escalationReason: string;
escalatedBy: string;
}
serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders });
}
try {
const supabase = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
);
const { submissionId, escalationReason, escalatedBy }: EscalationRequest = await req.json();
console.log('Processing escalation notification:', { submissionId, escalationReason, escalatedBy });
// Fetch submission details
const { data: submission, error: submissionError } = await supabase
.from('content_submissions')
.select('*, profiles:user_id(username, display_name, id)')
.eq('id', submissionId)
.single();
if (submissionError || !submission) {
throw new Error(`Failed to fetch submission: ${submissionError?.message || 'Not found'}`);
}
// Fetch escalator details
const { data: escalator, error: escalatorError } = await supabase
.from('profiles')
.select('username, display_name')
.eq('user_id', escalatedBy)
.single();
if (escalatorError) {
console.error('Failed to fetch escalator profile:', escalatorError);
}
// Fetch submission items count
const { count: itemsCount, error: countError } = await supabase
.from('submission_items')
.select('*', { count: 'exact', head: true })
.eq('submission_id', submissionId);
if (countError) {
console.error('Failed to fetch items count:', countError);
}
// Prepare email content
const escalatorName = escalator?.display_name || escalator?.username || 'Unknown User';
const submitterName = submission.profiles?.display_name || submission.profiles?.username || 'Unknown User';
const submissionType = submission.submission_type || 'Unknown';
const itemCount = itemsCount || 0;
const emailSubject = `🚨 Submission Escalated: ${submissionType} - ID: ${submissionId.substring(0, 8)}`;
const emailHtml = `
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background-color: #ef4444; color: white; padding: 20px; border-radius: 8px 8px 0 0; }
.content { background-color: #f9fafb; padding: 20px; border: 1px solid #e5e7eb; }
.info-row { margin: 10px 0; padding: 10px; background: white; border-radius: 4px; }
.label { font-weight: bold; color: #374151; }
.value { color: #1f2937; }
.reason { background-color: #fef3c7; padding: 15px; border-left: 4px solid #f59e0b; margin: 15px 0; }
.footer { text-align: center; padding: 20px; color: #6b7280; font-size: 14px; }
.button { display: inline-block; padding: 12px 24px; background-color: #3b82f6; color: white; text-decoration: none; border-radius: 6px; margin: 15px 0; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1 style="margin: 0;">⚠️ Submission Escalated</h1>
<p style="margin: 5px 0 0 0;">Admin review required</p>
</div>
<div class="content">
<div class="info-row">
<span class="label">Submission ID:</span>
<span class="value">${submissionId}</span>
</div>
<div class="info-row">
<span class="label">Submission Type:</span>
<span class="value">${submissionType}</span>
</div>
<div class="info-row">
<span class="label">Items Count:</span>
<span class="value">${itemCount}</span>
</div>
<div class="info-row">
<span class="label">Submitted By:</span>
<span class="value">${submitterName}</span>
</div>
<div class="info-row">
<span class="label">Escalated By:</span>
<span class="value">${escalatorName}</span>
</div>
<div class="reason">
<strong>📝 Escalation Reason:</strong>
<p style="margin: 10px 0 0 0;">${escalationReason}</p>
</div>
<div style="text-align: center;">
<a href="${Deno.env.get('SUPABASE_URL')?.replace('.supabase.co', '.lovable.app')}/admin" class="button">
Review Submission →
</a>
</div>
</div>
<div class="footer">
<p>This is an automated notification from your moderation system.</p>
<p>Please review and take appropriate action.</p>
</div>
</div>
</body>
</html>
`;
const emailText = `
SUBMISSION ESCALATED - Admin Review Required
Submission ID: ${submissionId}
Submission Type: ${submissionType}
Items Count: ${itemCount}
Submitted By: ${submitterName}
Escalated By: ${escalatorName}
Escalation Reason:
${escalationReason}
Please review this submission in the admin panel.
`;
// Send email via ForwardEmail API
const forwardEmailApiKey = Deno.env.get('FORWARDEMAIL_API_KEY');
const adminEmail = Deno.env.get('ADMIN_EMAIL_ADDRESS');
const fromEmail = Deno.env.get('FROM_EMAIL_ADDRESS');
if (!forwardEmailApiKey || !adminEmail || !fromEmail) {
throw new Error('Email configuration is incomplete. Please check environment variables.');
}
const emailResponse = await fetch('https://api.forwardemail.net/v1/emails', {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa(forwardEmailApiKey + ':'),
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: fromEmail,
to: adminEmail,
subject: emailSubject,
html: emailHtml,
text: emailText,
}),
});
if (!emailResponse.ok) {
const errorText = await emailResponse.text();
console.error('ForwardEmail API error:', errorText);
throw new Error(`Failed to send email: ${emailResponse.status} - ${errorText}`);
}
const emailResult = await emailResponse.json();
console.log('Email sent successfully:', emailResult);
// Update submission with notification status
const { error: updateError } = await supabase
.from('content_submissions')
.update({
escalated: true,
escalated_at: new Date().toISOString(),
escalated_by: escalatedBy,
escalation_reason: escalationReason
})
.eq('id', submissionId);
if (updateError) {
console.error('Failed to update submission escalation status:', updateError);
}
return new Response(
JSON.stringify({
success: true,
message: 'Escalation notification sent successfully',
emailId: emailResult.id
}),
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
} catch (error) {
console.error('Error in send-escalation-notification:', error);
return new Response(
JSON.stringify({
error: error instanceof Error ? error.message : 'Unknown error occurred',
details: 'Failed to send escalation notification'
}),
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
});