Add ACMCC app source, Supabase backend, and project config

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 20:19:26 -04:00
parent 313b51b412
commit 183fe0a93c
1422 changed files with 259271 additions and 0 deletions
@@ -0,0 +1,164 @@
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' },
})
}
})