This commit is contained in:
Your Name committed 2026-10-01 15:42:52 +07:00
1 parent 2d39edbd57
commit 78b722e120
24 files changed
+2130 -65

No files matched your search

+24 -47
View File
@@ -1,7 +1,14 @@
import { Elysia, t } from "elysia";
import type { Prisma } from "@prisma/client";
import prisma from "../lib/prisma";
import { localizeProductAttribute } from "../lib/productAttributes";
import { hydrateSectionPayload, validateSectionPayload } from "./pages";
import {
createCustomProductDetailSection,
createProductMainDetailSection,
defaultProductDetailSections,
validateProductDetailSectionPayload,
} from "../lib/productDetailSections";
import { hydrateSectionPayload } from "./pages";
function summarizeValidationIssues(issues: any[]) {
return issues.map((issue) => ({
@@ -11,16 +18,6 @@ function summarizeValidationIssues(issues: any[]) {
}));
}
function defaultProductDetailSections(productId: number) {
return [
{ productId, type: "PRODUCT_MAIN", order: 1, payload: { layoutMode: "standard", galleryBehavior: "carousel", purchaseCardSticky: true, showStickyPurchaseBarAfterMain: true, showBreadcrumbs: true, showBrandMeta: true }, isActive: true, isLocked: true },
{ productId, type: "PRODUCT_DETAILS", order: 2, payload: { title: { th: "รายละเอียดสินค้า", en: "Product Details" }, showHeading: true }, isActive: true },
{ productId, type: "PRODUCT_SPECS", order: 3, payload: { title: { th: "ข้อมูลสินค้า", en: "Specifications" }, showHeading: true }, isActive: true },
{ productId, type: "PRODUCT_REVIEWS", order: 4, payload: { title: { th: "รีวิว", en: "Reviews" }, showHeading: true }, isActive: true },
{ productId, type: "RELATED_PRODUCTS", order: 5, payload: { title: { th: "สินค้าที่เกี่ยวข้อง", en: "Related Products" }, showHeading: true, showViewAllLink: true }, isActive: true },
];
}
async function assertActiveCategory(categoryId: number, client: any = prisma) {
const category = await client.category.findFirst({
where: { id: categoryId, deletedAt: null },
@@ -508,7 +505,7 @@ export const productsRoute = new Elysia({ prefix: "/products" })
const payload = body as any;
const type = String(payload.type || "").toUpperCase();
const sectionPayload = payload.payload || {};
const validationResult = validateSectionPayload(type, sectionPayload);
const validationResult = validateProductDetailSectionPayload(type, sectionPayload);
if (!validationResult.success) {
return status(400, {
success: false,
@@ -520,7 +517,8 @@ export const productsRoute = new Elysia({ prefix: "/products" })
const product = await prisma.product.findUnique({ where: { id }, select: { id: true } });
if (!product) return status(404, { message: "Product not found" });
const isProductMain = type === "PRODUCT_MAIN";
const validatedType = validationResult.type;
const isProductMain = validatedType === "PRODUCT_MAIN";
if (isProductMain) {
const existingMain = await prisma.productDetailSection.count({
where: { productId: id, type: "PRODUCT_MAIN" },
@@ -530,38 +528,16 @@ export const productsRoute = new Elysia({ prefix: "/products" })
}
}
const lastSection = await prisma.productDetailSection.findFirst({
where: { productId: id },
orderBy: { order: "desc" },
});
const section = isProductMain
? (await prisma.$transaction([
prisma.productDetailSection.updateMany({
where: { productId: id },
data: { order: { increment: 1 } },
}),
prisma.productDetailSection.create({
data: {
productId: id,
type,
order: 1,
payload: validationResult.data,
isActive: true,
isLocked: true,
},
}),
]))[1]
: await prisma.productDetailSection.create({
data: {
productId: id,
type,
order: Number.isFinite(Number(payload.order)) ? Number(payload.order) : (lastSection?.order || 0) + 1,
payload: validationResult.data,
isActive: payload.isActive !== false,
isLocked: Boolean(payload.isLocked),
},
});
? await prisma.$transaction((tx: Prisma.TransactionClient) => createProductMainDetailSection(tx, id, validationResult.data))
: await prisma.$transaction((tx: Prisma.TransactionClient) => createCustomProductDetailSection(tx, {
productId: id,
type: validatedType,
payload: validationResult.data,
...(Number.isFinite(Number(payload.order)) ? { order: Number(payload.order) } : {}),
isActive: payload.isActive !== false,
isLocked: Boolean(payload.isLocked),
}));
return { success: true, section };
})
@@ -575,7 +551,7 @@ export const productsRoute = new Elysia({ prefix: "/products" })
const payload = body as any;
const type = String(payload.type || existing.type).toUpperCase();
const sectionPayload = payload.payload === undefined ? existing.payload : payload.payload;
const validationResult = validateSectionPayload(type, sectionPayload);
const validationResult = validateProductDetailSectionPayload(type, sectionPayload);
if (!validationResult.success) {
return status(400, {
success: false,
@@ -584,8 +560,9 @@ export const productsRoute = new Elysia({ prefix: "/products" })
});
}
const validatedType = validationResult.type;
if (existing.isLocked) {
if (payload.type && type !== existing.type) {
if (payload.type && validatedType !== existing.type) {
return status(409, { success: false, message: "Locked product detail sections cannot change type" });
}
if (payload.isActive === false) {
@@ -606,7 +583,7 @@ export const productsRoute = new Elysia({ prefix: "/products" })
const section = await prisma.productDetailSection.update({
where: { id: sectionId },
data: {
type,
type: validatedType,
payload: validationResult.data,
...(payload.order !== undefined ? { order: Number(payload.order) } : {}),
...(payload.isActive !== undefined ? { isActive: Boolean(payload.isActive) } : {}),