diff --git a/src/app/submit/actions.ts b/src/app/submit/actions.ts index e3b8500..a39b678 100644 --- a/src/app/submit/actions.ts +++ b/src/app/submit/actions.ts @@ -110,6 +110,35 @@ export async function prepareSubmission(formData: FormData) { return { ok: true, slug } as const; } +export async function saveHackCovers(args: { slug: string; coverUrls: string[] }) { + const supabase = await createClient(); + const { + data: { user }, + } = await supabase.auth.getUser(); + if (!user) return { ok: false, error: "Unauthorized" } as const; + + // Ensure hack exists and belongs to user (created_by) to prevent spoof + const { data: hack, error: hErr } = await supabase + .from("hacks") + .select("slug, created_by") + .eq("slug", args.slug) + .maybeSingle(); + if (hErr) return { ok: false, error: hErr.message } as const; + if (!hack) return { ok: false, error: "Hack not found" } as const; + if (hack.created_by !== user.id) return { ok: false, error: "Forbidden" } as const; + + // Insert covers (overwrite positions) + if (args.coverUrls && args.coverUrls.length > 0) { + // Clear any existing rows first (idempotency on retry) + await supabase.from("hack_covers").delete().eq("hack_slug", args.slug); + const rows = args.coverUrls.map((url, idx) => ({ hack_slug: args.slug, url, position: idx + 1 })); + const { error: cErr } = await supabase.from("hack_covers").insert(rows); + if (cErr) return { ok: false, error: cErr.message } as const; + } + + return { ok: true } as const; +} + export async function presignPatchAndSaveCovers(args: { slug: string; version: string; diff --git a/src/components/Hack/HackSubmitForm.tsx b/src/components/Hack/HackSubmitForm.tsx index 03ba58e..3af6dc5 100644 --- a/src/components/Hack/HackSubmitForm.tsx +++ b/src/components/Hack/HackSubmitForm.tsx @@ -5,7 +5,7 @@ import Image from "next/image"; import { baseRoms } from "@/data/baseRoms"; import HackCard from "@/components/HackCard"; import { createClient } from "@/utils/supabase/client"; -import { prepareSubmission, presignPatchAndSaveCovers, confirmPatchUpload } from "@/app/submit/actions"; +import { prepareSubmission, presignPatchAndSaveCovers, confirmPatchUpload, saveHackCovers } from "@/app/submit/actions"; import { presignCoverUpload } from "@/app/hack/actions"; import { DndContext, PointerSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core"; import { SortableContext, arrayMove, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable"; @@ -413,6 +413,8 @@ export default function HackSubmitForm({ if (isArchive) { // For archives, we don't need patch upload + const coversSaved = await saveHackCovers({ slug: prepared.slug, coverUrls: uploadedCoverUrls }); + if (!coversSaved.ok) throw new Error(coversSaved.error || 'Failed to save covers'); try { if (draftKey) { localStorage.removeItem(draftKey);