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 PUBLIC_BASE_URL = 'https://avria.cloud' Deno.serve(async (req) => { if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders }) try { const body = await req.json().catch(() => ({})) const ownerIds: string[] = Array.isArray(body.owner_ids) ? body.owner_ids.filter((x: any) => typeof x === 'string') : [] const customMessage: string = typeof body.custom_message === 'string' ? body.custom_message.trim() : '' if (ownerIds.length === 0) { return new Response(JSON.stringify({ error: 'owner_ids required' }), { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }) } const supabaseUrl = Deno.env.get('SUPABASE_URL')! const serviceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! const anonKey = Deno.env.get('SUPABASE_ANON_KEY')! const authHeader = req.headers.get('Authorization') || '' const authClient = createClient(supabaseUrl, anonKey, { global: { headers: { Authorization: authHeader } }, }) const { data: userRes } = await authClient.auth.getUser() if (!userRes?.user) { return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }) } const userId = userRes.user.id const admin = createClient(supabaseUrl, serviceKey) const { data: roleRows } = await admin.from('user_roles').select('role').eq('user_id', userId) const roles = (roleRows || []).map((r: any) => r.role) if (!roles.includes('admin') && !roles.includes('manager')) { return new Response(JSON.stringify({ error: 'Forbidden' }), { status: 403, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }) } const { data: profile } = await admin.from('profiles').select('full_name').eq('user_id', userId).maybeSingle() const requesterName = profile?.full_name || 'Avria Community Management' const { data: owners, error: oErr } = await admin .from('owners') .select('id, first_name, last_name, email, management_contact_email, management_contact_name, business_name, owner_type, unit_id, association_id, property_address, user_id, associations(name), units(address)') .in('id', ownerIds) if (oErr) { return new Response(JSON.stringify({ error: 'Failed to load owners', detail: oErr.message }), { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }) } const results: Array<{ owner_id: string; ok: boolean; link?: string; error?: string; sent_to?: string }> = [] for (const owner of owners || []) { const ownerName = (owner as any).business_name || (owner as any).management_contact_name || [owner.first_name, owner.last_name].filter(Boolean).join(' ') || 'Owner' const propertyAddress = (owner as any).units?.address || owner.property_address || '' const associationName = (owner as any).associations?.name || '' let recipientEmail = owner.email || (owner as any).management_contact_email || '' // Fallback: if owner has a linked user_id, try profiles.email if (!recipientEmail && (owner as any).user_id) { const { data: profileRow } = await admin .from('profiles') .select('email') .eq('user_id', (owner as any).user_id) .maybeSingle() if (profileRow?.email) { recipientEmail = profileRow.email } } if (!recipientEmail) { results.push({ owner_id: owner.id, ok: false, error: 'No email on file' }) continue } const { data: reqRow, error: reqErr } = await admin .from('tenant_info_requests') .insert({ owner_id: owner.id, unit_id: owner.unit_id, association_id: owner.association_id, sent_to_email: recipientEmail, custom_message: customMessage || null, created_by: userId, }) .select('id, token, expires_at') .single() if (reqErr || !reqRow) { results.push({ owner_id: owner.id, ok: false, error: reqErr?.message || 'Failed to create request' }) continue } const link = `${PUBLIC_BASE_URL}/tenant-info/${reqRow.token}` const expiresAt = new Date(reqRow.expires_at).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric', }) let emailErr: any = null try { const emailRes = await fetch(`${supabaseUrl}/functions/v1/send-transactional-email`, { method: 'POST', headers: { 'apikey': anonKey, ...(authHeader ? { Authorization: authHeader } : {}), 'Content-Type': 'application/json', }, body: JSON.stringify({ templateName: 'tenant-info-request', recipientEmail, idempotencyKey: `tenant-info-${reqRow.id}`, templateData: { ownerName, propertyAddress, associationName, requesterName, customMessage: customMessage || undefined, link, expiresAt, }, }), }) if (!emailRes.ok) { const text = await emailRes.text() emailErr = { message: `status ${emailRes.status}: ${text}` } } } catch (e) { emailErr = e } if (emailErr) { console.error('Email send failed:', emailErr) results.push({ owner_id: owner.id, ok: false, link, error: 'Email failed to send. Share the link manually.' }) } else { results.push({ owner_id: owner.id, ok: true, link, sent_to: recipientEmail }) } } const sentCount = results.filter((r) => r.ok).length return new Response(JSON.stringify({ ok: true, sent: sentCount, total: results.length, results }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }) } catch (e) { console.error('send-tenant-info-request error:', e) return new Response(JSON.stringify({ error: String(e) }), { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }) } })