feat: update Directus API routes, add health endpoint
This commit is contained in:
parent
78f8d225ee
commit
79f0af51eb
7 changed files with 771 additions and 807 deletions
|
|
@ -1,10 +1,8 @@
|
|||
// app/api/options/[collection]/route.ts
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { NextResponse } from "next/server";
|
||||
import { directusFetch } from "@/lib/directus";
|
||||
|
||||
const NM_FIELD = "nm"; // wavelength field in laser_source
|
||||
|
||||
// Parse wavelength that might be stored as "1064", "1064nm", "1,064", etc.
|
||||
// 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;
|
||||
|
|
@ -12,7 +10,7 @@ function parseNm(v: any): number | null {
|
|||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
// Target → wavelength range (nm)
|
||||
// target → wavelength range (nm)
|
||||
function nmRangeForTarget(t?: string): [number, number] | null {
|
||||
switch (t) {
|
||||
case "settings_fiber": return [1000, 1100];
|
||||
|
|
@ -23,89 +21,71 @@ function nmRangeForTarget(t?: string): [number, number] | null {
|
|||
}
|
||||
}
|
||||
|
||||
// Generic lookups (request only fields we know exist)
|
||||
const GENERIC: Record<
|
||||
string,
|
||||
{ path: string; fields: string[]; label: (x: any) => string }
|
||||
> = {
|
||||
material: { path: "/items/material", fields: ["id", "name"], label: (x) => x.name ?? String(x.id) },
|
||||
material_coating: { path: "/items/material_coating", fields: ["id", "name"], label: (x) => x.name ?? String(x.id) },
|
||||
material_color: { path: "/items/material_color", fields: ["id", "name"], label: (x) => x.name ?? String(x.id) },
|
||||
material_opacity: { path: "/items/material_opacity", fields: ["id", "opacity"], label: (x) => String(x.opacity ?? x.id) },
|
||||
laser_software: { path: "/items/laser_software", fields: ["id", "name"], label: (x) => x.name ?? String(x.id) },
|
||||
// generic collections we expose here
|
||||
const GENERIC_MAP: Record<string, { coll: string }> = {
|
||||
material: { coll: "material" },
|
||||
material_coating: { coll: "material_coating" },
|
||||
material_color: { coll: "material_color" },
|
||||
material_opacity: { coll: "material_opacity" },
|
||||
laser_software: { coll: "laser_software" },
|
||||
};
|
||||
|
||||
async function fetchDirectus<T>(pathname: string, params: URLSearchParams) {
|
||||
return directusFetch<T>(`${pathname}?${params.toString()}`);
|
||||
function bestLabel(it: any): string {
|
||||
// prefer common fields; fall back gracefully
|
||||
const cand = [it.name, it.label, it.title, it.value, it.opacity, it.color, it.coating];
|
||||
const label = cand.find((x) => x != null && String(x).trim().length) ?? it.id ?? it.submission_id;
|
||||
return String(label);
|
||||
}
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
export async function GET(req: Request, ctx: { params: { collection: string } }) {
|
||||
try {
|
||||
const url = new URL(req.url);
|
||||
const collection = url.pathname.split("/").pop() || "";
|
||||
const q = url.searchParams.get("q")?.trim() || "";
|
||||
const limit = Number(url.searchParams.get("limit") || "400");
|
||||
const target = url.searchParams.get("target") || undefined;
|
||||
const { searchParams } = new URL(req.url);
|
||||
const q = searchParams.get("q")?.trim() || "";
|
||||
const limit = Number(searchParams.get("limit") || "500");
|
||||
const target = searchParams.get("target") || undefined;
|
||||
|
||||
// ----- generic tables -----
|
||||
const gen = GENERIC[collection];
|
||||
if (gen) {
|
||||
const params = new URLSearchParams();
|
||||
params.set("fields", gen.fields.join(","));
|
||||
params.set("limit", String(limit));
|
||||
if (q) params.set("search", q);
|
||||
const key = ctx.params.collection;
|
||||
|
||||
const { data } = await fetchDirectus<{ data: any[] }>(gen.path, params);
|
||||
const out = (data ?? [])
|
||||
.map((x) => ({ id: String(x.id), label: gen.label(x) }))
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
return NextResponse.json({ data: out });
|
||||
}
|
||||
|
||||
// ----- laser_source (uses submission_id as the key) -----
|
||||
if (collection === "laser_source") {
|
||||
// laser_source: wavelength filtered by target, and pk may be 'submission_id'
|
||||
if (key === "laser_source") {
|
||||
const range = nmRangeForTarget(target);
|
||||
if (!range) {
|
||||
return NextResponse.json(
|
||||
{ error: "missing/invalid target for laser_source" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const params = new URLSearchParams();
|
||||
// IMPORTANT: request submission_id instead of id
|
||||
params.set("fields", ["submission_id", "make", "model", NM_FIELD].join(","));
|
||||
params.set("limit", String(limit));
|
||||
if (q) params.set("search", q);
|
||||
|
||||
const { data } = await fetchDirectus<{ data: any[] }>("/items/laser_source", params);
|
||||
const rows = data ?? [];
|
||||
if (!range) return NextResponse.json({ error: "missing/invalid target for laser_source" }, { status: 400 });
|
||||
|
||||
const url = `/items/laser_source?limit=${limit}${q ? `&search=${encodeURIComponent(q)}` : ""}`;
|
||||
const { data } = await directusFetch<{ data: any[] }>(url);
|
||||
const [lo, hi] = range;
|
||||
const filtered = rows.filter((x) => {
|
||||
const nm = parseNm(x[NM_FIELD]);
|
||||
|
||||
const filtered = (data || []).filter((x) => {
|
||||
const nm = parseNm(x?.nm);
|
||||
return nm !== null && nm >= lo && nm <= hi;
|
||||
});
|
||||
|
||||
const out = filtered
|
||||
.map((x) => ({
|
||||
id: String(x.submission_id), // <- use submission_id
|
||||
label: [x.make, x.model].filter(Boolean).join(" ").trim() || String(x.submission_id),
|
||||
sortKey: [(x.make ?? "").toLowerCase(), (x.model ?? "").toLowerCase()].join(" "),
|
||||
}))
|
||||
.filter((o) => o.id)
|
||||
.sort((a, b) => a.sortKey.localeCompare(b.sortKey))
|
||||
.map(({ id, label }) => ({ id, label }));
|
||||
.map((x) => {
|
||||
const id = String(x.submission_id ?? x.id);
|
||||
const label = [x.make, x.model].filter(Boolean).join(" ").trim() || id;
|
||||
const sortKey = [x.make ?? "", x.model ?? ""].join(" ").toLowerCase();
|
||||
return { id, label, sortKey };
|
||||
})
|
||||
.sort((a, b) => a.sortKey.localeCompare(b.sortKey))
|
||||
.map(({ id, label }) => ({ id, label }));
|
||||
|
||||
return NextResponse.json({ data: out });
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: "unsupported collection" }, { status: 400 });
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || "Unknown error" },
|
||||
{ status: 500 }
|
||||
);
|
||||
// generic collections
|
||||
const cfg = GENERIC_MAP[key];
|
||||
if (!cfg) return NextResponse.json({ error: "unsupported collection" }, { status: 400 });
|
||||
|
||||
const url = `/items/${cfg.coll}?limit=${limit}${q ? `&search=${encodeURIComponent(q)}` : ""}`;
|
||||
const { data } = await directusFetch<{ data: any[] }>(url);
|
||||
const mapped = (data || []).map((it) => ({
|
||||
id: String(it.submission_id ?? it.id),
|
||||
label: bestLabel(it),
|
||||
}));
|
||||
mapped.sort((a, b) => a.label.localeCompare(b.label));
|
||||
return NextResponse.json({ data: mapped });
|
||||
} catch (e: any) {
|
||||
return NextResponse.json({ error: e?.message || "options error" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,39 +2,53 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import { directusFetch } from "@/lib/directus";
|
||||
|
||||
/** pick a decent label from whatever fields are readable */
|
||||
function pickLabel(it: any) {
|
||||
const mm = [it?.make, it?.model].filter(Boolean).join(" ").trim();
|
||||
if (mm) return mm;
|
||||
if (it?.name) return String(it.name);
|
||||
const f = it?.focal_length ?? it?.f ?? it?.fl;
|
||||
if (f != null) return `${mm ? mm + " " : ""}${f} mm`.trim();
|
||||
return String(it?.label ?? it?.title ?? it?.id ?? "");
|
||||
/**
|
||||
* For fiber, co2-galvo, uv → f-theta scan lenses
|
||||
* For co2-gantry → focusing lenses
|
||||
*/
|
||||
function collectionForTarget(target?: string): { coll: string } | null {
|
||||
switch (target) {
|
||||
case "settings_fiber":
|
||||
case "settings_co2gal":
|
||||
case "settings_uv":
|
||||
return { coll: "laser_scan_lens" };
|
||||
case "settings_co2gan":
|
||||
return { coll: "laser_focusing_lens" };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function bestLensLabel(it: any): string {
|
||||
// Try common fields, then derive something readable
|
||||
if (it.name) return String(it.name);
|
||||
if (it.model) return String(it.model);
|
||||
if (it.focal_length_mm) return `F${it.focal_length_mm} mm`;
|
||||
return String(it.submission_id ?? it.id ?? "lens");
|
||||
}
|
||||
|
||||
export async function GET(req: Request) {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const target = searchParams.get("target") || ""; // required
|
||||
const q = (searchParams.get("q") || "").toLowerCase();
|
||||
const limit = Number(searchParams.get("limit") || "500");
|
||||
try {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const q = searchParams.get("q")?.trim() || "";
|
||||
const limit = Number(searchParams.get("limit") || "500");
|
||||
const target = searchParams.get("target") || undefined;
|
||||
|
||||
// Fiber / CO2 Galvo / UV -> scan lens ; CO2 Gantry -> focus lens
|
||||
const isGantry = target === "settings_co2gan";
|
||||
const coll = isGantry ? "laser_focus_lens" : "laser_scan_lens";
|
||||
const cfg = collectionForTarget(target);
|
||||
if (!cfg) return NextResponse.json({ error: "missing/invalid target" }, { status: 400 });
|
||||
|
||||
// Avoid explicit fields -> prevents 403 on disallowed fields
|
||||
const res = await directusFetch<{ data: any[] }>(`/items/${coll}?limit=${limit}`);
|
||||
let items = res?.data ?? [];
|
||||
const url = `/items/${cfg.coll}?limit=${limit}${q ? `&search=${encodeURIComponent(q)}` : ""}`;
|
||||
const { data } = await directusFetch<{ data: any[] }>(url);
|
||||
|
||||
let rows = items.map((it) => {
|
||||
const label = pickLabel(it);
|
||||
const search = Object.values(it ?? {}).join(" ").toLowerCase();
|
||||
return { id: String(it?.id ?? ""), label, _search: search };
|
||||
}).filter((r) => r.id);
|
||||
const out = (data || [])
|
||||
.map((it) => ({
|
||||
id: String(it.submission_id ?? it.id),
|
||||
label: bestLensLabel(it),
|
||||
}))
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
if (q) rows = rows.filter((r) => r._search.includes(q));
|
||||
rows.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
return NextResponse.json({ data: rows.map(({ _search, ...r }) => r) });
|
||||
return NextResponse.json({ data: out });
|
||||
} catch (e: any) {
|
||||
return NextResponse.json({ error: e?.message || "lens error" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue