/** * GET /api/public/feedback/ — public-facing instance config (slug = access token). * GET /api/public/feedback/?session_id= — also resolves the caller's previous submission * when single_submission is on, so the participant * page can render the read-only summary on first * paint instead of falling through to the legacy * "submit again" branch on reload. */ import type { RequestHandler } from './$types'; import { json } from '$lib/server/response'; import { handleApiError, notFound } from '$lib/server/errors'; import { getInstanceBySlug, clampUserAgent } from '$lib/server/feedback'; import { fdb } from '$lib/server/fdb'; export const GET: RequestHandler = async ({ params, url, request, getClientAddress }) => { try { const inst = await getInstanceBySlug(params.slug); if (!inst) return notFound('Feedback instance not found'); let previousSubmission: | { submitted_at: string; display_name: string | null; answers: unknown } | null = null; if (inst.single_submission) { const cols = 'answers, display_name, created_at'; const sessionId = url.searchParams.get('session_id'); if (sessionId && sessionId.length > 0 && sessionId.length <= 100) { const r = await fdb() .from('feedback_submissions') .select(cols) .eq('instance_id', inst.id) .eq('client_session_id', sessionId) .order('created_at', { ascending: false }) .limit(1) .maybeSingle(); if (r.error) throw r.error; if (r.data) { previousSubmission = { submitted_at: r.data.created_at, display_name: r.data.display_name, answers: r.data.answers, }; } } // Back-stop: same IP + UA. Catches the "cleared LocalStorage but same browser" case. if (!previousSubmission) { const ip = getClientAddress(); const ua = clampUserAgent(request.headers.get('user-agent')); if (ua) { const r = await fdb() .from('feedback_submissions') .select(cols) .eq('instance_id', inst.id) .eq('client_ip', ip) .eq('user_agent', ua) .order('created_at', { ascending: false }) .limit(1) .maybeSingle(); if (r.error) throw r.error; if (r.data) { previousSubmission = { submitted_at: r.data.created_at, display_name: r.data.display_name, answers: r.data.answers, }; } } } } return json({ title: inst.title, description: inst.description, form_definition: inst.form_definition, chat_enabled: inst.chat_enabled, live_results_enabled: inst.live_results_enabled, single_submission: inst.single_submission, status: inst.status, closed_at: inst.closed_at, previous_submission: previousSubmission, }); } catch (e) { return handleApiError(e, 'feedback GET'); } };