Initial commit for homaroma
This commit is contained in:
commit
f2179cfe57
269 files changed
+22635
No files matched your search
@@ -0,0 +1,186 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import prisma from "../lib/prisma";
|
||||
|
||||
export const productsRoute = new Elysia({ prefix: "/products" })
|
||||
.get("/", async ({ query }) => {
|
||||
const categoryId = query.categoryId ? parseInt(query.categoryId) : undefined;
|
||||
const scentId = query.scentId ? parseInt(query.scentId) : undefined;
|
||||
const search = query.search?.trim() || undefined;
|
||||
|
||||
// Pagination params
|
||||
const page = query.page ? parseInt(query.page) : 1;
|
||||
const limit = query.limit ? parseInt(query.limit) : 12;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const where = {
|
||||
...(categoryId && { categoryId }),
|
||||
...(scentId && { scentId }),
|
||||
...(search && {
|
||||
OR: [
|
||||
{ name: { contains: search, mode: "insensitive" as const } },
|
||||
{ scent: { name: { contains: search, mode: "insensitive" as const } } },
|
||||
{ category: { name: { contains: search, mode: "insensitive" as const } } },
|
||||
],
|
||||
}),
|
||||
} as const;
|
||||
|
||||
const [products, total] = await Promise.all([
|
||||
prisma.product.findMany({
|
||||
where,
|
||||
include: {
|
||||
category: true,
|
||||
scent: true,
|
||||
variants: {
|
||||
include: {
|
||||
bomItems: { include: { rawMaterial: true } }
|
||||
},
|
||||
orderBy: { price: "asc" }
|
||||
},
|
||||
images: { orderBy: { order: "asc" } },
|
||||
},
|
||||
orderBy: { name: "asc" },
|
||||
skip,
|
||||
take: limit,
|
||||
}),
|
||||
prisma.product.count({ where }),
|
||||
]);
|
||||
|
||||
console.log(`Fetch products paginated: count=${products.length}, total=${total}`)
|
||||
if (products.length > 0) {
|
||||
console.log(`Sample product ${products[0].id} images count: ${products[0].images?.length || 0}`)
|
||||
}
|
||||
|
||||
return {
|
||||
data: products,
|
||||
meta: {
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
lastPage: Math.ceil(total / limit),
|
||||
},
|
||||
};
|
||||
})
|
||||
.get("/:id", async ({ params, status }) => {
|
||||
const id = parseInt(params.id);
|
||||
if (isNaN(id)) return status(400, { message: "Invalid product ID" });
|
||||
|
||||
const product = await prisma.product.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
category: true,
|
||||
scent: true,
|
||||
variants: {
|
||||
include: {
|
||||
bomItems: { include: { rawMaterial: true } }
|
||||
},
|
||||
orderBy: { price: "asc" }
|
||||
},
|
||||
images: { orderBy: { order: "asc" } },
|
||||
},
|
||||
});
|
||||
|
||||
if (!product) return status(404, { message: "Product not found" });
|
||||
return product;
|
||||
})
|
||||
.put(
|
||||
"/:id",
|
||||
async ({ params, body, status }) => {
|
||||
const id = parseInt(params.id);
|
||||
if (isNaN(id)) return status(400, { message: "Invalid product ID" });
|
||||
|
||||
const { categoryId, description, details, variants, images } = body;
|
||||
console.log(`[Product Update] ID=${id}, Category=${categoryId}, Variants=${variants?.length || 0}, Images=${images?.length || 0}`);
|
||||
|
||||
try {
|
||||
const updatedProduct = await prisma.$transaction(async (tx) => {
|
||||
// 1. Update basic product info
|
||||
const prod = await tx.product.update({
|
||||
where: { id },
|
||||
data: { categoryId, description, details },
|
||||
});
|
||||
|
||||
// 2. Sync Variants
|
||||
await tx.productVariant.deleteMany({
|
||||
where: { productId: id },
|
||||
});
|
||||
|
||||
if (variants && variants.length > 0) {
|
||||
for (const v of variants) {
|
||||
await tx.productVariant.create({
|
||||
data: {
|
||||
productId: id,
|
||||
size: v.size,
|
||||
price: v.price,
|
||||
bomItems: v.bomItems && v.bomItems.length > 0 ? {
|
||||
create: v.bomItems.map((b: any) => ({
|
||||
rawMaterialId: b.rawMaterialId,
|
||||
quantity: b.quantity,
|
||||
}))
|
||||
} : undefined
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Sync Images
|
||||
await tx.productImage.deleteMany({
|
||||
where: { productId: id },
|
||||
});
|
||||
|
||||
if (images && images.length > 0) {
|
||||
// Using a loop instead of createMany to be more robust with Postgres sequence/ID management
|
||||
// and avoid unique constraint collisions if IDs were somehow kept in body
|
||||
for (const [idx, img] of images.entries()) {
|
||||
await tx.productImage.create({
|
||||
data: {
|
||||
productId: id,
|
||||
url: img.url,
|
||||
order: img.order ?? (typeof img.order === 'number' ? img.order : idx),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return prod;
|
||||
});
|
||||
|
||||
return { success: true, product: updatedProduct };
|
||||
} catch (err: any) {
|
||||
console.error("Update product error:", err);
|
||||
return status(500, { success: false, message: "Failed to update product" });
|
||||
}
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
categoryId: t.Number(),
|
||||
description: t.Optional(t.String()),
|
||||
details: t.Optional(t.String()),
|
||||
variants: t.Array(
|
||||
t.Object({
|
||||
size: t.String(),
|
||||
price: t.Number(),
|
||||
bomItems: t.Optional(
|
||||
t.Array(
|
||||
t.Object({
|
||||
rawMaterialId: t.Number(),
|
||||
quantity: t.Number(),
|
||||
})
|
||||
)
|
||||
),
|
||||
})
|
||||
),
|
||||
images: t.Optional(
|
||||
t.Array(
|
||||
t.Object({
|
||||
url: t.String(),
|
||||
order: t.Optional(t.Number()),
|
||||
})
|
||||
)
|
||||
),
|
||||
}),
|
||||
detail: {
|
||||
summary: "Update Product",
|
||||
tags: ["products"],
|
||||
},
|
||||
}
|
||||
);
|
||||
Reference in new issue
Block a user