/* |-------------------------------------------------------------------------- | Agência PSI — Edge: save-intake-progress (8.4) |-------------------------------------------------------------------------- | Autosave de campos parciais do form de cadastro externo. Chamada pelo | CadastroPacienteExterno ao preencher campos chave (nome, telefone, etc) | pra que a gente consiga detectar abandonos e converter em lead no CRM. | | Body: | { | token: "", // obrigatório | nome_completo?: string, | telefone?: string, | email_principal?: string, | onde_nos_conheceu?: string, | ... qualquer campo do form | } | | Lógica: | - Busca intake por token (valida que ainda não foi convertido/rejeitado) | - Se status='new', vira 'in_progress' | - Atualiza campos enviados | - Atualiza last_progress_at |-------------------------------------------------------------------------- */ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2' const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', 'Access-Control-Allow-Methods': 'POST, OPTIONS', } function json(body: unknown, status = 200) { return new Response(JSON.stringify(body), { status, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }) } // Whitelist de campos que o form pode autosavar. Protege contra POST malicioso // tentando setar status/converted_patient_id/owner_id etc. const ALLOWED_FIELDS = new Set([ 'nome_completo', 'telefone', 'email_principal', 'cpf', 'rg', 'cep', 'pais', 'cidade', 'estado', 'endereco', 'numero', 'bairro', 'complemento', 'data_nascimento', 'naturalidade', 'genero', 'estado_civil', 'onde_nos_conheceu' ]) function sanitizeString(s: unknown, maxLen = 500): string | null { if (s === null || s === undefined) return null const v = String(s).trim() if (!v) return null return v.slice(0, maxLen) } Deno.serve(async (req: Request) => { if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders }) if (req.method !== 'POST') return json({ ok: false, error: 'method_not_allowed' }, 405) try { const payload = await req.json().catch(() => null) as Record | null const token = sanitizeString(payload?.token, 128) if (!token) return json({ ok: false, error: 'token_required' }, 400) const supa = createClient( Deno.env.get('SUPABASE_URL')!, Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! ) // Busca intake pelo token const { data: intake } = await supa .from('patient_intake_requests') .select('id, status') .eq('token', token) .maybeSingle() if (!intake) return json({ ok: false, error: 'token_not_found' }, 404) if (intake.status === 'converted' || intake.status === 'rejected') { return json({ ok: false, error: 'intake_already_closed', status: intake.status }, 409) } // Monta patch com campos sanitizados const patch: Record = { last_progress_at: new Date().toISOString(), status: intake.status === 'new' ? 'in_progress' : intake.status } for (const key of Object.keys(payload || {})) { if (!ALLOWED_FIELDS.has(key)) continue const clean = sanitizeString((payload as Record)[key]) if (clean !== null) patch[key] = clean } const { error: updErr } = await supa .from('patient_intake_requests') .update(patch) .eq('id', intake.id) if (updErr) return json({ ok: false, error: updErr.message }, 500) return json({ ok: true, saved: Object.keys(patch).length - 2 }) } catch (err) { console.error('[save-intake-progress] fatal:', err) return json({ ok: false, error: (err as Error).message || 'unexpected' }, 500) } })