This commit is contained in:
Your Name committed 2026-06-23 20:02:29 -04:00
1 parent ed69a4b840
commit 992335a52b
22 files changed
+1064 -346

No files matched your search

+1 -1
View File
@@ -29,7 +29,7 @@ import { mediaRoute } from "./routes/media";
import { contactsRoute } from "./routes/contacts";
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3051;
const CORS_ORIGIN = process.env.CORS_ORIGIN || "http://localhost:5173";
const JWT_SECRET = process.env.JWT_SECRET || "homaroma_secret_2026_scent";
const JWT_SECRET = process.env.JWT_SECRET || "homaroma_secret_2026_brand";
const isDevelopment = process.env.NODE_ENV !== "production";
const isPrivateLanHostname = (hostname: string) => {
+2 -2
View File
@@ -50,7 +50,7 @@ export const categoriesRoute = new Elysia({ prefix: "/categories" })
translations: true,
products: {
include: {
scent: true,
brand: true,
category: { include: { translations: true } },
translations: true,
variants: { orderBy: { price: "asc" as const } },
@@ -98,7 +98,7 @@ export const categoriesRoute = new Elysia({ prefix: "/categories" })
const trans = p.translations?.find((t: any) => t.langCode === lang) || p.translations?.find((t: any) => t.langCode === 'th') || p.translations?.[0];
return {
...p,
name: trans?.name || p.scent?.name || '',
name: trans?.name || p.brand?.name || '',
description: trans?.description,
details: trans?.details,
category: { ...p.category, name: resolveCatName(p.category, lang) },
+11 -2
View File
@@ -559,8 +559,17 @@ export const pagesRoute = new Elysia({ prefix: "/pages" })
const id = Number(sectionId);
const { type, order, payload, isActive } = body as any;
if (payload && type) {
const validationError = validateSectionPayload(type, payload);
if (payload) {
let validationType = type;
if (!validationType) {
const existingSection = await prisma.pageSection.findUnique({ where: { id } });
if (!existingSection) {
set.status = 404;
return { error: "Section not found" };
}
validationType = existingSection.type;
}
const validationError = validateSectionPayload(validationType, payload);
if (validationError) {
set.status = 400;
return { error: validationError };
+113 -26
View File
@@ -2,15 +2,15 @@ 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" });
if (isNaN(id)) return status(400, { message: "Invalid brand ID" });
const payload = body as any;
const results: any[] = [];
@@ -20,34 +20,34 @@ 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';
@@ -58,11 +58,11 @@ export const productsRoute = new Elysia({ prefix: "/products" })
const where = {
...(categoryId && { categoryId }),
...(scentId && { scentId }),
...(brandId && { brandId }),
...(search && {
OR: [
{ translations: { some: { name: { contains: search, mode: "insensitive" as const } } } },
{ scent: { name: { contains: search, mode: "insensitive" as const } } },
{ brand: { name: { contains: search, mode: "insensitive" as const } } },
{ category: { name: { contains: search, mode: "insensitive" as const } } },
],
}),
@@ -73,7 +73,7 @@ export const productsRoute = new Elysia({ prefix: "/products" })
where,
include: {
category: { include: { translations: true } },
scent: true,
brand: true,
translations: true,
variants: {
include: {
@@ -106,9 +106,9 @@ export const productsRoute = new Elysia({ prefix: "/products" })
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,
name: trans?.name || p.name || '',
description: trans?.description || p.description,
details: trans?.details || p.details,
category: { ...p.category, name: catTrans?.name || p.category.name }
};
}
@@ -139,7 +139,7 @@ export const productsRoute = new Elysia({ prefix: "/products" })
where: { id },
include: {
category: { include: { translations: true } },
scent: true,
brand: true,
translations: true,
variants: {
include: {
@@ -168,9 +168,9 @@ export const productsRoute = new Elysia({ prefix: "/products" })
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,
name: trans?.name || product.name || '',
description: trans?.description || product.description,
details: trans?.details || product.details,
category: { ...product.category, name: catTrans?.name || product.category.name }
};
}
@@ -186,9 +186,13 @@ export const productsRoute = new Elysia({ prefix: "/products" })
try {
const updatedProduct = await prisma.$transaction(async (tx) => {
// 1. Update basic product info
const updateData: any = {};
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
@@ -201,8 +205,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: any) => ({
rawMaterialId: b.rawMaterialId,
@@ -248,6 +257,83 @@ export const productsRoute = new Elysia({ prefix: "/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 as any;
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: any) {
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 as any;
try {
const product = await prisma.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 || [],
}
});
// Also create the TH translation by default
await prisma.productTranslation.create({
data: {
productId: product.id,
langCode: 'th',
name: payload.name || "New Product",
description: payload.description || "",
details: payload.details || "",
}
});
return { success: true, product };
} catch (err: any) {
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 }) => {
@@ -258,8 +344,9 @@ export const productsRoute = new Elysia({ prefix: "/products" })
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.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 },