Spaces:
Runtime error
Runtime error
| import { NextResponse } from "next/server"; | |
| import { getCombos, createCombo, getComboByName, isCloudEnabled } from "@/lib/localDb"; | |
| import { getConsistentMachineId } from "@/shared/utils/machineId"; | |
| import { syncToCloud } from "@/lib/cloudSync"; | |
| import { validateCompositeTiersConfig } from "@/lib/combos/compositeTiers"; | |
| import { normalizeComboModels } from "@/lib/combos/steps"; | |
| import { validateComboDAG, clampComboDepth } from "@omniroute/open-sse/services/combo.ts"; | |
| import { createComboSchema } from "@/shared/validation/schemas"; | |
| import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; | |
| import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; | |
| import { comboErrorResponse } from "@/lib/api/comboErrorResponse"; | |
| // GET /api/combos - Get all combos | |
| export async function GET(request: Request) { | |
| const authError = await requireManagementAuth(request); | |
| if (authError) return authError; | |
| try { | |
| const combos = await getCombos(); | |
| return NextResponse.json({ combos }); | |
| } catch (error) { | |
| console.log("Error fetching combos:", error); | |
| return NextResponse.json({ error: "Failed to fetch combos" }, { status: 500 }); | |
| } | |
| } | |
| // POST /api/combos - Create new combo | |
| export async function POST(request) { | |
| const authError = await requireManagementAuth(request); | |
| if (authError) return authError; | |
| try { | |
| const body = await request.json(); | |
| // Zod validation (covers name format, length, etc.) | |
| const validation = validateBody(createComboSchema, body); | |
| if (isValidationFailure(validation)) { | |
| return NextResponse.json({ error: validation.error }, { status: 400 }); | |
| } | |
| const allCombos = await getCombos(); | |
| const normalizedModels = normalizeComboModels(validation.data.models, { | |
| comboName: validation.data.name, | |
| // `allCombos` from `getCombos()` is typed as the DB-shaped record | |
| // (JsonRecord & { version: 2; models: ComboStep[] }) which is | |
| // structurally compatible with the local ComboCollectionLike in | |
| // `normalizeComboModels` but TS does not infer the relationship. | |
| allCombos: allCombos as never, | |
| }); | |
| const comboInput = { | |
| ...validation.data, | |
| models: normalizedModels, | |
| }; | |
| const { name, strategy, config } = comboInput; | |
| const compositeValidation = validateCompositeTiersConfig(comboInput); | |
| if (compositeValidation.success === false) { | |
| const failure = compositeValidation as { | |
| success: false; | |
| error: { message: string; details: unknown[] }; | |
| }; | |
| return comboErrorResponse( | |
| "COMBO_003", | |
| 400, | |
| { reason: failure.error.message, details: failure.error.details }, | |
| request | |
| ); | |
| } | |
| // Check if name already exists | |
| const existing = await getComboByName(name); | |
| if (existing) { | |
| return NextResponse.json({ error: "Combo name already exists" }, { status: 400 }); | |
| } | |
| // Validate nested combo DAG (no circular references, max depth) | |
| // Temporarily add the new combo to validate its graph | |
| const tempCombo = { | |
| ...comboInput, | |
| name, | |
| strategy, | |
| config, | |
| }; | |
| try { | |
| validateComboDAG( | |
| name, | |
| [...allCombos, tempCombo], | |
| new Set(), | |
| 0, | |
| clampComboDepth((config as { maxComboDepth?: unknown } | undefined)?.maxComboDepth) | |
| ); | |
| } catch (dagError) { | |
| return NextResponse.json({ error: dagError.message }, { status: 400 }); | |
| } | |
| const combo = await createCombo(comboInput); | |
| // Auto sync to Cloud if enabled | |
| await syncToCloudIfEnabled(); | |
| return NextResponse.json(combo, { status: 201 }); | |
| } catch (error) { | |
| console.log("Error creating combo:", error); | |
| return NextResponse.json({ error: "Failed to create combo" }, { status: 500 }); | |
| } | |
| } | |
| /** | |
| * Sync to Cloud if enabled | |
| */ | |
| async function syncToCloudIfEnabled() { | |
| try { | |
| const cloudEnabled = await isCloudEnabled(); | |
| if (!cloudEnabled) return; | |
| const machineId = await getConsistentMachineId(); | |
| await syncToCloud(machineId); | |
| } catch (error) { | |
| console.log("Error syncing to cloud:", error); | |
| } | |
| } | |