MEGAINFRA-WEBAPP/src/app/api/sites/route.ts

100 lines
3.1 KiB
TypeScript

import { getCurrentUser } from "@/lib/auth/session";
import { authorizeOperation } from "@/lib/auth/operation";
import { NextResponse } from "next/server";
import { pbAdmin } from "@/lib/pb";
import { parseCoordinate, type CoordinateKind } from "@/lib/site-coordinates";
/**
* POST /api/sites
*
* Creates a new site row. Required: project_code (unique). The rest
* is optional and can be backfilled later via PATCH /api/sites/[id].
*/
export async function POST(request: Request) {
const denied = await authorizeOperation(request);
if (denied) return denied;
const user = await getCurrentUser();
if (!user) return NextResponse.json({ error: "Not signed in" }, { status: 401 });
let body: Record<string, unknown>;
try {
body = await request.json();
} catch {
return NextResponse.json({ error: "Body must be JSON" }, { status: 400 });
}
const projectCode = typeof body.project_code === "string" ? body.project_code.trim() : "";
if (!projectCode) {
return NextResponse.json(
{ error: "project_code is required" },
{ status: 400 },
);
}
if (projectCode.length > 64) {
return NextResponse.json(
{ error: "project_code must be 64 characters or fewer" },
{ status: 400 },
);
}
const payload: Record<string, unknown> = { project_code: projectCode };
for (const key of [
"name",
"duration",
"contract_value",
"work_hours",
"hours_per_week",
"wages_hours_per_day",
"job_start_date",
"work_month_start_day",
]) {
if (body[key] !== undefined && body[key] !== null && body[key] !== "") {
payload[key] = body[key];
}
}
for (const key of [
"contract_value",
"work_hours",
"hours_per_week",
"wages_hours_per_day",
"work_month_start_day",
]) {
if (typeof payload[key] === "string" && payload[key] === "") {
delete payload[key];
}
}
for (const k of ["latitude", "longitude"] as const satisfies readonly CoordinateKind[]) {
if (!(k in body)) continue;
const parsed = parseCoordinate(body[k], k);
if (!parsed.ok) {
return NextResponse.json({ error: parsed.error }, { status: 400 });
}
// A blank coordinate on create is simply "not known yet".
if (parsed.value !== null) payload[k] = parsed.value;
}
const pb = await pbAdmin();
try {
const created = await pb.collection("sites").create(payload);
if (user.role !== "admin") {
try {
// Append atomically so concurrent assignments are preserved.
await pb.collection("users").update(user.id, { "sites+": [created.id] });
} catch (assignmentError) {
await pb.collection("sites").delete(created.id);
throw assignmentError;
}
}
return NextResponse.json({ site: created }, { status: 201 });
} catch (err) {
const msg = err instanceof Error ? err.message : "Insert failed";
if (/UNIQUE|duplicate|idx_sites_project_code/i.test(msg)) {
return NextResponse.json(
{ error: `project_code "${projectCode}" is already in use` },
{ status: 409 },
);
}
return NextResponse.json({ error: msg }, { status: 500 });
}
}