import { createFileRoute } from "@tanstack/react-router"; import { COLLECTION_RE } from "@/lib/echo-runtime"; // Shared cloud storage for websites Echo builds (window.EchoDB). Public by design: anyone using a // built app can read and add its records, so apps must not store private data here. const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; const MAX_BYTES = 8 * 1024; const MAX_RECORDS = 5000; const cors = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type", }; const json = (body: unknown, status = 200) => new Response(JSON.stringify(body), { status, headers: { ...cors, "Content-Type": "application/json" } }); async function appExists(appId: string) { const { supabaseAdmin } = await import("@/integrations/supabase/client.server"); const { count } = await supabaseAdmin.from("voice_builds").select("id", { count: "exact", head: true }).eq("session_id", appId); return { db: supabaseAdmin, exists: (count ?? 0) > 0 }; } export const Route = createFileRoute("/api/public/apps/$appId/$collection")({ server: { handlers: { OPTIONS: async () => new Response(null, { status: 204, headers: cors }), GET: async ({ params }) => { if (!UUID_RE.test(params.appId) || !COLLECTION_RE.test(params.collection)) return json({ error: "Invalid app or collection" }, 400); const { db, exists } = await appExists(params.appId); if (!exists) return json({ error: "App not found" }, 404); const { data, error } = await db.from("app_records").select("id, data, created_at") .eq("app_id", params.appId).eq("collection", params.collection) .order("created_at", { ascending: false }).limit(200); if (error) return json({ error: "Could not load records" }, 500); return json({ records: data ?? [] }); }, POST: async ({ request, params }) => { if (!UUID_RE.test(params.appId) || !COLLECTION_RE.test(params.collection)) return json({ error: "Invalid app or collection" }, 400); const text = await request.text(); if (text.length > MAX_BYTES) return json({ error: "Record is too large" }, 413); let data: unknown; try { data = JSON.parse(text); } catch { return json({ error: "Body must be JSON" }, 400); } if (!data || typeof data !== "object" || Array.isArray(data)) return json({ error: "Body must be a JSON object" }, 400); const { db, exists } = await appExists(params.appId); if (!exists) return json({ error: "App not found" }, 404); const { count } = await db.from("app_records").select("id", { count: "exact", head: true }).eq("app_id", params.appId); if ((count ?? 0) >= MAX_RECORDS) return json({ error: "This app's storage is full" }, 429); const { data: record, error } = await db.from("app_records") .insert({ app_id: params.appId, collection: params.collection, data: data as never }) .select("id, data, created_at").single(); if (error) return json({ error: "Could not save record" }, 500); return json({ record }, 201); }, }, }, });