import { NextResponse } from "next/server"; import { z } from "zod"; import { ItemSource } from "@prisma/client"; import { db } from "@/lib/db"; import { requireAuth } from "@/lib/auth"; const PANTRY_SELECT = { id: true, name: true, description: true, quantity: true, unit: true, storedAt: true, bestBefore: true, source: true, plantId: true, notes: true, pantryCategory: true, pantrySubcategory: true, pricePaid: true, priceUnit: true, location: { select: { id: true, name: true } }, plant: { select: { id: true, commonName: true, variety: true } }, labels: { select: { label: { select: { id: true, name: true } } } }, } as const; function serialize(i: any) { return { id: i.id, name: i.name, description: i.description, quantity: Number(i.quantity), unit: i.unit, packageSize: i.description, storedAt: i.storedAt, bestBefore: i.bestBefore, source: i.source, notes: i.notes, pantryCategory: i.pantryCategory, pantrySubcategory: i.pantrySubcategory, pricePaid: i.pricePaid != null ? Number(i.pricePaid) : null, priceUnit: i.priceUnit, locationId: i.location?.id ?? null, locationName: i.location?.name ?? null, plantId: i.plant?.id ?? null, plantName: i.plant ? `${i.plant.commonName}${i.plant.variety ? ` (${i.plant.variety})` : ""}` : null, tags: (i.labels ?? []).map((l: any) => l.label.name) as string[], }; } async function syncTags(itemId: string, tags: string[]) { const names = Array.from(new Set(tags.map(t => t.trim()).filter(Boolean))); // Upsert each label, then replace all LabelOnItem rows for this item. const labels = await Promise.all( names.map(name => db.label.upsert({ where: { name }, update: {}, create: { name }, select: { id: true } })) ); await db.labelOnItem.deleteMany({ where: { itemId } }); if (labels.length) { await db.labelOnItem.createMany({ data: labels.map(l => ({ itemId, labelId: l.id })), skipDuplicates: true, }); } } export async function GET() { await requireAuth(); const items = await db.item.findMany({ where: { kind: "CONSUMABLE", active: true }, orderBy: [{ location: { name: "asc" } }, { name: "asc" }], select: PANTRY_SELECT, }); return NextResponse.json(items.map(serialize)); } const createSchema = z.object({ name: z.string().min(1), description: z.string().nullable().optional(), quantity: z.number().min(0).default(1), unit: z.string().nullable().optional(), packageSize: z.string().nullable().optional(), locationId: z.string().nullable().optional(), storedAt: z.string().nullable().optional(), bestBefore: z.string().nullable().optional(), source: z.nativeEnum(ItemSource).nullable().optional(), plantId: z.string().nullable().optional(), pantryCategory: z.string().nullable().optional(), pantrySubcategory: z.string().nullable().optional(), pricePaid: z.number().nullable().optional(), priceUnit: z.string().nullable().optional(), notes: z.string().nullable().optional(), tags: z.array(z.string()).optional(), }); export async function POST(req: Request) { try { const session = await requireAuth(); const data = createSchema.parse(await req.json()); const item = await db.item.create({ data: { name: data.name, description: data.description ?? data.packageSize ?? null, kind: "CONSUMABLE", quantity: data.quantity, unit: data.unit ?? null, locationId: data.locationId ?? null, storedAt: data.storedAt ? new Date(data.storedAt) : null, bestBefore: data.bestBefore ? new Date(data.bestBefore) : null, source: data.source ?? null, plantId: data.plantId ?? null, pantryCategory: data.pantryCategory ?? null, pantrySubcategory: data.pantrySubcategory ?? null, pricePaid: data.pricePaid ?? null, priceUnit: data.priceUnit ?? null, notes: data.notes ?? null, }, select: PANTRY_SELECT, }); if (data.tags?.length) await syncTags(item.id, data.tags); await db.auditEvent.create({ data: { action: "pantry.create", entityId: item.id, actorId: session.user.id, payload: { name: item.name } }, }); // Re-fetch with labels const full = await db.item.findUniqueOrThrow({ where: { id: item.id }, select: PANTRY_SELECT }); return NextResponse.json(serialize(full), { status: 201 }); } catch (err) { if (err instanceof z.ZodError) return NextResponse.json({ error: "Invalid input" }, { 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 }); } }