import { prisma } from "@/lib/prisma"; import { errorResponse, getJsonBody, jsonResponse } from "@/lib/api-helpers"; import { scrapePartInfo } from "@/lib/scrape"; export async function GET(req: Request) { const { searchParams } = new URL(req.url); const q = searchParams.get("q")?.trim() ?? ""; const carId = searchParams.get("carId"); const includeAll = searchParams.get("all") === "true"; const includeGlobal = searchParams.get("includeGlobal") === "true"; const term = q.toLowerCase(); const cid = carId ? Number(carId) : undefined; const parts = await prisma.part.findMany({ where: { AND: [ q ? { OR: [ { name: { contains: term } }, { manufacturer: { contains: term } }, ], } : {}, carId ? includeGlobal ? { OR: [{ carId: cid }, { isGlobal: true }] } : { carId: cid } : includeAll ? {} : { isGlobal: true }, ], }, include: { car: true }, orderBy: { updatedAt: "desc" }, take: 100, }); return jsonResponse(parts); } export async function POST(req: Request) { const body = await getJsonBody(req); if (!body) return errorResponse("Invalid JSON"); const { name, manufacturer, supplierLink, url, purchasePrice, notes, carId, orderId, isGlobal, } = body; if (!name) return errorResponse("name is required"); const normalizedName = name.trim(); const carIdNum = carId ? Number(carId) : null; // Prevent duplicate part names per car and avoid global/private collisions. if (carIdNum) { const duplicate = await prisma.part.findFirst({ where: { name: { equals: normalizedName }, OR: [{ carId: carIdNum }, { isGlobal: true }], }, }); if (duplicate && duplicate.name.toLowerCase() === normalizedName.toLowerCase()) { return errorResponse( `A part named "${duplicate.name}" already exists for this car or globally.`, 409, ); } } const part = await prisma.part.create({ data: { name: normalizedName, manufacturer, supplierLink, url, purchasePrice: Number(purchasePrice) || 0, notes: notes || "", carId: carIdNum, orderId: orderId ? Number(orderId) : null, // Private by default. User must explicitly mark global. isGlobal: isGlobal === true || isGlobal === "true", }, include: { car: true }, }); return jsonResponse(part, 201); } export async function PATCH(req: Request) { const body = await getJsonBody(req); if (!body) return errorResponse("Invalid JSON"); const { id, ...data } = body; if (!id) return errorResponse("id is required"); if (data.purchasePrice !== undefined) data.purchasePrice = Number(data.purchasePrice) || 0; if (data.sellPrice !== undefined) data.sellPrice = Number(data.sellPrice) || 0; if (data.qty !== undefined) data.qty = Number(data.qty) || 0; if (data.isGlobal !== undefined) data.isGlobal = data.isGlobal === true || data.isGlobal === "true"; if (data.name) { const target = await prisma.part.findUnique({ where: { id: Number(id) } }); if (target) { const carIdNum = data.carId !== undefined ? Number(data.carId) || null : target.carId; const duplicate = await prisma.part.findFirst({ where: { id: { not: target.id }, name: { equals: data.name.trim() }, OR: [{ carId: carIdNum }, { isGlobal: true }], }, }); if ( duplicate && duplicate.name.toLowerCase() === data.name.trim().toLowerCase() ) { return errorResponse( `A part named "${duplicate.name}" already exists for this car or globally.`, 409, ); } } } const updated = await prisma.part.update({ where: { id: Number(id) }, data, include: { car: true }, }); return jsonResponse(updated); }