import { NextResponse } from "next/server"; import { z } from "zod"; import { LocationKind } from "@prisma/client"; import { db } from "@/lib/db"; import { requireAuth, isAdmin } from "@/lib/auth"; import { recomputeAllPaths, wouldCreateCycle } from "@/lib/locations/db"; const updateSchema = z.object({ name: z.string().min(1).optional(), kind: z.nativeEnum(LocationKind).optional(), parentId: z.string().nullable().optional(), // null → move to top level notes: z.string().nullable().optional(), }); export async function PATCH(req: Request, { params }: { params: { id: string } }) { try { const session = await requireAuth(); const location = await db.location.findUnique({ where: { id: params.id } }); if (!location || !location.active) return NextResponse.json({ error: "Not found" }, { status: 404 }); const data = updateSchema.parse(await req.json()); // Guard against cycles when re-parenting. if (data.parentId) { if (await wouldCreateCycle(params.id, data.parentId)) return NextResponse.json( { error: "A location can't be moved under itself or its own descendant" }, { status: 400 }, ); const parent = await db.location.findUnique({ where: { id: data.parentId } }); if (!parent) return NextResponse.json({ error: "Parent location not found" }, { status: 400 }); } const updated = await db.$transaction(async (tx) => { const u = await tx.location.update({ where: { id: params.id }, data: { ...(data.name !== undefined ? { name: data.name.trim() } : {}), ...(data.kind !== undefined ? { kind: data.kind } : {}), ...(data.parentId !== undefined ? { parentId: data.parentId } : {}), ...(data.notes !== undefined ? { notes: data.notes?.trim() || null } : {}), }, }); await recomputeAllPaths(tx as unknown as typeof db); await tx.auditEvent.create({ data: { action: "location.update", entityId: params.id, actorId: session.user.id, payload: { name: u.name }, }, }); return u; }); return NextResponse.json(updated); } catch (err) { if (err instanceof z.ZodError) return NextResponse.json({ error: err.issues[0]?.message ?? "Invalid data" }, { status: 400 }); if (err instanceof Error && err.message === "Unauthorized") return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); console.error(err); return NextResponse.json({ error: "Server error" }, { status: 500 }); } } export async function DELETE(_req: Request, { params }: { params: { id: string } }) { try { const session = await requireAuth(); if (!isAdmin(session.user.role)) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); const location = await db.location.findUnique({ where: { id: params.id } }); if (!location || !location.active) return NextResponse.json({ error: "Not found" }, { status: 404 }); // Don't orphan live records — require the location be emptied first. const [plantCount, childCount] = await Promise.all([ db.plant.count({ where: { locationId: params.id, active: true } }), db.location.count({ where: { parentId: params.id, active: true } }), ]); if (plantCount > 0 || childCount > 0) return NextResponse.json( { error: "Move the plants and sub-locations out of this location first" }, { status: 400 }, ); await db.location.update({ where: { id: params.id }, data: { active: false } }); await db.auditEvent.create({ data: { action: "location.delete", entityId: params.id, actorId: session.user.id, payload: { name: location.name }, }, }); return NextResponse.json({ ok: true }); } catch (err) { if (err instanceof Error && err.message === "Unauthorized") return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); console.error(err); return NextResponse.json({ error: "Server error" }, { status: 500 }); } }