feat(api): add /quote_items/[id]/item-history endpoint
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
d2f6a0cd0b
commit
e5a9221c3e
1 changed files with 85 additions and 0 deletions
85
src/app/api/v1/quote_items/[id]/item-history/route.ts
Normal file
85
src/app/api/v1/quote_items/[id]/item-history/route.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getAdminPocketBase } from "@/lib/server/pocketbase-admin";
|
||||
import { getItemHistory } from "@/lib/server/item-history-service";
|
||||
import type { ItemHistoryResponse } from "@/lib/types";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* GET /api/v1/quote_items/[id]/item-history
|
||||
*
|
||||
* Composes deterministic PB joins (via `getItemHistory`) with the
|
||||
* existing n8n-written `ai_item_insights` blob into one response.
|
||||
*
|
||||
* The `ITEM_HISTORY_COMPARE_MODE` env var controls which side runs:
|
||||
* - `both` (default) — both sides, side-by-side render.
|
||||
* - `deterministic` — PB joins only; skip the AI read.
|
||||
* - `ai` — AI blob only; skip the service call.
|
||||
* - `none` — 404; trigger hides itself.
|
||||
*
|
||||
* Auth: same as the rest of `/api/v1` (no Bearer required, uses admin PB).
|
||||
*/
|
||||
|
||||
type Mode = "both" | "deterministic" | "ai" | "none";
|
||||
|
||||
function readMode(): Mode {
|
||||
const raw = process.env.ITEM_HISTORY_COMPARE_MODE?.toLowerCase();
|
||||
if (raw === "deterministic" || raw === "ai" || raw === "none") return raw;
|
||||
return "both";
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
_req: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
const { id } = await params;
|
||||
if (!id || typeof id !== "string") {
|
||||
return NextResponse.json({ error: "Missing id" }, { status: 400 });
|
||||
}
|
||||
|
||||
const mode = readMode();
|
||||
if (mode === "none") {
|
||||
return NextResponse.json({ error: "Item history disabled" }, { status: 404 });
|
||||
}
|
||||
|
||||
const generated_at = new Date().toISOString();
|
||||
const response: ItemHistoryResponse = {
|
||||
deterministic: null,
|
||||
ai: null,
|
||||
ai_updated_at: null,
|
||||
generated_at,
|
||||
};
|
||||
|
||||
// Run the deterministic service unless mode === "ai".
|
||||
if (mode === "both" || mode === "deterministic") {
|
||||
try {
|
||||
response.deterministic = await getItemHistory(id);
|
||||
} catch (err) {
|
||||
response.error = err instanceof Error ? err.message : "Deterministic lookup failed";
|
||||
}
|
||||
}
|
||||
|
||||
// Read the AI blob unless mode === "deterministic".
|
||||
if (mode === "both" || mode === "ai") {
|
||||
try {
|
||||
const pb = await getAdminPocketBase();
|
||||
const row = await pb.collection("quote_items").getOne<{
|
||||
ai_item_insights?: string | null;
|
||||
updated?: string;
|
||||
}>(id, { fields: "ai_item_insights,updated" });
|
||||
if (row.ai_item_insights) {
|
||||
try {
|
||||
response.ai = JSON.parse(row.ai_item_insights);
|
||||
response.ai_updated_at = row.updated || null;
|
||||
} catch {
|
||||
// Malformed JSON — leave `ai` null and continue.
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Silent: deterministic side still works.
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json(response);
|
||||
}
|
||||
Loading…
Reference in a new issue