This commit is contained in:
Your Name committed 2026-06-24 19:15:04 -04:00
1 parent 81ed9af406
commit 4536db6741
56 files changed
+1127 -198

No files matched your search

+137 -27
View File
@@ -1,16 +1,16 @@
import { Elysia, t } from "elysia";
import prisma from "../lib/prisma";
export const productsRoute = new Elysia({ prefix: "/products" })
.get("/scents", async () => {
return await prisma.scent.findMany({
.get("/brands", async () => {
return await prisma.productBrand.findMany({
orderBy: { name: "asc" },
include: { translations: true }
});
})
.patch("/scents/:id/translations", async ({ params, body, status }) => {
.patch("/brands/:id/translations", async ({ params, body, status }) => {
const id = parseInt(params.id);
if (isNaN(id))
return status(400, { message: "Invalid scent ID" });
return status(400, { message: "Invalid brand ID" });
const payload = body;
const results = [];
const LANGS = ['th', 'en', 'ja', 'zh', 'vi', 'ko'];
@@ -18,32 +18,32 @@ export const productsRoute = new Elysia({ prefix: "/products" })
const name = typeof payload.name === 'string' ? (lang === 'th' ? payload.name : null) : payload.name?.[lang];
if (!name)
continue;
const existing = await prisma.scentTranslation.findUnique({
where: { scentId_langCode: { scentId: id, langCode: lang } }
const existing = await prisma.productBrandTranslation.findUnique({
where: { brandId_langCode: { brandId: id, langCode: lang } }
});
if (existing) {
const updated = await prisma.scentTranslation.update({
const updated = await prisma.productBrandTranslation.update({
where: { id: existing.id },
data: { name },
});
results.push(updated);
}
else {
const created = await prisma.scentTranslation.create({
data: { scentId: id, langCode: lang, name },
const created = await prisma.productBrandTranslation.create({
data: { brandId: id, langCode: lang, name },
});
results.push(created);
}
}
const updated = await prisma.scent.findUnique({
const updated = await prisma.productBrand.findUnique({
where: { id },
include: { translations: true },
});
return { success: true, translations: results, scent: updated };
return { success: true, translations: results, brand: updated };
})
.get("/", async ({ query }) => {
const categoryId = query.categoryId ? parseInt(query.categoryId) : undefined;
const scentId = query.scentId ? parseInt(query.scentId) : undefined;
const brandId = query.brandId ? parseInt(query.brandId) : undefined;
const search = query.search?.trim() || undefined;
const lang = query.lang || 'th';
// Pagination params
@@ -52,11 +52,11 @@ export const productsRoute = new Elysia({ prefix: "/products" })
const skip = (page - 1) * limit;
const where = {
...(categoryId && { categoryId }),
...(scentId && { scentId }),
...(brandId && { brandId }),
...(search && {
OR: [
{ translations: { some: { name: { contains: search, mode: "insensitive" } } } },
{ scent: { name: { contains: search, mode: "insensitive" } } },
{ brand: { name: { contains: search, mode: "insensitive" } } },
{ category: { name: { contains: search, mode: "insensitive" } } },
],
}),
@@ -66,7 +66,7 @@ export const productsRoute = new Elysia({ prefix: "/products" })
where,
include: {
category: { include: { translations: true } },
scent: true,
brand: true,
translations: true,
variants: {
include: {
@@ -99,9 +99,9 @@ export const productsRoute = new Elysia({ prefix: "/products" })
const catTrans = p.category.translations?.find((t) => t.langCode === lang) || p.category.translations?.find((t) => t.langCode === 'th') || p.category.translations?.[0];
return {
...p,
name: trans?.name || '',
description: trans?.description,
details: trans?.details,
name: trans?.name || p.name || '',
description: trans?.description || p.description,
details: trans?.details || p.details,
category: { ...p.category, name: catTrans?.name || p.category.name }
};
}
@@ -130,7 +130,7 @@ export const productsRoute = new Elysia({ prefix: "/products" })
where: { id },
include: {
category: { include: { translations: true } },
scent: true,
brand: true,
translations: true,
variants: {
include: {
@@ -159,9 +159,9 @@ export const productsRoute = new Elysia({ prefix: "/products" })
const catTrans = product.category.translations?.find((t) => t.langCode === lang) || product.category.translations?.find((t) => t.langCode === 'th') || product.category.translations?.[0];
return {
...product,
name: trans?.name || '',
description: trans?.description,
details: trans?.details,
name: trans?.name || product.name || '',
description: trans?.description || product.description,
details: trans?.details || product.details,
category: { ...product.category, name: catTrans?.name || product.category.name }
};
}
@@ -174,9 +174,14 @@ export const productsRoute = new Elysia({ prefix: "/products" })
try {
const updatedProduct = await prisma.$transaction(async (tx) => {
// 1. Update basic product info
const updateData = {};
if (categoryId !== undefined)
updateData.categoryId = categoryId;
if (body.options !== undefined)
updateData.options = body.options;
const prod = await tx.product.update({
where: { id },
data: { categoryId },
data: updateData,
});
// 2. Sync Variants
await tx.productVariant.deleteMany({
@@ -187,8 +192,13 @@ export const productsRoute = new Elysia({ prefix: "/products" })
await tx.productVariant.create({
data: {
productId: id,
size: v.size,
price: v.price,
sku: v.sku || null,
barcode: v.barcode || null,
stock: v.stock || 0,
option1: v.option1 || "",
option2: v.option2 || "",
option3: v.option3 || "",
price: v.price || 0,
bomItems: v.bomItems && v.bomItems.length > 0 ? {
create: v.bomItems.map((b) => ({
rawMaterialId: b.rawMaterialId,
@@ -228,6 +238,104 @@ export const productsRoute = new Elysia({ prefix: "/products" })
summary: "Update Product",
tags: ["products"],
},
})
.post("/:id/images", async ({ params, body, status }) => {
const id = parseInt(params.id);
if (isNaN(id))
return status(400, { message: "Invalid product ID" });
const { url } = body;
if (!url)
return status(400, { message: "URL is required" });
try {
const product = await prisma.product.findUnique({
where: { id },
include: { images: true }
});
if (!product)
return status(404, { message: "Product not found" });
const newImage = await prisma.productImage.create({
data: {
productId: id,
url,
order: product.images.length
}
});
return { success: true, image: newImage };
}
catch (err) {
console.error("Add product image error:", err);
return status(500, { success: false, message: "Failed to add product image" });
}
}, {
body: t.Object({
url: t.String()
})
})
.post("/", async ({ body, status }) => {
const payload = body;
try {
const product = await prisma.$transaction(async (tx) => {
const prod = await tx.product.create({
data: {
categoryId: payload.categoryId,
brandId: payload.brandId,
name: payload.name || "New Product",
price: payload.price || 0,
description: payload.description || "",
details: payload.details || "",
options: payload.options || [],
}
});
await tx.productTranslation.create({
data: {
productId: prod.id,
langCode: 'th',
name: payload.name || "New Product",
description: payload.description || "",
details: payload.details || "",
}
});
if (payload.variants && payload.variants.length > 0) {
for (const v of payload.variants) {
await tx.productVariant.create({
data: {
productId: prod.id,
sku: v.sku || null,
barcode: v.barcode || null,
stock: v.stock || 0,
option1: v.option1 || "",
option2: v.option2 || "",
option3: v.option3 || "",
price: v.price || 0,
}
});
}
}
if (payload.images && payload.images.length > 0) {
for (const [idx, img] of payload.images.entries()) {
await tx.productImage.create({
data: {
productId: prod.id,
url: img.url,
order: img.order ?? idx,
}
});
}
}
return prod;
});
return { success: true, product };
}
catch (err) {
console.error("Create product error:", err);
return status(500, { success: false, message: "Failed to create product" });
}
}, {
body: t.Any(),
detail: {
summary: "Create Product",
tags: ["products"],
},
})
.patch("/:id", async ({ params, body, status }) => {
const id = parseInt(params.id);
@@ -238,10 +346,12 @@ export const productsRoute = new Elysia({ prefix: "/products" })
const prodData = {};
if (payload.categoryId !== undefined)
prodData.categoryId = payload.categoryId;
if (payload.scentId !== undefined)
prodData.scentId = payload.scentId;
if (payload.brandId !== undefined)
prodData.brandId = payload.brandId;
if (payload.price !== undefined)
prodData.price = payload.price;
if (payload.options !== undefined)
prodData.options = payload.options;
const prod = await prisma.product.update({
where: { id },
data: prodData,