99 lines
3.7 KiB
TypeScript
99 lines
3.7 KiB
TypeScript
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 } },
|
|
} 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,
|
|
};
|
|
}
|
|
|
|
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(),
|
|
});
|
|
|
|
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,
|
|
});
|
|
|
|
await db.auditEvent.create({
|
|
data: { action: "pantry.create", entityId: item.id, actorId: session.user.id, payload: { name: item.name } },
|
|
});
|
|
|
|
return NextResponse.json(serialize(item), { 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 });
|
|
}
|
|
}
|