mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
183fe0a93c
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
165 lines
5.3 KiB
TypeScript
165 lines
5.3 KiB
TypeScript
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.45.0'
|
|
|
|
const corsHeaders = {
|
|
'Access-Control-Allow-Origin': '*',
|
|
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
|
}
|
|
|
|
const SOURCE_TYPE_LABELS: Record<string, string> = {
|
|
public_form: 'Public form submission',
|
|
client_request: 'Client request',
|
|
homeowner_ticket: 'Homeowner ticket',
|
|
violation_response: 'Violation response',
|
|
registration_request: 'Registration request',
|
|
}
|
|
|
|
const SOURCE_TYPE_LINKS: Record<string, string> = {
|
|
public_form: '/dashboard/form-inbox',
|
|
client_request: '/dashboard/client-requests',
|
|
homeowner_ticket: '/dashboard/form-inbox',
|
|
violation_response: '/dashboard/form-inbox',
|
|
registration_request: '/dashboard/form-inbox',
|
|
}
|
|
|
|
Deno.serve(async (req) => {
|
|
if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders })
|
|
|
|
try {
|
|
const { inbox_id } = await req.json()
|
|
if (!inbox_id) {
|
|
return new Response(JSON.stringify({ error: 'inbox_id required' }), {
|
|
status: 400,
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
})
|
|
}
|
|
|
|
const supabase = createClient(
|
|
Deno.env.get('SUPABASE_URL')!,
|
|
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!,
|
|
)
|
|
|
|
// Fetch the inbox entry
|
|
const { data: inbox, error: inboxError } = await supabase
|
|
.from('form_inbox')
|
|
.select('*')
|
|
.eq('id', inbox_id)
|
|
.single()
|
|
|
|
if (inboxError || !inbox) {
|
|
console.error('Inbox lookup failed:', inboxError)
|
|
return new Response(JSON.stringify({ error: 'inbox entry not found' }), {
|
|
status: 404,
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
})
|
|
}
|
|
|
|
const sourceLabel = SOURCE_TYPE_LABELS[inbox.source_type] || 'Form submission'
|
|
const link = SOURCE_TYPE_LINKS[inbox.source_type] || '/dashboard/form-inbox'
|
|
|
|
// Get admin + manager user IDs
|
|
const { data: roleRows, error: roleError } = await supabase
|
|
.from('user_roles')
|
|
.select('user_id')
|
|
.in('role', ['admin', 'manager'])
|
|
|
|
if (roleError) {
|
|
console.error('Role lookup failed:', roleError)
|
|
return new Response(JSON.stringify({ error: 'failed to fetch staff' }), {
|
|
status: 500,
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
})
|
|
}
|
|
|
|
const userIds = Array.from(new Set((roleRows || []).map((r) => r.user_id).filter(Boolean)))
|
|
if (userIds.length === 0) {
|
|
return new Response(JSON.stringify({ ok: true, notified: 0 }), {
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
})
|
|
}
|
|
|
|
// Insert in-app notifications for every staff user
|
|
const notificationRows = userIds.map((uid) => ({
|
|
user_id: uid,
|
|
type: 'form_submission_received',
|
|
title: `New ${sourceLabel.toLowerCase()}`,
|
|
message: `${inbox.submitter_name || 'Someone'} submitted: ${inbox.title}`,
|
|
related_item_id: inbox.id,
|
|
related_item_type: 'form_inbox',
|
|
link,
|
|
}))
|
|
const { error: notifError } = await supabase
|
|
.from('in_app_notifications')
|
|
.insert(notificationRows)
|
|
if (notifError) console.error('Notification insert error:', notifError)
|
|
|
|
// Look up staff emails from auth.users
|
|
const { data: authData, error: authError } = await supabase.auth.admin.listUsers({
|
|
page: 1,
|
|
perPage: 1000,
|
|
})
|
|
if (authError) {
|
|
console.error('Auth list failed:', authError)
|
|
}
|
|
|
|
const staffEmails = (authData?.users || [])
|
|
.filter((u) => userIds.includes(u.id) && u.email)
|
|
.map((u) => ({ id: u.id, email: u.email as string }))
|
|
|
|
const baseUrl = Deno.env.get('SUPABASE_URL') || ''
|
|
const projectId = baseUrl.replace('https://', '').split('.')[0]
|
|
const fullLink = `https://avria.cloud${link}`
|
|
|
|
let emailsSent = 0
|
|
let emailsFailed = 0
|
|
|
|
for (const staff of staffEmails) {
|
|
try {
|
|
const { error: emailError } = await supabase.functions.invoke(
|
|
'send-transactional-email',
|
|
{
|
|
body: {
|
|
templateName: 'ticket-submitted',
|
|
recipientEmail: staff.email,
|
|
idempotencyKey: `form-inbox-${inbox.id}-${staff.id}`,
|
|
templateData: {
|
|
recipientName: '',
|
|
homeownerName: inbox.submitter_name || 'Anonymous',
|
|
ticketTitle: inbox.title,
|
|
category: sourceLabel,
|
|
priority: '',
|
|
summary: inbox.summary || '',
|
|
link: fullLink,
|
|
},
|
|
},
|
|
},
|
|
)
|
|
if (emailError) {
|
|
emailsFailed++
|
|
console.error(`Email to ${staff.email} failed:`, emailError)
|
|
} else {
|
|
emailsSent++
|
|
}
|
|
} catch (e) {
|
|
emailsFailed++
|
|
console.error(`Email exception for ${staff.email}:`, e)
|
|
}
|
|
}
|
|
|
|
return new Response(
|
|
JSON.stringify({
|
|
ok: true,
|
|
notified: userIds.length,
|
|
emails_sent: emailsSent,
|
|
emails_failed: emailsFailed,
|
|
}),
|
|
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } },
|
|
)
|
|
} catch (e) {
|
|
console.error('notify-staff-new-form error:', e)
|
|
return new Response(JSON.stringify({ error: String(e) }), {
|
|
status: 500,
|
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
|
})
|
|
}
|
|
})
|