import { Buffer } from "node:buffer"; import { randomUUID } from "node:crypto"; import { createError, readBody } from "h3"; import { z } from "zod"; import { requireAuth0User } from "../../utils/auth0"; import { getSupabaseServiceClient } from "../../utils/supabase"; import { useRuntimeConfig } from "#imports"; const requestSchema = z.object({ id: z.string().uuid().optional(), name: z.string().min(1).max(120), templateId: z.string().min(1).max(64), previewDataUrl: z.string().regex(/^data:image\/png;base64,/), productionDataUrl: z.string().regex(/^data:image\/png;base64,/).optional(), designJson: z.record(z.any()), notes: z.string().max(640).optional(), }); type RequestPayload = z.infer; type DesignRecord = { id: string; user_id: string; name: string; template_id: string; preview_path: string; preview_url: string | null; design_json: unknown; notes?: string | null; created_at?: string; updated_at?: string; }; const dataUrlToBuffer = (dataUrl: string): Buffer => { const [, base64Data] = dataUrl.split(","); if (!base64Data) { throw createError({ statusCode: 400, statusMessage: "Invalid image data URI." }); } return Buffer.from(base64Data, "base64"); }; export default defineEventHandler(async (event) => { const user = await requireAuth0User(event); const rawBody = await readBody(event); const parsed = requestSchema.safeParse(rawBody); if (!parsed.success) { throw createError({ statusCode: 400, statusMessage: "Invalid request body.", data: parsed.error.flatten(), }); } const body = parsed.data; const config = useRuntimeConfig(); const bucket = config.public.supabase?.storageBucket; if (!bucket) { throw createError({ statusCode: 500, statusMessage: "Supabase storage bucket is not configured.", }); } const supabase = getSupabaseServiceClient(); const designId = body.id ?? randomUUID(); const filePath = `previews/${user.sub}/${designId}.png`; const uploadResult = await supabase.storage .from(bucket) .upload(filePath, dataUrlToBuffer(body.previewDataUrl), { contentType: "image/png", upsert: true, }); if (uploadResult.error) { throw createError({ statusCode: 500, statusMessage: `Failed to upload preview: ${uploadResult.error.message}`, }); } const { data: publicUrlData } = supabase.storage.from(bucket).getPublicUrl(filePath); const previewUrl = publicUrlData.publicUrl ?? null; const record: DesignRecord = { id: designId, user_id: user.sub, name: body.name, template_id: body.templateId, preview_path: filePath, preview_url: previewUrl, design_json: body.designJson, notes: body.notes ?? null, updated_at: new Date().toISOString(), }; const upsertResult = await supabase .from("designs") .upsert(record, { onConflict: "id" }) .select() .single(); if (upsertResult.error) { throw createError({ statusCode: 500, statusMessage: `Failed to save design: ${upsertResult.error.message}`, }); } return upsertResult.data; });