From a56550298925f133c49f9380dbd07f6145638fd6 Mon Sep 17 00:00:00 2001 From: Alex Curley Date: Fri, 29 May 2026 09:24:16 -0400 Subject: [PATCH] Insert row --- netlify/functions/save-submission.js | 97 ++++++++++++++++++++++++++++ src/App.jsx | 11 ++++ 2 files changed, 108 insertions(+) create mode 100644 netlify/functions/save-submission.js diff --git a/netlify/functions/save-submission.js b/netlify/functions/save-submission.js new file mode 100644 index 0000000..e9cc12c --- /dev/null +++ b/netlify/functions/save-submission.js @@ -0,0 +1,97 @@ +/** + * Netlify Function (Node runtime). + * + * Inserts a row into the Supabase `funder_data_submissions` table + * when the user submits the funder discovery form. Runs in parallel + * with the Anthropic agent stream and is intentionally independent of + * it — a Supabase failure must never block (or be observed by) the + * streaming brief. + * + * Environment variables (set in .env.local for local `netlify dev`, + * and in the Netlify UI for production): + * - SUPABASE_URL e.g. https://.supabase.co + * - SUPABASE_SERVICE_ROLE_KEY service role key (server-side only) + * + * The service role key bypasses RLS, so this function must only be + * called from trusted server-side code (which it is — this endpoint + * is the only caller). Do NOT expose this key to the browser. + */ + +export const handler = async (event) => { + if (event.httpMethod !== 'POST') { + return { + statusCode: 405, + body: JSON.stringify({ error: 'Method not allowed' }), + } + } + + const SUPABASE_URL = process.env.SUPABASE_URL + const SUPABASE_SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY + + if (!SUPABASE_URL || !SUPABASE_SERVICE_ROLE_KEY) { + return { + statusCode: 500, + body: JSON.stringify({ + error: 'Server is missing SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY', + }), + } + } + + let payload + try { + payload = JSON.parse(event.body || '{}') + } catch { + return { + statusCode: 400, + body: JSON.stringify({ error: 'Invalid JSON body' }), + } + } + + const row = { + name: payload.userName ?? null, + email: payload.userEmail ?? null, + station_name: payload.stationName ?? null, + station_location: payload.stationLocation ?? null, + station_website: payload.stationWebsite ?? null, + } + + try { + const res = await fetch( + `${SUPABASE_URL.replace(/\/$/, '')}/rest/v1/funder_data_submissions`, + { + method: 'POST', + headers: { + apikey: SUPABASE_SERVICE_ROLE_KEY, + Authorization: `Bearer ${SUPABASE_SERVICE_ROLE_KEY}`, + 'Content-Type': 'application/json', + Prefer: 'return=minimal', + }, + body: JSON.stringify(row), + }, + ) + + if (!res.ok) { + const text = await res.text() + console.error('Supabase insert failed', res.status, text) + return { + statusCode: 502, + body: JSON.stringify({ + error: `Supabase insert failed: ${res.status} ${text}`, + }), + } + } + + return { + statusCode: 204, + body: '', + } + } catch (err) { + console.error('Supabase request errored', err) + return { + statusCode: 500, + body: JSON.stringify({ + error: `Supabase request errored: ${err?.message || String(err)}`, + }), + } + } +} diff --git a/src/App.jsx b/src/App.jsx index 835e24a..40bf196 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -174,6 +174,17 @@ export default function App() { setIsThinking(true) setIsStreaming(false) + // Fire-and-forget: save the submission to Supabase in parallel + // with the Anthropic agent stream. Any failure is logged but must + // never block or surface in the streaming UX. + fetch('/.netlify/functions/save-submission', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(form), + }).catch((err) => { + console.error('Failed to save submission to Supabase', err) + }) + try { let resume = null // Loop across segments. Each iteration is one HTTP request;