Files
andris-carservice/src/app/api/parts/route.ts
T
Hermes f7a9906add
Build and publish images / docker (push) Canceled after 0s
carservice: initial commit
2026-07-20 20:51:23 +01:00

138 lines
3.9 KiB
TypeScript

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);
}