user auth bearer updates to multiple apis and scripts

This commit is contained in:
makearmy 2025-09-29 12:39:59 -04:00
parent eb1a97541e
commit b54120d88e
4 changed files with 182 additions and 160 deletions

View file

@ -1,6 +1,7 @@
// app/api/submit/settings/route.ts
import { NextResponse } from "next/server";
import { uploadFile, createSettingsItem, bytesFromMB } from "@/lib/directus";
import { uploadFile, createSettingsItem, bytesFromMB, dxGET } from "@/lib/directus";
import { requireBearer } from "@/app/api/_lib/auth";
/**
* Accepts EITHER:
@ -8,7 +9,7 @@ import { uploadFile, createSettingsItem, bytesFromMB } from "@/lib/directus";
* (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)
* - photo = File (required if no photo id present)
* - screen = File (optional)
*
* Targets (collections):
@ -47,10 +48,6 @@ function num(v: any, fallback: number | null = null) {
return Number.isFinite(n) ? n : fallback;
}
function bool(v: any) {
return !!v;
}
type ReadResult = {
mode: "json" | "multipart";
body: any;
@ -64,7 +61,6 @@ async function readJsonOrMultipart(req: Request): Promise<ReadResult> {
if (ct.includes("multipart/form-data")) {
const form = await (req as any).formData();
// payload JSON (required for the structured fields)
const payloadRaw = String(form.get("payload") ?? "{}");
let body: any = {};
try {
@ -73,7 +69,6 @@ async function readJsonOrMultipart(req: Request): Promise<ReadResult> {
throw new Error("Invalid JSON in 'payload'");
}
// Files (File or null)
const p = form.get("photo");
const s = form.get("screen");
const photoFile = p instanceof File && p.size > 0 ? (p as File) : null;
@ -82,7 +77,6 @@ async function readJsonOrMultipart(req: Request): Promise<ReadResult> {
return { mode: "multipart", body, photoFile, screenFile };
}
// JSON body
const body = await (req as any).json().catch(() => ({}));
return { mode: "json", body, photoFile: null, screenFile: null };
}
@ -119,6 +113,9 @@ export async function POST(req: Request) {
return NextResponse.json({ error: "Rate limited" }, { status: 429 });
}
// NEW: enforce user auth
const bearer = requireBearer(req);
const { mode, body, photoFile, screenFile } = await readJsonOrMultipart(req);
const target: Target = body?.target;
@ -128,9 +125,16 @@ export async function POST(req: Request) {
// Required basics
const setting_title = String(body?.setting_title || "").trim();
const uploader = String(body?.uploader || "").trim();
if (!setting_title) return NextResponse.json({ error: "Missing required: setting_title" }, { status: 400 });
if (!uploader) return NextResponse.json({ error: "Missing required: uploader" }, { status: 400 });
// Derive uploader from the authenticated user (ignore any spoofed value in body)
const me = await dxGET<any>("/users/me?fields=username,display_name,first_name,last_name,email", bearer);
const uploader =
me?.display_name ||
me?.username ||
[me?.first_name, me?.last_name].filter(Boolean).join(" ") ||
me?.email ||
"user";
// Relations & numerics
const mat = body?.mat ?? null;
@ -155,7 +159,7 @@ export async function POST(req: Request) {
if (!photo_id && photoFile) {
if (photoFile.size > MAX_BYTES)
return NextResponse.json({ error: `Photo exceeds ${MAX_MB} MB` }, { status: 400 });
const up = await uploadFile(photoFile, (photoFile as File).name, {
const up = await uploadFile(photoFile, (photoFile as File).name, bearer, {
folderNamePath: folderPathFor(target, "photo"),
title: setting_title,
});
@ -168,7 +172,7 @@ export async function POST(req: Request) {
if (!screen_id && screenFile) {
if (screenFile.size > MAX_BYTES)
return NextResponse.json({ error: `Screenshot exceeds ${MAX_MB} MB` }, { status: 400 });
const up = await uploadFile(screenFile, (screenFile as File).name, {
const up = await uploadFile(screenFile, (screenFile as File).name, bearer, {
folderNamePath: folderPathFor(target, "screen"),
title: `${setting_title} (screen)`,
});
@ -185,11 +189,11 @@ export async function POST(req: Request) {
const payload: Record<string, any> = {
setting_title,
uploader,
uploader, // ← server-derived from /users/me
setting_notes,
submission_date: nowIso, // required by your schema
last_modified_date: nowIso, // ← add this to satisfy required rule
submission_date: nowIso, // required by your schema
last_modified_date: nowIso, // keep in sync
photo: photo_id,
screen: screen_id ?? null,
@ -217,11 +221,11 @@ export async function POST(req: Request) {
payload.repeat_all = repeat_all ?? null;
}
const { data } = await createSettingsItem(target, payload);
const { data } = await createSettingsItem(target, payload, bearer);
return NextResponse.json({ ok: true, id: data.id });
} catch (err: any) {
console.error("[submit/settings] error", err?.message || err);
return NextResponse.json({ error: err?.message || "Unknown error" }, { status: 500 });
return NextResponse.json({ error: err?.message || "Unknown error" }, { status: err?.status ?? 500 });
} finally {
const ms = Date.now() - started;
if (ms) console.log(`[submit/settings] handled in ~${ms}ms`);