From 4e2fd5d8132f6bb1e94cf6ff1a9ba57447445f8c Mon Sep 17 00:00:00 2001 From: makearmy Date: Thu, 2 Oct 2025 22:58:09 -0400 Subject: [PATCH] build error fixes for submission --- app/api/submit/settings/route.ts | 98 ++++++++++++++++++++++++-------- 1 file changed, 75 insertions(+), 23 deletions(-) diff --git a/app/api/submit/settings/route.ts b/app/api/submit/settings/route.ts index 1ed685b2..c270f09b 100644 --- a/app/api/submit/settings/route.ts +++ b/app/api/submit/settings/route.ts @@ -1,6 +1,6 @@ // app/api/submit/settings/route.ts import { NextResponse } from "next/server"; -import { uploadFile, createSettingsItem, bytesFromMB, dxGET } from "@/lib/directus"; +import { uploadFile, createSettingsItem, bytesFromMB, dxGET, dxPATCH } from "@/lib/directus"; import { requireBearer } from "@/app/api/_lib/auth"; /** ───────────────────────────────────────────────────────────── @@ -9,7 +9,7 @@ import { requireBearer } from "@/app/api/_lib/auth"; * (photo/screen can be existing file ids on the body) * - multipart/form-data with: * - payload = JSON string (same shape as JSON body) - * - photo = File (required if no photo id present) + * - photo = File (required if no photo id present) ← create only * - screen = File (optional) * * Targets (collections): @@ -17,6 +17,10 @@ import { requireBearer } from "@/app/api/_lib/auth"; * - settings_co2gan * - settings_co2gal * - settings_uv + * + * Also supports editing: + * Body must include { mode: "edit", submission_id: string|number } + * We PATCH via filter[submission_id][_eq] and owner = current user. * ──────────────────────────────────────────────────────────── */ export const runtime = "nodejs"; @@ -49,7 +53,7 @@ function num(v: any, fallback: number | null = null) { } type ReadResult = { - mode: "json" | "multipart"; + mode: "json" | "multipart"; // transport mode, not create/edit body: any; photoFile: File | null; screenFile: File | null; @@ -125,7 +129,7 @@ export async function POST(req: Request) { // Enforce user auth (everything uses the user's token) const bearer = requireBearer(req); - const { mode, body, photoFile, screenFile } = await readJsonOrMultipart(req); + const { body, photoFile, screenFile } = await readJsonOrMultipart(req); const target: Target = body?.target; if ( @@ -135,6 +139,10 @@ export async function POST(req: Request) { return NextResponse.json({ error: "Invalid target" }, { status: 400 }); } + // Create vs Edit + const op: "create" | "edit" = + body?.mode === "edit" ? "edit" : "create"; + // Required basics const setting_title = String(body?.setting_title || "").trim(); if (!setting_title) { @@ -148,6 +156,12 @@ export async function POST(req: Request) { const meRes = await dxGET("/users/me?fields=id,username", bearer); const meId = meRes?.data?.id ?? meRes?.id ?? null; const meUsername = meRes?.data?.username ?? meRes?.username ?? null; + if (!meId) { + return NextResponse.json( + { error: "Unable to resolve current user." }, + { status: 401 } + ); + } // Attribution const uploader = meUsername || "user"; // string field mirrors owner.username @@ -165,14 +179,14 @@ export async function POST(req: Request) { // Shared string fields const laser_soft = body?.laser_soft ?? null; // exact key: 'laser_soft' - const repeat_all = num(body?.repeat_all, null); // ← universally applicable now + const repeat_all = num(body?.repeat_all, null); // universally applicable // Upload / accept existing file ids let photo_id: string | null = body?.photo ?? null; let screen_id: string | null = body?.screen ?? null; - // photo is required: if no id on body, require file - if (!photo_id && photoFile) { + // In CREATE mode: require a photo (either an id or a file upload) + if (op === "create" && !photo_id && photoFile) { if (photoFile.size > MAX_BYTES) { return NextResponse.json( { error: `Photo exceeds ${MAX_MB} MB` }, @@ -185,13 +199,14 @@ export async function POST(req: Request) { }); photo_id = up.id; } - if (!photo_id) { + if (op === "create" && !photo_id) { return NextResponse.json( { error: "Missing required: photo" }, { status: 400 } ); } + // Optional screen (both modes) if (!screen_id && screenFile) { if (screenFile.size > MAX_BYTES) { return NextResponse.json( @@ -214,24 +229,17 @@ export async function POST(req: Request) { // timestamps const nowIso = new Date().toISOString(); - const payload: Record = { + // Build payload common to both modes + const basePayload: Record = { setting_title, - + setting_notes, // Ownership & attribution owner: meId || null, // M2O to directus_users uploader, // string mirror of username - setting_notes, - - submission_date: nowIso, - last_modified_date: nowIso, - - photo: photo_id, - screen: screen_id ?? null, - // exact keys laser_soft, - repeat_all, // ← always included + repeat_all, mat, mat_coat, @@ -247,12 +255,56 @@ export async function POST(req: Request) { raster_settings: rasters, status: "pending", - submitted_via: "makearmy-app", - submitted_at: nowIso, + last_modified_date: nowIso, }; - const { data } = await createSettingsItem(target, payload, bearer); - return NextResponse.json({ ok: true, id: data.id }); + if (op === "create") { + // Create-only fields + basePayload.photo = photo_id; + basePayload.screen = screen_id ?? null; + basePayload.submission_date = nowIso; + basePayload.submitted_via = "makearmy-app"; + basePayload.submitted_at = nowIso; + + const { data } = await createSettingsItem(target, basePayload, bearer); + return NextResponse.json({ ok: true, id: data.id }); + } + + // EDIT mode + const submission_id = body?.submission_id ?? null; + if (!submission_id) { + return NextResponse.json( + { error: "Edit mode requires submission_id" }, + { status: 400 } + ); + } + + // Only include photo/screen if provided; otherwise leave untouched + const editPayload: Record = { ...basePayload }; + if (photo_id) editPayload.photo = photo_id; + if (screen_id) editPayload.screen = screen_id; + + // Patch by filter to avoid needing internal item id, and restrict to your own record + const qs = new URLSearchParams(); + qs.set("filter[_and][0][submission_id][_eq]", String(submission_id)); + // enforce owner matches current user (works whether owner is id or M2O) + qs.set("filter[_and][1][owner][_eq]", String(meId)); + + const res = await dxPATCH<{ data: any[] }>( + `/items/${target}?${qs.toString()}`, + bearer, + editPayload + ); + + const updatedCount = Array.isArray(res?.data) ? res.data.length : 0; + if (updatedCount < 1) { + return NextResponse.json( + { error: "Nothing updated (not found or not owned by you)" }, + { status: 404 } + ); + } + + return NextResponse.json({ ok: true, updated: updatedCount, submission_id }); } catch (err: any) { console.error("[submit/settings] error", err?.message || err); return NextResponse.json(