x
This commit is contained in:
1 parent
81ed9af406
commit
4536db6741
56 files changed
+1127
-198
No files matched your search
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user