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({ orderBy: { name: "asc" }, include: { translations: true } }); }) .patch("/scents/:id/translations", async ({ params, body, status }) => { const id = parseInt(params.id); if (isNaN(id)) return status(400, { message: "Invalid scent ID" }); const payload = body as any; const results: any[] = []; const LANGS = ['th', 'en', 'ja', 'zh', 'vi', 'ko']; for (const lang of LANGS) { 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 } } }); if (existing) { const updated = await prisma.scentTranslation.update({ where: { id: existing.id }, data: { name }, }); results.push(updated); } else { const created = await prisma.scentTranslation.create({ data: { scentId: id, langCode: lang, name }, }); results.push(created); } } const updated = await prisma.scent.findUnique({ where: { id }, include: { translations: true }, }); return { success: true, translations: results, scent: updated }; }) .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; const lang = query.lang || 'th'; // 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: [ { translations: { some: { 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: { include: { translations: true } }, scent: true, translations: true, variants: { include: { bomItems: { include: { rawMaterial: true } } }, orderBy: { price: "asc" } }, images: { orderBy: { order: "asc" } }, }, skip, take: limit, }), prisma.product.count({ where }), ]); // Map translations const mappedProducts = products.map(p => { if (lang === 'all') { const nameObj: any = {}; const descObj: any = {}; const detailsObj: any = {}; p.translations.forEach(t => { nameObj[t.langCode] = t.name; descObj[t.langCode] = t.description; detailsObj[t.langCode] = t.details; }); return { ...p, name: nameObj, description: descObj, details: detailsObj, category: { ...p.category, name: p.category.name } }; // Actually for 'all', keep as is, but wait, category name needs resolving if we want it? Let's just resolve it simply below. } else { const trans = p.translations.find(t => t.langCode === lang) || p.translations.find(t => t.langCode === 'th') || p.translations[0]; const catTrans = p.category.translations?.find((t: any) => t.langCode === lang) || p.category.translations?.find((t: any) => t.langCode === 'th') || p.category.translations?.[0]; return { ...p, name: trans?.name || '', description: trans?.description, details: trans?.details, category: { ...p.category, name: catTrans?.name || p.category.name } }; } }); mappedProducts.sort((a, b) => { const aName = typeof a.name === 'string' ? a.name : (a.name?.th || ''); const bName = typeof b.name === 'string' ? b.name : (b.name?.th || ''); return aName.localeCompare(bName); }); return { data: mappedProducts, meta: { total, page, limit, lastPage: Math.ceil(total / limit), }, }; }) .get("/:id", async ({ params, query, status }) => { const id = parseInt(params.id); if (isNaN(id)) return status(400, { message: "Invalid product ID" }); const lang = query.lang || 'th'; const product = await prisma.product.findUnique({ where: { id }, include: { category: { include: { translations: true } }, scent: true, translations: true, variants: { include: { bomItems: { include: { rawMaterial: true } } }, orderBy: { price: "asc" } }, images: { orderBy: { order: "asc" } }, }, }); if (!product) return status(404, { message: "Product not found" }); if (lang === 'all') { const nameObj: any = {}; const descObj: any = {}; const detailsObj: any = {}; product.translations.forEach(t => { nameObj[t.langCode] = t.name; descObj[t.langCode] = t.description; detailsObj[t.langCode] = t.details; }); return { ...product, name: nameObj, description: descObj, details: detailsObj }; } else { const trans = product.translations.find(t => t.langCode === lang) || product.translations.find(t => t.langCode === 'th') || product.translations[0]; const catTrans = product.category.translations?.find((t: any) => t.langCode === lang) || product.category.translations?.find((t: any) => t.langCode === 'th') || product.category.translations?.[0]; return { ...product, name: trans?.name || '', description: trans?.description, details: trans?.details, category: { ...product.category, name: catTrans?.name || product.category.name } }; } }) .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 as any; try { const updatedProduct = await prisma.$transaction(async (tx) => { // 1. Update basic product info const prod = await tx.product.update({ where: { id }, data: { categoryId }, }); // 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) { 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.Any(), detail: { summary: "Update Product", tags: ["products"], }, } ) .patch( "/:id", async ({ params, body, status }) => { const id = parseInt(params.id); if (isNaN(id)) return status(400, { message: "Invalid product ID" }); try { const payload = body as any; const prodData: any = {}; if (payload.categoryId !== undefined) prodData.categoryId = payload.categoryId; if (payload.scentId !== undefined) prodData.scentId = payload.scentId; if (payload.price !== undefined) prodData.price = payload.price; const prod = await prisma.product.update({ where: { id }, data: prodData, }); // Update translations // If name, description, details are strings, treat as 'th' // If they are objects, iterate over them const languages = ['th', 'en', 'ja', 'zh', 'vi', 'ko']; for (const lang of languages) { const updateData: any = {}; let shouldUpdate = false; if (payload.name) { const val = typeof payload.name === 'string' ? (lang === 'th' ? payload.name : null) : payload.name[lang]; if (val !== undefined && val !== null) { updateData.name = val; shouldUpdate = true; } } if (payload.description) { const val = typeof payload.description === 'string' ? (lang === 'th' ? payload.description : null) : payload.description[lang]; if (val !== undefined && val !== null) { updateData.description = val; shouldUpdate = true; } } if (payload.details) { const val = typeof payload.details === 'string' ? (lang === 'th' ? payload.details : null) : payload.details[lang]; if (val !== undefined && val !== null) { updateData.details = val; shouldUpdate = true; } } if (shouldUpdate) { // First check if translation exists to create or update const existing = await prisma.productTranslation.findUnique({ where: { productId_langCode: { productId: id, langCode: lang } } }); if (existing) { await prisma.productTranslation.update({ where: { id: existing.id }, data: updateData }); } else if (updateData.name) { // name is required for creation await prisma.productTranslation.create({ data: { productId: id, langCode: lang, name: updateData.name, description: updateData.description, details: updateData.details } }); } } } return { success: true, product: prod }; } catch (err: any) { console.error("Patch product error:", err); return status(500, { success: false, message: "Failed to patch product" }); } }, { body: t.Any(), detail: { summary: "Partial Update Product", tags: ["products"], }, } );