built user portal behind auth
This commit is contained in:
parent
5c6962f4a5
commit
37d474d7c8
48 changed files with 822 additions and 496 deletions
|
|
@ -1,11 +1,28 @@
|
|||
// app/api/auth/logout/route.ts
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { clearAuthCookies } from "@/lib/auth-cookies";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
const secure = process.env.NODE_ENV === "production";
|
||||
|
||||
export async function POST(_req: NextRequest) {
|
||||
const res = NextResponse.json({ ok: true });
|
||||
clearAuthCookies(res);
|
||||
|
||||
res.cookies.set({
|
||||
name: "ma_at",
|
||||
value: "",
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
secure,
|
||||
path: "/",
|
||||
expires: new Date(0), // expire immediately
|
||||
maxAge: 0,
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Optional: support GET if you ever link to /api/auth/logout directly
|
||||
export async function GET(_req: NextRequest) {
|
||||
return POST(_req);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
// app/api/my/rigs/[id]/route.ts
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { cookies } from "next/headers";
|
||||
import { directusFetch } from "@/lib/directus";
|
||||
|
|
@ -5,17 +6,16 @@ import { directusFetch } from "@/lib/directus";
|
|||
const BASE_COLLECTION = "user_rigs";
|
||||
|
||||
async function bearerFromCookies() {
|
||||
// Some Next 15 type defs model cookies() as async—await to satisfy TS in all envs.
|
||||
const store = await cookies();
|
||||
const at = store.get("ma_at")?.value;
|
||||
if (!at) throw new Error("Not authenticated");
|
||||
return `Bearer ${at}`;
|
||||
}
|
||||
|
||||
export async function PATCH(req: NextRequest, { params }: any) {
|
||||
export async function PATCH(req: NextRequest, { params }: { params: { id?: string } }) {
|
||||
try {
|
||||
const auth = await bearerFromCookies();
|
||||
const body = await req.json();
|
||||
const body = await req.json().catch(() => ({}));
|
||||
const id = params?.id;
|
||||
if (!id) return NextResponse.json({ error: "Missing id" }, { status: 400 });
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ export async function PATCH(req: NextRequest, { params }: any) {
|
|||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: auth, // overrides helper's default token
|
||||
Authorization: auth, // force user-token for this call
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ export async function PATCH(req: NextRequest, { params }: any) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function DELETE(_req: NextRequest, { params }: any) {
|
||||
export async function DELETE(_req: NextRequest, { params }: { params: { id?: string } }) {
|
||||
try {
|
||||
const auth = await bearerFromCookies();
|
||||
const id = params?.id;
|
||||
|
|
@ -48,7 +48,7 @@ export async function DELETE(_req: NextRequest, { params }: any) {
|
|||
|
||||
await directusFetch(`/items/${BASE_COLLECTION}/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: { Authorization: auth },
|
||||
headers: { Authorization: auth }, // force user-token
|
||||
});
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
const BASE = process.env.DIRECTUS_URL!;
|
||||
const BASE = (process.env.DIRECTUS_URL || "").replace(/\/$/, "");
|
||||
|
||||
async function bearerFromCookies() {
|
||||
const store = await cookies();
|
||||
|
|
@ -25,6 +25,8 @@ async function getMyUserId(bearer: string) {
|
|||
export async function GET(_req: NextRequest) {
|
||||
try {
|
||||
const bearer = await bearerFromCookies();
|
||||
const myId = await getMyUserId(bearer);
|
||||
|
||||
const fields = [
|
||||
"id",
|
||||
"name",
|
||||
|
|
@ -42,10 +44,17 @@ export async function GET(_req: NextRequest) {
|
|||
"date_updated",
|
||||
].join(",");
|
||||
|
||||
const res = await fetch(
|
||||
`${BASE}/items/user_rigs?fields=${encodeURIComponent(fields)}&sort=-date_created`,
|
||||
{ headers: { Authorization: bearer, Accept: "application/json" }, cache: "no-store" }
|
||||
);
|
||||
const url = new URL(`${BASE}/items/user_rigs`);
|
||||
url.searchParams.set("fields", fields);
|
||||
url.searchParams.set("sort", "-date_created");
|
||||
// If you use a custom owner field, switch this to filter[owner][_eq]
|
||||
url.searchParams.set("filter[user_created][_eq]", myId);
|
||||
|
||||
const res = await fetch(String(url), {
|
||||
headers: { Authorization: bearer, Accept: "application/json" },
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
const txt = await res.text();
|
||||
if (!res.ok) return NextResponse.json({ error: txt || res.statusText }, { status: res.status });
|
||||
const j = txt ? JSON.parse(txt) : { data: [] };
|
||||
|
|
@ -65,9 +74,11 @@ export async function POST(req: NextRequest) {
|
|||
try {
|
||||
const bearer = await bearerFromCookies();
|
||||
const body = await req.json().catch(() => ({}));
|
||||
const owner = await getMyUserId(bearer);
|
||||
|
||||
const payload = { ...body, owner };
|
||||
// If your collection requires a custom 'owner' field, uncomment:
|
||||
// const owner = await getMyUserId(bearer);
|
||||
// const payload = { ...body, owner };
|
||||
const payload = body;
|
||||
|
||||
const res = await fetch(`${BASE}/items/user_rigs`, {
|
||||
method: "POST",
|
||||
|
|
|
|||
|
|
@ -44,13 +44,12 @@ export async function GET(req: NextRequest, ctx: any) {
|
|||
|
||||
const mapped = items.map((it) => ({
|
||||
id: String(it.id ?? it.submission_id ?? ""),
|
||||
label: pickLabel(it, cfg.labelFields),
|
||||
name: pickLabel(it, cfg.labelFields),
|
||||
_search: `${Object.values(it).join(" ")}`.toLowerCase(),
|
||||
})).filter((m) => !!m.id);
|
||||
|
||||
const filtered = q ? mapped.filter((m) => m._search.includes(q)) : mapped;
|
||||
filtered.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
filtered.sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
|
||||
return NextResponse.json({ data: filtered.map(({ _search, ...r }) => r) });
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
|
|
|
|||
26
app/api/options/laser_focus_lens/route.ts
Normal file
26
app/api/options/laser_focus_lens/route.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
export const dynamic = "force-dynamic";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
const BASE = (process.env.DIRECTUS_URL || "").replace(/\/$/, "");
|
||||
const PATH = `/items/laser_focus_lens?fields=id,name&sort=name`;
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const userAt = req.cookies.get("ma_at")?.value;
|
||||
if (!userAt) return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
|
||||
|
||||
const res = await fetch(`${BASE}${PATH}`, {
|
||||
headers: { Accept: "application/json", Authorization: `Bearer ${userAt}` },
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
const txt = await res.text();
|
||||
if (!res.ok) return NextResponse.json({ error: txt || res.statusText }, { status: res.status });
|
||||
|
||||
const j = txt ? JSON.parse(txt) : { data: [] };
|
||||
const data = (j.data ?? []).map(({ id, name }: any) => ({ id, name }));
|
||||
return NextResponse.json({ data });
|
||||
} catch (e: any) {
|
||||
return NextResponse.json({ error: e?.message || "Failed to load focus lenses" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
39
app/api/options/laser_software/route.ts
Normal file
39
app/api/options/laser_software/route.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// app/api/options/laser_software/route.ts
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
const BASE = (process.env.DIRECTUS_URL || "").replace(/\/$/, "");
|
||||
const PATH = `/items/laser_software?fields=id,name&sort=name`;
|
||||
|
||||
async function dFetch(bearer: string) {
|
||||
const res = await fetch(`${BASE}${PATH}`, {
|
||||
headers: { Accept: "application/json", Authorization: `Bearer ${bearer}` },
|
||||
cache: "no-store",
|
||||
});
|
||||
const text = await res.text().catch(() => "");
|
||||
let json: any = null;
|
||||
try { json = text ? JSON.parse(text) : null; } catch {}
|
||||
return { res, json, text };
|
||||
}
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const userAt = req.cookies.get("ma_at")?.value;
|
||||
if (!userAt) return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
|
||||
|
||||
const r = await dFetch(userAt);
|
||||
if (!r.res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: `Directus ${r.res.status}: ${r.text || r.res.statusText}` },
|
||||
{ status: r.res.status }
|
||||
);
|
||||
}
|
||||
|
||||
const rows: Array<{ id: number | string; name: string }> = r.json?.data ?? [];
|
||||
const data = rows.map(({ id, name }) => ({ id, name }));
|
||||
return NextResponse.json({ data });
|
||||
} catch (e: any) {
|
||||
return NextResponse.json({ error: e?.message || "Failed to load software" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
|
@ -1,65 +1,49 @@
|
|||
// app/app/api/options/laser_source/route.ts
|
||||
import { NextResponse } from "next/server";
|
||||
import { directusFetch } from "@/lib/directus";
|
||||
// app/api/options/laser_source/route.ts
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
// Parse "nm" that may be stored as a string (e.g., "1064", "1064nm", "1,064")
|
||||
function parseNm(v: any): number | null {
|
||||
const s = String(v ?? "").replace(/[^0-9.]/g, "");
|
||||
if (!s) return null;
|
||||
const n = Number(s);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
const BASE = (process.env.DIRECTUS_URL || "").replace(/\/$/, "");
|
||||
|
||||
function buildPath(target?: string | null) {
|
||||
// If your schema supports target filtering, add it here. Otherwise we return all.
|
||||
const url = new URL(`${BASE}/items/laser_source`);
|
||||
url.searchParams.set("fields", "id,name");
|
||||
url.searchParams.set("sort", "name");
|
||||
// Example (uncomment/adjust if you actually have a `target` field or relation):
|
||||
// if (target) url.searchParams.set("filter[target][_eq]", target);
|
||||
return String(url);
|
||||
}
|
||||
|
||||
// target → wavelength range (nm)
|
||||
function nmRangeForTarget(t?: string): [number, number] | null {
|
||||
switch (t) {
|
||||
case "settings_fiber": return [1000, 1100];
|
||||
case "settings_uv": return [300, 400];
|
||||
case "settings_co2gan":
|
||||
case "settings_co2gal": return [10000, 11000];
|
||||
default: return null;
|
||||
}
|
||||
async function dFetch(bearer: string, target?: string | null) {
|
||||
const res = await fetch(buildPath(target), {
|
||||
headers: { Accept: "application/json", Authorization: `Bearer ${bearer}` },
|
||||
cache: "no-store",
|
||||
});
|
||||
const text = await res.text().catch(() => "");
|
||||
let json: any = null;
|
||||
try { json = text ? JSON.parse(text) : null; } catch {}
|
||||
return { res, json, text };
|
||||
}
|
||||
|
||||
export async function GET(req: Request) {
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const target = searchParams.get("target") || undefined;
|
||||
const q = (searchParams.get("q") || "").trim().toLowerCase();
|
||||
const limit = Number(searchParams.get("limit") || "500");
|
||||
const userAt = req.cookies.get("ma_at")?.value;
|
||||
if (!userAt) return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
|
||||
|
||||
const range = nmRangeForTarget(target);
|
||||
if (!range) {
|
||||
return NextResponse.json({ error: "missing/invalid target" }, { status: 400 });
|
||||
const target = req.nextUrl.searchParams.get("target");
|
||||
const r = await dFetch(userAt, target);
|
||||
if (!r.res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: `Directus ${r.res.status}: ${r.text || r.res.statusText}` },
|
||||
{ status: r.res.status }
|
||||
);
|
||||
}
|
||||
const [lo, hi] = range;
|
||||
|
||||
// Only request fields we can read. laser_source uses submission_id as PK.
|
||||
const url = `/items/laser_source?fields=submission_id,make,model,nm&limit=${limit}`;
|
||||
const { data } = await directusFetch<{ data: any[] }>(url);
|
||||
const list = Array.isArray(data) ? data : [];
|
||||
|
||||
// Filter by nm and optional text query
|
||||
const filtered = list.filter((x) => {
|
||||
const nm = parseNm(x.nm);
|
||||
if (nm === null || nm < lo || nm > hi) return false;
|
||||
if (!q) return true;
|
||||
const label = [x.make, x.model].filter(Boolean).join(" ").toLowerCase();
|
||||
return label.includes(q);
|
||||
});
|
||||
|
||||
// Build labels and sort by make, then model
|
||||
const out = filtered
|
||||
.map((x) => ({
|
||||
id: String(x.submission_id), // critical: use submission_id, not id
|
||||
label: [x.make, x.model].filter(Boolean).join(" ") || String(x.submission_id),
|
||||
sortKey: [x.make ?? "", x.model ?? ""].join(" ").toLowerCase(),
|
||||
}))
|
||||
.sort((a, b) => a.sortKey.localeCompare(b.sortKey))
|
||||
.map(({ id, label }) => ({ id, label }));
|
||||
|
||||
return NextResponse.json({ data: out });
|
||||
const rows: Array<{ id: number | string; name: string }> = r.json?.data ?? [];
|
||||
const data = rows.map(({ id, name }) => ({ id, name }));
|
||||
return NextResponse.json({ data });
|
||||
} catch (e: any) {
|
||||
return NextResponse.json({ error: e?.message || "Failed to load laser_source" }, { status: 500 });
|
||||
return NextResponse.json({ error: e?.message || "Failed to load laser sources" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,101 +1,49 @@
|
|||
// app/api/options/lens/route.ts
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { directusFetch } from "@/lib/directus";
|
||||
|
||||
/**
|
||||
* Parse "110x110", "110×110", "110 X 110", etc. -> [110, 110]
|
||||
* Returns null if we can't find two numbers.
|
||||
*/
|
||||
function parseFieldSize(s: unknown): [number, number] | null {
|
||||
if (!s) return null;
|
||||
const nums = String(s).match(/\d+(\.\d+)?/g)?.map((n) => Number(n)) ?? [];
|
||||
if (nums.length >= 2 && Number.isFinite(nums[0]) && Number.isFinite(nums[1])) {
|
||||
return [nums[0], nums[1]];
|
||||
}
|
||||
return null;
|
||||
const BASE = (process.env.DIRECTUS_URL || "").replace(/\/$/, "");
|
||||
|
||||
function buildPath(target?: string | null) {
|
||||
// Adjust the collection name if yours differs (e.g., laser_scan_lens)
|
||||
const url = new URL(`${BASE}/items/laser_scan_lens`);
|
||||
url.searchParams.set("fields", "id,name");
|
||||
url.searchParams.set("sort", "name");
|
||||
// Example if you model per-target lenses:
|
||||
// if (target) url.searchParams.set("filter[target][_eq]", target);
|
||||
return String(url);
|
||||
}
|
||||
|
||||
/** Pull a clean integer from a value like "F160", "160", "160mm" */
|
||||
function parseFocalLength(v: unknown): number | null {
|
||||
if (v == null) return null;
|
||||
const m = String(v).match(/\d+(\.\d+)?/);
|
||||
if (!m) return null;
|
||||
const n = Number(m[0]);
|
||||
return Number.isFinite(n) ? Math.round(n) : null;
|
||||
}
|
||||
|
||||
/** Natural-ish compare for scan lens sizes: sort by width then height */
|
||||
function sizeCompare(a: string, b: string): number {
|
||||
const as = parseFieldSize(a);
|
||||
const bs = parseFieldSize(b);
|
||||
if (!as && !bs) return String(a).localeCompare(String(b));
|
||||
if (!as) return 1;
|
||||
if (!bs) return -1;
|
||||
// width, then height
|
||||
if (as[0] !== bs[0]) return as[0] - bs[0];
|
||||
if (as[1] !== bs[1]) return as[1] - bs[1];
|
||||
return 0;
|
||||
async function dFetch(bearer: string, target?: string | null) {
|
||||
const res = await fetch(buildPath(target), {
|
||||
headers: { Accept: "application/json", Authorization: `Bearer ${bearer}` },
|
||||
cache: "no-store",
|
||||
});
|
||||
const text = await res.text().catch(() => "");
|
||||
let json: any = null;
|
||||
try { json = text ? JSON.parse(text) : null; } catch {}
|
||||
return { res, json, text };
|
||||
}
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const target = searchParams.get("target") || "";
|
||||
const q = (searchParams.get("q") || "").trim().toLowerCase();
|
||||
const limit = Number(searchParams.get("limit") || "500");
|
||||
const userAt = req.cookies.get("ma_at")?.value;
|
||||
if (!userAt) return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
|
||||
|
||||
const isGantry = target === "settings_co2gan";
|
||||
|
||||
if (isGantry) {
|
||||
// CO2 Gantry -> FOCUS lenses
|
||||
const url = `/items/laser_focus_lens?fields=id,name&limit=${limit}`;
|
||||
const { data } = await directusFetch<{ data: Array<{ id: string | number; name?: string }> }>(url);
|
||||
let rows = (data ?? []).map((r) => ({
|
||||
id: String(r.id),
|
||||
label: r.name?.trim() || String(r.id),
|
||||
_key: r.name?.toLowerCase() ?? "",
|
||||
}));
|
||||
|
||||
if (q) rows = rows.filter((r) => r.label.toLowerCase().includes(q));
|
||||
rows.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
return NextResponse.json({ data: rows.map(({ id, label }) => ({ id, label })) });
|
||||
const target = req.nextUrl.searchParams.get("target");
|
||||
const r = await dFetch(userAt, target);
|
||||
if (!r.res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: `Directus ${r.res.status}: ${r.text || r.res.statusText}` },
|
||||
{ status: r.res.status }
|
||||
);
|
||||
}
|
||||
|
||||
// Galvo/UV/Fiber -> SCAN lenses with field_size + focal_length
|
||||
const url = `/items/laser_scan_lens?fields=id,field_size,focal_length&limit=${limit}`;
|
||||
const { data } = await directusFetch<{
|
||||
data: Array<{ id: string | number; field_size?: string; focal_length?: string | number }>;
|
||||
}>(url);
|
||||
|
||||
let rows = (data ?? []).map((r) => {
|
||||
const sizeTxt = (r.field_size ?? "").toString().trim(); // e.g. "110x110"
|
||||
const fmm = parseFocalLength(r.focal_length); // e.g. 160
|
||||
// Build label: "110x110mm (F160)" or "110x110mm" if focal length missing
|
||||
const label =
|
||||
sizeTxt
|
||||
? `${sizeTxt}mm${fmm != null ? ` (F${fmm})` : ""}`
|
||||
: `${r.id}`;
|
||||
return {
|
||||
id: String(r.id),
|
||||
label,
|
||||
_size: sizeTxt,
|
||||
};
|
||||
});
|
||||
|
||||
if (q) {
|
||||
const qq = q.toLowerCase();
|
||||
rows = rows.filter((r) => r.label.toLowerCase().includes(qq));
|
||||
}
|
||||
|
||||
rows.sort((a, b) => {
|
||||
const c = sizeCompare(a._size, b._size);
|
||||
return c !== 0 ? c : a.label.localeCompare(b.label);
|
||||
});
|
||||
|
||||
return NextResponse.json({ data: rows.map(({ id, label }) => ({ id, label })) });
|
||||
const rows: Array<{ id: number | string; name: string }> = r.json?.data ?? [];
|
||||
const data = rows.map(({ id, name }) => ({ id, name }));
|
||||
return NextResponse.json({ data });
|
||||
} catch (e: any) {
|
||||
console.error("[options/lens] error:", e?.message || e);
|
||||
return NextResponse.json({ error: e?.message || "Internal error" }, { status: 500 });
|
||||
return NextResponse.json({ error: e?.message || "Failed to load lenses" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,57 +0,0 @@
|
|||
// app/api/options/rig_type/route.ts
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
const BASE = (process.env.DIRECTUS_URL || "").replace(/\/$/, "");
|
||||
const SUBMIT = process.env.DIRECTUS_TOKEN_SUBMIT || ""; // fallback for anon contexts
|
||||
const ADMIN = process.env.DIRECTUS_TOKEN_ADMIN_REGISTER || ""; // last-resort fallback
|
||||
|
||||
const PATH = `/items/user_rig_type?fields=id,name&sort=sort`;
|
||||
|
||||
async function dFetch(auth?: string) {
|
||||
const headers: HeadersInit = { Accept: "application/json" };
|
||||
if (auth) headers.Authorization = `Bearer ${auth}`;
|
||||
const res = await fetch(`${BASE}${PATH}`, { headers, cache: "no-store" });
|
||||
const text = await res.text().catch(() => "");
|
||||
let json: any = null;
|
||||
try { json = text ? JSON.parse(text) : null; } catch {}
|
||||
return { res, json, text };
|
||||
}
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
// 1) Prefer the *user's* token set by login
|
||||
const userAt = req.cookies.get("ma_at")?.value;
|
||||
|
||||
let r = await dFetch(userAt);
|
||||
|
||||
// 2) If that’s forbidden/unauthorized (or they’re logged out), fall back to SUBMIT
|
||||
if ((r.res.status === 401 || r.res.status === 403) && SUBMIT) {
|
||||
r = await dFetch(SUBMIT);
|
||||
}
|
||||
|
||||
// 3) As a final fallback, try ADMIN (useful during migrations/hardening)
|
||||
if ((r.res.status === 401 || r.res.status === 403) && ADMIN) {
|
||||
r = await dFetch(ADMIN);
|
||||
}
|
||||
|
||||
if (!r.res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: `Directus ${r.res.status}: ${r.text || r.res.statusText}` },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
const rows: Array<{ id: number | string; name: string }> =
|
||||
r.json?.data ?? r.json ?? [];
|
||||
|
||||
const data = rows.map(({ id, name }) => ({ id, label: name }));
|
||||
return NextResponse.json({ data }, { status: 200 });
|
||||
} catch (e: any) {
|
||||
return NextResponse.json(
|
||||
{ error: e?.message || "Failed to load rig types" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
39
app/api/options/user_rig_type/route.ts
Normal file
39
app/api/options/user_rig_type/route.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// app/api/options/user_rig_type/route.ts
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
const BASE = (process.env.DIRECTUS_URL || "").replace(/\/$/, "");
|
||||
const PATH = `/items/user_rig_type?fields=id,name&sort=sort`;
|
||||
|
||||
async function dFetch(bearer: string) {
|
||||
const res = await fetch(`${BASE}${PATH}`, {
|
||||
headers: { Accept: "application/json", Authorization: `Bearer ${bearer}` },
|
||||
cache: "no-store",
|
||||
});
|
||||
const text = await res.text().catch(() => "");
|
||||
let json: any = null;
|
||||
try { json = text ? JSON.parse(text) : null; } catch {}
|
||||
return { res, json, text };
|
||||
}
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const userAt = req.cookies.get("ma_at")?.value;
|
||||
if (!userAt) return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
|
||||
|
||||
const r = await dFetch(userAt);
|
||||
if (!r.res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: `Directus ${r.res.status}: ${r.text || r.res.statusText}` },
|
||||
{ status: r.res.status === 401 || r.res.status === 403 ? r.res.status : 500 }
|
||||
);
|
||||
}
|
||||
|
||||
const rows: Array<{ id: number | string; name: string }> = r.json?.data ?? [];
|
||||
const data = rows.map(({ id, name }) => ({ id, name }));
|
||||
return NextResponse.json({ data }, { status: 200 });
|
||||
} catch (e: any) {
|
||||
return NextResponse.json({ error: e?.message || "Failed to load rig types" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue