mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 11:31:11 -05:00
feat: Use forwardemail_proxy function
This commit is contained in:
250
supabase/functions/send-escalation-notification/index.ts
Normal file
250
supabase/functions/send-escalation-notification/index.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
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;
|
||||
additionalNotes?: string;
|
||||
}
|
||||
|
||||
serve(async (req) => {
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response(null, { headers: corsHeaders });
|
||||
}
|
||||
|
||||
try {
|
||||
const supabaseClient = createClient(
|
||||
Deno.env.get('SUPABASE_URL') ?? '',
|
||||
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '',
|
||||
{
|
||||
auth: {
|
||||
autoRefreshToken: false,
|
||||
persistSession: false
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const { submissionId, escalationReason, escalatedBy, additionalNotes }: EscalationRequest = await req.json();
|
||||
|
||||
console.log('Processing escalation notification:', { submissionId, escalationReason, escalatedBy });
|
||||
|
||||
// Get submission details
|
||||
const { data: submission, error: submissionError } = await supabaseClient
|
||||
.from('content_submissions')
|
||||
.select(`
|
||||
*,
|
||||
submitter:profiles!content_submissions_user_id_fkey(username, display_name)
|
||||
`)
|
||||
.eq('id', submissionId)
|
||||
.single();
|
||||
|
||||
if (submissionError || !submission) {
|
||||
throw new Error('Submission not found');
|
||||
}
|
||||
|
||||
// Get escalated by user details
|
||||
const { data: escalator, error: escalatorError } = await supabaseClient
|
||||
.from('profiles')
|
||||
.select('username, display_name')
|
||||
.eq('user_id', escalatedBy)
|
||||
.single();
|
||||
|
||||
// Get all admin/superuser emails
|
||||
const { data: adminUsers, error: adminError } = await supabaseClient
|
||||
.from('user_roles')
|
||||
.select('user_id, profiles!inner(user_id, display_name, username)')
|
||||
.in('role', ['admin', 'superuser']);
|
||||
|
||||
if (adminError || !adminUsers || adminUsers.length === 0) {
|
||||
throw new Error('No admin users found to notify');
|
||||
}
|
||||
|
||||
// Get email addresses for admins
|
||||
const adminUserIds = adminUsers.map(u => u.user_id);
|
||||
const { data: authUsers, error: authError } = await supabaseClient.auth.admin.listUsers();
|
||||
|
||||
if (authError) {
|
||||
throw new Error('Failed to fetch admin emails');
|
||||
}
|
||||
|
||||
const adminEmails = authUsers.users
|
||||
.filter(u => adminUserIds.includes(u.id))
|
||||
.map(u => u.email)
|
||||
.filter(Boolean);
|
||||
|
||||
if (adminEmails.length === 0) {
|
||||
throw new Error('No admin emails found');
|
||||
}
|
||||
|
||||
// Get submission items for context
|
||||
const { data: items } = await supabaseClient
|
||||
.from('submission_items')
|
||||
.select('item_type, item_data')
|
||||
.eq('submission_id', submissionId);
|
||||
|
||||
const itemsSummary = items?.map(item =>
|
||||
`${item.item_type}: ${item.item_data.name || 'Unnamed'}`
|
||||
).join(', ') || 'No items';
|
||||
|
||||
// Prepare email content
|
||||
const emailSubject = `🚨 Escalated Submission: ${submission.title || submissionId}`;
|
||||
const emailHtml = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; }
|
||||
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
||||
.header { background: #ef4444; color: white; padding: 20px; border-radius: 8px 8px 0 0; }
|
||||
.content { background: #f9fafb; padding: 30px; border: 1px solid #e5e7eb; border-top: none; }
|
||||
.section { margin-bottom: 20px; }
|
||||
.label { font-weight: 600; color: #6b7280; margin-bottom: 5px; }
|
||||
.value { background: white; padding: 10px; border-radius: 4px; border: 1px solid #e5e7eb; }
|
||||
.reason { background: #fef2f2; padding: 15px; border-left: 4px solid #ef4444; margin: 15px 0; }
|
||||
.button { display: inline-block; background: #3b82f6; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; margin-top: 20px; }
|
||||
.footer { text-align: center; color: #6b7280; font-size: 12px; margin-top: 30px; padding-top: 20px; border-top: 1px solid #e5e7eb; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1 style="margin: 0; font-size: 24px;">⚠️ Submission Escalated</h1>
|
||||
<p style="margin: 10px 0 0 0; opacity: 0.9;">Immediate attention required</p>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="section">
|
||||
<div class="label">Submission Title</div>
|
||||
<div class="value">${submission.title || 'Untitled Submission'}</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="label">Submitted By</div>
|
||||
<div class="value">${submission.submitter?.display_name || submission.submitter?.username || 'Unknown'}</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="label">Items in Submission</div>
|
||||
<div class="value">${itemsSummary}</div>
|
||||
</div>
|
||||
|
||||
<div class="reason">
|
||||
<div class="label">Escalation Reason</div>
|
||||
<div style="margin-top: 10px; font-size: 15px;">${escalationReason}</div>
|
||||
${additionalNotes ? `<div style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #fca5a5;"><strong>Additional Notes:</strong><br>${additionalNotes}</div>` : ''}
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="label">Escalated By</div>
|
||||
<div class="value">${escalator?.display_name || escalator?.username || 'System'}</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="label">Submission ID</div>
|
||||
<div class="value" style="font-family: monospace; font-size: 12px;">${submissionId}</div>
|
||||
</div>
|
||||
|
||||
<a href="${Deno.env.get('SUPABASE_URL')?.replace('supabase.co', 'lovableproject.com')}/admin?tab=moderation&submission=${submissionId}" class="button">
|
||||
Review Submission →
|
||||
</a>
|
||||
|
||||
<div class="footer">
|
||||
<p>This is an automated notification from the Moderation System</p>
|
||||
<p>To adjust your notification preferences, visit your admin settings</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
const emailText = `
|
||||
ESCALATED SUBMISSION - IMMEDIATE ATTENTION REQUIRED
|
||||
|
||||
Submission: ${submission.title || 'Untitled'}
|
||||
Submitted By: ${submission.submitter?.display_name || submission.submitter?.username || 'Unknown'}
|
||||
|
||||
Items: ${itemsSummary}
|
||||
|
||||
ESCALATION REASON:
|
||||
${escalationReason}
|
||||
|
||||
${additionalNotes ? `Additional Notes:\n${additionalNotes}\n` : ''}
|
||||
|
||||
Escalated By: ${escalator?.display_name || escalator?.username || 'System'}
|
||||
|
||||
Submission ID: ${submissionId}
|
||||
|
||||
Review at: ${Deno.env.get('SUPABASE_URL')?.replace('supabase.co', 'lovableproject.com')}/admin?tab=moderation&submission=${submissionId}
|
||||
`.trim();
|
||||
|
||||
// Send emails to all admins using forwardemail_proxy
|
||||
const emailResults = [];
|
||||
|
||||
for (const adminEmail of adminEmails) {
|
||||
try {
|
||||
const { data: emailData, error: emailError } = await supabaseClient.functions.invoke('forwardemail_proxy', {
|
||||
body: {
|
||||
to: adminEmail,
|
||||
subject: emailSubject,
|
||||
html: emailHtml,
|
||||
text: emailText,
|
||||
}
|
||||
});
|
||||
|
||||
if (emailError) {
|
||||
console.error(`Failed to send email to ${adminEmail}:`, emailError);
|
||||
emailResults.push({ email: adminEmail, status: 'failed', error: emailError.message });
|
||||
} else {
|
||||
console.log(`Email sent successfully to ${adminEmail}`);
|
||||
emailResults.push({ email: adminEmail, status: 'sent' });
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(`Exception sending email to ${adminEmail}:`, error);
|
||||
emailResults.push({ email: adminEmail, status: 'failed', error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
// Log escalation
|
||||
await supabaseClient
|
||||
.from('content_submissions')
|
||||
.update({
|
||||
escalated: true,
|
||||
escalation_reason: escalationReason,
|
||||
escalated_at: new Date().toISOString(),
|
||||
escalated_by: escalatedBy,
|
||||
})
|
||||
.eq('id', submissionId);
|
||||
|
||||
const successCount = emailResults.filter(r => r.status === 'sent').length;
|
||||
const failureCount = emailResults.filter(r => r.status === 'failed').length;
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
emailsSent: successCount,
|
||||
totalRecipients: adminEmails.length,
|
||||
failures: failureCount,
|
||||
results: emailResults,
|
||||
}),
|
||||
{
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
status: 200,
|
||||
}
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error('Error in send-escalation-notification:', error);
|
||||
return new Response(
|
||||
JSON.stringify({ error: error.message }),
|
||||
{
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
status: 500,
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user