From 11658bce2df0c15fcc17c6d65e41eb21160537ae Mon Sep 17 00:00:00 2001 From: DEls Date: Fri, 28 Aug 2026 13:01:58 +0200 Subject: [PATCH] feat(api): POST /api/sites/[id]/fixed-costs --- src/app/api/sites/[id]/fixed-costs/route.ts | 66 +++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/app/api/sites/[id]/fixed-costs/route.ts diff --git a/src/app/api/sites/[id]/fixed-costs/route.ts b/src/app/api/sites/[id]/fixed-costs/route.ts new file mode 100644 index 0000000..715d799 --- /dev/null +++ b/src/app/api/sites/[id]/fixed-costs/route.ts @@ -0,0 +1,66 @@ +import { NextResponse } from "next/server"; +import { pbAdmin } from "@/lib/pb"; + +const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/; + +function badRequest(field: string, message: string) { + return NextResponse.json({ error: "validation", field, message }, { status: 400 }); +} + +export async function POST( + request: Request, + { params }: { params: Promise<{ id: string }> }, +) { + const { id } = await params; + + let body: Record; + try { + body = await request.json(); + } catch { + return NextResponse.json({ error: "Body must be JSON" }, { status: 400 }); + } + + const description = typeof body.description === "string" ? body.description.trim() : ""; + if (!description) return badRequest("description", "Description is required."); + if (description.length > 200) return badRequest("description", "Description must be 200 characters or fewer."); + + const costNum = typeof body.cost === "number" ? body.cost : Number(body.cost); + if (!Number.isFinite(costNum) || costNum <= 0) { + return badRequest("cost", "Cost must be greater than zero."); + } + + const dateLogged = typeof body.date_logged === "string" ? body.date_logged : ""; + if (!ISO_DATE.test(dateLogged)) { + return badRequest("date_logged", "Date must be in YYYY-MM-DD format."); + } + + if (typeof body.is_recurring !== "boolean") { + return badRequest("is_recurring", "is_recurring is required and must be a boolean."); + } + + const notes = typeof body.notes === "string" ? body.notes.slice(0, 500) : ""; + + const pb = await pbAdmin(); + let site; + try { + site = await pb.collection("sites").getOne(id); + } catch { + return NextResponse.json({ error: "site_not_found" }, { status: 404 }); + } + + try { + const created = await pb.collection("fixed_costs").create({ + site: id, + project_code: site.project_code, + description, + cost: costNum, + date_logged: dateLogged, + notes, + is_recurring: body.is_recurring, + }); + return NextResponse.json({ fixed_cost: created }, { status: 201 }); + } catch (err) { + console.error("[POST /api/sites/[id]/fixed-costs] PB write failed", err); + return NextResponse.json({ error: "write_failed" }, { status: 500 }); + } +} \ No newline at end of file