Initial commit for homaroma

This commit is contained in:
Your Name committed 2026-06-03 08:39:12 +07:00
commit f2179cfe57
269 files changed
+22635

No files matched your search

+186
View File
@@ -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"],
},
}
);