routing fixes
This commit is contained in:
parent
0ee9686fb1
commit
44a690f4d2
20 changed files with 769 additions and 962 deletions
Binary file not shown.
|
|
@ -1,31 +0,0 @@
|
||||||
// app/api/me/route.ts
|
|
||||||
import { NextResponse } from "next/server";
|
|
||||||
|
|
||||||
function readCookie(name: string, cookieHeader: string) {
|
|
||||||
const m = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]+)`));
|
|
||||||
return m?.[1] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function GET(req: Request) {
|
|
||||||
const base = process.env.NEXT_PUBLIC_API_BASE_URL!;
|
|
||||||
// include username here
|
|
||||||
const url = `${base}/users/me?fields=id,username,display_name,first_name,last_name,email`;
|
|
||||||
|
|
||||||
const cookieHeader = req.headers.get("cookie") ?? "";
|
|
||||||
const ma_at = readCookie("ma_at", cookieHeader);
|
|
||||||
|
|
||||||
const headers: Record<string, string> = { "cache-control": "no-store" };
|
|
||||||
if (cookieHeader) headers.cookie = cookieHeader;
|
|
||||||
if (ma_at) headers.authorization = `Bearer ${ma_at}`;
|
|
||||||
|
|
||||||
const res = await fetch(url, { headers, cache: "no-store" });
|
|
||||||
const body = await res.json().catch(() => ({}));
|
|
||||||
|
|
||||||
return new NextResponse(JSON.stringify(body), {
|
|
||||||
status: res.status,
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
"cache-control": "no-store",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
// app/api/my/rigs/[id]/route.ts
|
|
||||||
import { NextResponse } from "next/server";
|
|
||||||
import { cookies } from "next/headers";
|
|
||||||
import { directusFetch } from "@/lib/directus";
|
|
||||||
|
|
||||||
const BASE_COLLECTION = "user_rigs";
|
|
||||||
|
|
||||||
async function bearerFromCookies() {
|
|
||||||
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: Request, ctx: any) {
|
|
||||||
try {
|
|
||||||
const auth = await bearerFromCookies();
|
|
||||||
const body = await req.json().catch(() => ({}));
|
|
||||||
const id = ctx?.params?.id as string | undefined;
|
|
||||||
if (!id) return NextResponse.json({ error: "Missing id" }, { status: 400 });
|
|
||||||
|
|
||||||
const data = await directusFetch<{ data: any }>(`/items/${BASE_COLLECTION}/${id}`, {
|
|
||||||
method: "PATCH",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: auth, // force user-token for this call
|
|
||||||
Accept: "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
|
|
||||||
return NextResponse.json({ ok: true, data: data.data });
|
|
||||||
} catch (err: any) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: err?.message || "Update failed" },
|
|
||||||
{ status: err?.message === "Not authenticated" ? 401 : 400 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function DELETE(_req: Request, ctx: any) {
|
|
||||||
try {
|
|
||||||
const auth = await bearerFromCookies();
|
|
||||||
const id = ctx?.params?.id as string | undefined;
|
|
||||||
if (!id) return NextResponse.json({ error: "Missing id" }, { status: 400 });
|
|
||||||
|
|
||||||
await directusFetch(`/items/${BASE_COLLECTION}/${id}`, {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
Authorization: auth, // force user-token
|
|
||||||
Accept: "application/json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return NextResponse.json({ ok: true });
|
|
||||||
} catch (err: any) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: err?.message || "Delete failed" },
|
|
||||||
{ status: err?.message === "Not authenticated" ? 401 : 400 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
// app/api/my/rigs/route.ts
|
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
import { cookies } from "next/headers";
|
|
||||||
|
|
||||||
const BASE = (process.env.DIRECTUS_URL || "").replace(/\/$/, "");
|
|
||||||
|
|
||||||
async function bearerFromCookies() {
|
|
||||||
const store = await cookies();
|
|
||||||
const at = store.get("ma_at")?.value;
|
|
||||||
if (!at) throw new Error("Not authenticated");
|
|
||||||
return `Bearer ${at}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getMyUserId(bearer: string) {
|
|
||||||
const res = await fetch(`${BASE}/users/me`, {
|
|
||||||
headers: { Authorization: bearer, Accept: "application/json" },
|
|
||||||
cache: "no-store",
|
|
||||||
});
|
|
||||||
const txt = await res.text();
|
|
||||||
if (!res.ok) throw new Error(txt || res.statusText);
|
|
||||||
const j = txt ? JSON.parse(txt) : {};
|
|
||||||
return j?.data?.id as string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function GET(_req: NextRequest) {
|
|
||||||
try {
|
|
||||||
const bearer = await bearerFromCookies();
|
|
||||||
const myId = await getMyUserId(bearer);
|
|
||||||
|
|
||||||
const fields = [
|
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"rig_type",
|
|
||||||
"rig_type.name",
|
|
||||||
"laser_source",
|
|
||||||
"laser_focus_lens",
|
|
||||||
"laser_scan_lens",
|
|
||||||
"laser_scan_lens_apt",
|
|
||||||
"laser_scan_lens_exp",
|
|
||||||
"laser_software",
|
|
||||||
"notes",
|
|
||||||
"user_created",
|
|
||||||
"date_created",
|
|
||||||
"date_updated",
|
|
||||||
].join(",");
|
|
||||||
|
|
||||||
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: [] };
|
|
||||||
|
|
||||||
const data = (j.data ?? []).map((r: any) => ({
|
|
||||||
...r,
|
|
||||||
rig_type_name: r?.rig_type?.name ?? r?.rig_type_name ?? null,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return NextResponse.json({ data });
|
|
||||||
} catch (e: any) {
|
|
||||||
return NextResponse.json({ error: e?.message || "Failed to list rigs" }, { status: 401 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
|
||||||
try {
|
|
||||||
const bearer = await bearerFromCookies();
|
|
||||||
const body = await req.json().catch(() => ({}));
|
|
||||||
|
|
||||||
// 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",
|
|
||||||
headers: {
|
|
||||||
Authorization: bearer,
|
|
||||||
Accept: "application/json",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(payload),
|
|
||||||
});
|
|
||||||
|
|
||||||
const txt = await res.text();
|
|
||||||
if (!res.ok) return NextResponse.json({ error: txt || res.statusText }, { status: res.status });
|
|
||||||
const j = txt ? JSON.parse(txt) : {};
|
|
||||||
return NextResponse.json(j);
|
|
||||||
} catch (e: any) {
|
|
||||||
return NextResponse.json({ error: e?.message || "Failed to create rig" }, { status: 400 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
// app/api/options/[collection]/route.ts
|
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
import { directusFetch } from "@/lib/directus";
|
|
||||||
|
|
||||||
// Expandable label-field preferences per collection.
|
|
||||||
// We’ll try each key in order until we find a value.
|
|
||||||
const MAP: Record<
|
|
||||||
string,
|
|
||||||
{ coll: string; labelFields: string[] }
|
|
||||||
> = {
|
|
||||||
material: { coll: "material", labelFields: ["name", "label", "title"] },
|
|
||||||
material_coating: { coll: "material_coating", labelFields: ["name", "label", "title"] },
|
|
||||||
material_color: { coll: "material_color", labelFields: ["name", "label", "title"] },
|
|
||||||
material_opacity: { coll: "material_opacity", labelFields: ["name", "label", "title", "value"] },
|
|
||||||
laser_software: { coll: "laser_software", labelFields: ["name", "label", "title"] },
|
|
||||||
|
|
||||||
// NEW: Galvo scan head aperture list
|
|
||||||
laser_scan_lens_apt: { coll: "laser_scan_lens_apt", labelFields: ["name", "label", "title", "aperture_mm", "size_mm", "value"] },
|
|
||||||
|
|
||||||
// NEW: Beam expander multiplier list
|
|
||||||
laser_scan_lens_exp: { coll: "laser_scan_lens_exp", labelFields: ["name", "label", "title", "multiplier", "value"] },
|
|
||||||
};
|
|
||||||
|
|
||||||
function pickLabel(it: any, candidates: string[]) {
|
|
||||||
for (const k of candidates) if (it?.[k] != null && it[k] !== "") return String(it[k]);
|
|
||||||
// fallback: try some common numeric-ish fields if present
|
|
||||||
if (it?.value != null) return String(it.value);
|
|
||||||
return String(it?.name ?? it?.label ?? it?.title ?? it?.id ?? "");
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function GET(req: NextRequest, ctx: any) {
|
|
||||||
try {
|
|
||||||
const key = String(ctx?.params?.collection || "");
|
|
||||||
const cfg = MAP[key];
|
|
||||||
if (!cfg) return NextResponse.json({ data: [] });
|
|
||||||
|
|
||||||
const { searchParams } = new URL(req.url);
|
|
||||||
const q = (searchParams.get("q") || "").toLowerCase();
|
|
||||||
|
|
||||||
// Keep fields=* so we can build a friendly label from whatever exists
|
|
||||||
const url = `/items/${cfg.coll}?fields=*&limit=500`;
|
|
||||||
const res = await directusFetch<{ data: any[] }>(url);
|
|
||||||
const items = res?.data ?? [];
|
|
||||||
|
|
||||||
const mapped = items.map((it) => ({
|
|
||||||
id: String(it.id ?? it.submission_id ?? ""),
|
|
||||||
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.name ?? "").localeCompare(b.name ?? ""));
|
|
||||||
return NextResponse.json({ data: filtered.map(({ _search, ...r }) => r) });
|
|
||||||
} catch (err: any) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: err?.message || "options error" },
|
|
||||||
{ status: 500 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
48
app/api/options/_lib.ts
Normal file
48
app/api/options/_lib.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
// app/api/options/_lib.ts
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
|
export type Option = { id: string | number; label: string };
|
||||||
|
|
||||||
|
export function readCookie(name: string, cookieHeader: string) {
|
||||||
|
const m = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]+)`));
|
||||||
|
return m?.[1] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAuthHeaders(req: NextRequest) {
|
||||||
|
const cookieHeader = req.headers.get("cookie") ?? "";
|
||||||
|
const ma_at = readCookie("ma_at", cookieHeader);
|
||||||
|
const headers: Record<string, string> = { Accept: "application/json" };
|
||||||
|
if (cookieHeader) headers.cookie = cookieHeader;
|
||||||
|
if (ma_at) headers.authorization = `Bearer ${ma_at}`;
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function apiBase() {
|
||||||
|
const base = (process.env.DIRECTUS_URL || process.env.NEXT_PUBLIC_API_BASE_URL || "").replace(/\/$/, "");
|
||||||
|
if (!base) throw new Error("Missing DIRECTUS_URL or NEXT_PUBLIC_API_BASE_URL");
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function dFetchJSON<T = any>(req: NextRequest, path: string): Promise<T> {
|
||||||
|
const res = await fetch(`${apiBase()}${path}`, {
|
||||||
|
headers: getAuthHeaders(req),
|
||||||
|
cache: "no-store",
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const text = await res.text().catch(() => "");
|
||||||
|
throw new Error(`Directus ${res.status} fetching ${path}: ${text}`);
|
||||||
|
}
|
||||||
|
return res.json() as Promise<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyQFilter<T>(rows: T[], q: string | null, pick: (row: T) => string): T[] {
|
||||||
|
if (!q) return rows;
|
||||||
|
const needle = q.trim().toLowerCase();
|
||||||
|
if (!needle) return rows;
|
||||||
|
return rows.filter((r) => (pick(r) || "").toLowerCase().includes(needle));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function json(data: Option[] | { data: Option[] }, status = 200) {
|
||||||
|
const body = Array.isArray(data) ? { data } : data;
|
||||||
|
return NextResponse.json(body, { status, headers: { "cache-control": "no-store" } });
|
||||||
|
}
|
||||||
|
|
@ -1,26 +1,11 @@
|
||||||
export const dynamic = "force-dynamic";
|
import { NextRequest } from "next/server";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { dFetchJSON, applyQFilter, json, Option } from "../_lib";
|
||||||
|
|
||||||
const BASE = (process.env.DIRECTUS_URL || "").replace(/\/$/, "");
|
type Row = { id: number | string; name?: string | null };
|
||||||
const PATH = `/items/laser_focus_lens?fields=id,name&sort=name`;
|
|
||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
try {
|
const q = new URL(req.url).searchParams.get("q");
|
||||||
const userAt = req.cookies.get("ma_at")?.value;
|
const { data } = await dFetchJSON<{ data: Row[] }>(req, "/items/laser_focus_lens?fields=id,name&limit=1000&sort=name");
|
||||||
if (!userAt) return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
|
const options: Option[] = data.map((r) => ({ id: r.id, label: r.name ?? String(r.id) }));
|
||||||
|
return json(applyQFilter(options, q, (o) => o.label));
|
||||||
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 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
16
app/api/options/laser_scan_lens/route.ts
Normal file
16
app/api/options/laser_scan_lens/route.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { NextRequest } from "next/server";
|
||||||
|
import { dFetchJSON, applyQFilter, json, Option } from "../_lib";
|
||||||
|
|
||||||
|
type Row = { id: number | string; name?: string | null; focal_length?: number | string | null; field_size?: number | string | null };
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
const q = new URL(req.url).searchParams.get("q");
|
||||||
|
const { data } = await dFetchJSON<{ data: Row[] }>(req, "/items/laser_scan_lens?fields=id,name,focal_length,field_size&limit=1000&sort=name");
|
||||||
|
const options: Option[] = data.map((r) => {
|
||||||
|
const fl = r.focal_length != null ? `${r.focal_length}` : "";
|
||||||
|
const fs = r.field_size != null ? `${r.field_size}` : "";
|
||||||
|
const composed = r.name ?? [fl && `${fl} mm`, fs && `${fs} mm`].filter(Boolean).join(" — ") || String(r.id);
|
||||||
|
return { id: r.id, label: composed };
|
||||||
|
});
|
||||||
|
return json(applyQFilter(options, q, (o) => o.label));
|
||||||
|
}
|
||||||
11
app/api/options/laser_scan_lens_apt/route.ts
Normal file
11
app/api/options/laser_scan_lens_apt/route.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { NextRequest } from "next/server";
|
||||||
|
import { dFetchJSON, applyQFilter, json, Option } from "../_lib";
|
||||||
|
|
||||||
|
type Row = { id: number | string; name?: string | null };
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
const q = new URL(req.url).searchParams.get("q");
|
||||||
|
const { data } = await dFetchJSON<{ data: Row[] }>(req, "/items/laser_scan_lens_apt?fields=id,name&limit=1000&sort=name");
|
||||||
|
const options: Option[] = data.map((r) => ({ id: r.id, label: r.name ?? String(r.id) }));
|
||||||
|
return json(applyQFilter(options, q, (o) => o.label));
|
||||||
|
}
|
||||||
11
app/api/options/laser_scan_lens_exp/route.ts
Normal file
11
app/api/options/laser_scan_lens_exp/route.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { NextRequest } from "next/server";
|
||||||
|
import { dFetchJSON, applyQFilter, json, Option } from "../_lib";
|
||||||
|
|
||||||
|
type Row = { id: number | string; name?: string | null };
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
const q = new URL(req.url).searchParams.get("q");
|
||||||
|
const { data } = await dFetchJSON<{ data: Row[] }>(req, "/items/laser_scan_lens_apt?fields=id,name&limit=1000&sort=name");
|
||||||
|
const options: Option[] = data.map((r) => ({ id: r.id, label: r.name ?? String(r.id) }));
|
||||||
|
return json(applyQFilter(options, q, (o) => o.label));
|
||||||
|
}
|
||||||
|
|
@ -1,39 +1,11 @@
|
||||||
// app/api/options/laser_software/route.ts
|
import { NextRequest } from "next/server";
|
||||||
export const dynamic = "force-dynamic";
|
import { dFetchJSON, applyQFilter, json, Option } from "../_lib";
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
type Row = { id: number | string; name?: string | null };
|
||||||
|
|
||||||
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) {
|
export async function GET(req: NextRequest) {
|
||||||
try {
|
const q = new URL(req.url).searchParams.get("q");
|
||||||
const userAt = req.cookies.get("ma_at")?.value;
|
const { data } = await dFetchJSON<{ data: Row[] }>(req, "/items/laser_software?fields=id,name&limit=1000&sort=name");
|
||||||
if (!userAt) return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
|
const options: Option[] = data.map((r) => ({ id: r.id, label: r.name ?? String(r.id) }));
|
||||||
|
return json(applyQFilter(options, q, (o) => o.label));
|
||||||
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,42 +1,43 @@
|
||||||
// app/api/options/laser_source/route.ts
|
import { NextRequest } from "next/server";
|
||||||
export const dynamic = "force-dynamic";
|
import { dFetchJSON, applyQFilter, json, Option } from "../_lib";
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
type Row = { submission_id: string | number; make?: string | null; model?: string | null; nm?: string | null };
|
||||||
const BASE = (process.env.DIRECTUS_URL || "").replace(/\/$/, "");
|
|
||||||
|
function rangeForTarget(target?: string | null): [number, number] | null {
|
||||||
|
if (!target) return null;
|
||||||
|
const t = target.toLowerCase();
|
||||||
|
if (t === "fiber") return [1000, 9000];
|
||||||
|
if (t === "uv") return [300, 400];
|
||||||
|
if (t === "co2-gantry" || t === "co2-galvo") return [10000, 11000];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function parseNm(s?: string | null): number | null {
|
||||||
|
if (!s) return null;
|
||||||
|
const m = String(s).match(/(\d+(\.\d+)?)/);
|
||||||
|
return m ? Number(m[1]) : null;
|
||||||
|
}
|
||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
try {
|
const url = new URL(req.url);
|
||||||
const userAt = req.cookies.get("ma_at")?.value;
|
const q = url.searchParams.get("q");
|
||||||
if (!userAt) return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
|
const target = url.searchParams.get("target"); // fiber | uv | co2-gantry | co2-galvo
|
||||||
|
const { data } = await dFetchJSON<{ data: Row[] }>(
|
||||||
|
req,
|
||||||
|
"/items/laser_source?fields=submission_id,make,model,nm&limit=2000&sort=make,model"
|
||||||
|
);
|
||||||
|
|
||||||
const url = new URL(`${BASE}/items/laser_source`);
|
const range = rangeForTarget(target);
|
||||||
// IMPORTANT: schema uses submission_id as the FK target
|
const filteredByNm = range
|
||||||
url.searchParams.set("fields", "submission_id,make,model,nm");
|
? data.filter((r) => {
|
||||||
url.searchParams.set("sort", "make,model");
|
const v = parseNm(r.nm);
|
||||||
|
return v != null && v >= range[0] && v <= range[1];
|
||||||
|
})
|
||||||
|
: data;
|
||||||
|
|
||||||
const res = await fetch(String(url), {
|
const options: Option[] = filteredByNm.map((r) => ({
|
||||||
headers: { Accept: "application/json", Authorization: `Bearer ${userAt}` },
|
id: r.submission_id,
|
||||||
cache: "no-store",
|
label: [r.make, r.model].filter(Boolean).join(" ") || String(r.submission_id),
|
||||||
});
|
}));
|
||||||
|
|
||||||
const text = await res.text().catch(() => "");
|
return json(applyQFilter(options, q, (o) => o.label));
|
||||||
const json = text ? JSON.parse(text) : {};
|
|
||||||
if (!res.ok) {
|
|
||||||
return NextResponse.json({ error: `Directus ${res.status}: ${text || res.statusText}` }, { status: res.status });
|
|
||||||
}
|
|
||||||
|
|
||||||
const rows: Array<{ submission_id: string | number; make?: string; model?: string; nm?: string | number }> =
|
|
||||||
json?.data ?? [];
|
|
||||||
|
|
||||||
const data = rows
|
|
||||||
.map((r) => {
|
|
||||||
const parts = [r.make, r.model, r.nm ? `${r.nm}nm` : null].filter(Boolean);
|
|
||||||
return { id: r.submission_id, label: parts.join(" ") };
|
|
||||||
})
|
|
||||||
.filter((x) => x.label);
|
|
||||||
|
|
||||||
return NextResponse.json({ data });
|
|
||||||
} catch (e: any) {
|
|
||||||
return NextResponse.json({ error: e?.message || "Failed to load laser sources" }, { status: 500 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
// app/api/options/lens/route.ts
|
|
||||||
export const dynamic = "force-dynamic";
|
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
|
|
||||||
const BASE = (
|
|
||||||
process.env.DIRECTUS_URL || process.env.NEXT_PUBLIC_API_BASE_URL || ""
|
|
||||||
).replace(/\/$/, "");
|
|
||||||
|
|
||||||
function buildPath(target?: string | null) {
|
|
||||||
// CO2 Gantry → focus lenses (name)
|
|
||||||
// everything else (Fiber/UV/CO2 Galvo) → scan lenses (field_size + focal_length)
|
|
||||||
const isGantry = target === "co2-gantry";
|
|
||||||
|
|
||||||
if (isGantry) {
|
|
||||||
const url = new URL(`${BASE}/items/laser_focus_lens`);
|
|
||||||
url.searchParams.set("fields", "id,name");
|
|
||||||
url.searchParams.set("sort", "name");
|
|
||||||
return String(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = new URL(`${BASE}/items/laser_scan_lens`);
|
|
||||||
url.searchParams.set("fields", "id,field_size,focal_length");
|
|
||||||
url.searchParams.set("sort", "field_size,focal_length");
|
|
||||||
return String(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 userAt = req.cookies.get("ma_at")?.value;
|
|
||||||
if (!userAt) {
|
|
||||||
return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
|
|
||||||
}
|
|
||||||
|
|
||||||
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 rows: any[] = r.json?.data ?? [];
|
|
||||||
const isGantry = target === "co2-gantry";
|
|
||||||
|
|
||||||
const data = rows.map((row) => {
|
|
||||||
if (isGantry) {
|
|
||||||
// Focus lens: label is just the stored name
|
|
||||||
return { id: row.id, name: row.name, label: row.name };
|
|
||||||
}
|
|
||||||
// Scan lens: label "300x300 mm | F420" etc
|
|
||||||
const fs =
|
|
||||||
row.field_size != null && row.field_size !== ""
|
|
||||||
? `${row.field_size} mm`
|
|
||||||
: "";
|
|
||||||
const fl =
|
|
||||||
row.focal_length != null && row.focal_length !== ""
|
|
||||||
? `F${row.focal_length}`
|
|
||||||
: "";
|
|
||||||
const label = [fs, fl].filter(Boolean).join(" | ");
|
|
||||||
return { id: row.id, name: label, label };
|
|
||||||
});
|
|
||||||
|
|
||||||
return NextResponse.json({ data });
|
|
||||||
} catch (e: any) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: e?.message || "Failed to load lenses" },
|
|
||||||
{ status: 500 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
app/api/options/material/route.ts
Normal file
11
app/api/options/material/route.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { NextRequest } from "next/server";
|
||||||
|
import { dFetchJSON, applyQFilter, json, Option } from "../_lib";
|
||||||
|
|
||||||
|
type Row = { id: number | string; name?: string | null };
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
const q = new URL(req.url).searchParams.get("q");
|
||||||
|
const { data } = await dFetchJSON<{ data: Row[] }>(req, "/items/material?fields=id,name&limit=1000&sort=name");
|
||||||
|
const options: Option[] = data.map((r) => ({ id: r.id, label: r.name ?? String(r.id) }));
|
||||||
|
return json(applyQFilter(options, q, (o) => o.label));
|
||||||
|
}
|
||||||
11
app/api/options/material_coating/route.ts
Normal file
11
app/api/options/material_coating/route.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { NextRequest } from "next/server";
|
||||||
|
import { dFetchJSON, applyQFilter, json, Option } from "../_lib";
|
||||||
|
|
||||||
|
type Row = { id: number | string; name?: string | null };
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
const q = new URL(req.url).searchParams.get("q");
|
||||||
|
const { data } = await dFetchJSON<{ data: Row[] }>(req, "/items/material_coating?fields=id,name&limit=1000&sort=name");
|
||||||
|
const options: Option[] = data.map((r) => ({ id: r.id, label: r.name ?? String(r.id) }));
|
||||||
|
return json(applyQFilter(options, q, (o) => o.label));
|
||||||
|
}
|
||||||
11
app/api/options/material_color/route.ts
Normal file
11
app/api/options/material_color/route.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { NextRequest } from "next/server";
|
||||||
|
import { dFetchJSON, applyQFilter, json, Option } from "../_lib";
|
||||||
|
|
||||||
|
type Row = { id: number | string; name?: string | null };
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
const q = new URL(req.url).searchParams.get("q");
|
||||||
|
const { data } = await dFetchJSON<{ data: Row[] }>(req, "/items/material_color?fields=id,name&limit=1000&sort=name");
|
||||||
|
const options: Option[] = data.map((r) => ({ id: r.id, label: r.name ?? String(r.id) }));
|
||||||
|
return json(applyQFilter(options, q, (o) => o.label));
|
||||||
|
}
|
||||||
|
|
@ -1,36 +1,12 @@
|
||||||
// app/api/options/material_opacity/route.ts
|
import { NextRequest } from "next/server";
|
||||||
export const dynamic = "force-dynamic";
|
import { dFetchJSON, applyQFilter, json, Option } from "../_lib";
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
type Row = { id: number | string; opacity?: string | null };
|
||||||
const BASE = (process.env.DIRECTUS_URL || "").replace(/\/$/, "");
|
|
||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
try {
|
const q = new URL(req.url).searchParams.get("q");
|
||||||
const userAt = req.cookies.get("ma_at")?.value;
|
// Ensure role can read id,opacity on this collection.
|
||||||
if (!userAt) return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
|
const { data } = await dFetchJSON<{ data: Row[] }>(req, "/items/material_opacity?fields=id,opacity&limit=1000&sort=opacity");
|
||||||
|
const options: Option[] = data.map((r) => ({ id: r.id, label: r.opacity ?? String(r.id) }));
|
||||||
const url = new URL(`${BASE}/items/material_opacity`);
|
return json(applyQFilter(options, q, (o) => o.label));
|
||||||
url.searchParams.set("fields", "id,opacity");
|
|
||||||
url.searchParams.set("sort", "opacity");
|
|
||||||
|
|
||||||
const res = await fetch(String(url), {
|
|
||||||
headers: { Accept: "application/json", Authorization: `Bearer ${userAt}` },
|
|
||||||
cache: "no-store",
|
|
||||||
});
|
|
||||||
|
|
||||||
const text = await res.text().catch(() => "");
|
|
||||||
const json = text ? JSON.parse(text) : {};
|
|
||||||
if (!res.ok) {
|
|
||||||
return NextResponse.json({ error: `Directus ${res.status}: ${text || res.statusText}` }, { status: res.status });
|
|
||||||
}
|
|
||||||
|
|
||||||
const rows: Array<{ id: number | string; opacity?: string | number }> = json?.data ?? [];
|
|
||||||
const data = rows
|
|
||||||
.map(({ id, opacity }) => ({ id, label: String(opacity ?? "") }))
|
|
||||||
.filter((x) => x.label);
|
|
||||||
|
|
||||||
return NextResponse.json({ data });
|
|
||||||
} catch (e: any) {
|
|
||||||
return NextResponse.json({ error: e?.message || "Failed to load opacity options" }, { status: 500 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
app/api/user/me/route.ts
Normal file
7
app/api/user/me/route.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { dFetchJSON } from "../../options/_lib";
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
const body = await dFetchJSON(req, "/users/me?fields=id,username,display_name,first_name,last_name,email");
|
||||||
|
return NextResponse.json(body, { headers: { "cache-control": "no-store" } });
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue