import { Elysia, t } from "elysia"; import type { Prisma } from "@prisma/client"; import prisma from "../lib/prisma"; import { defaultCategorySections, createCategorySection, validateCategorySectionPayload, } from "../lib/categorySections"; import { hydrateSectionPayload } from "./pages"; function summarizeValidationIssues(issues: any[]) { return issues.map((issue) => ({ path: issue.path?.join(".") || "(root)", message: issue.message, code: issue.code, })); } export const categorySectionsRoute = new Elysia({ prefix: "/categories" }) // Get all sections for a category .get("/:id/sections", async ({ params, query, status }) => { const id = parseInt(params.id); if (isNaN(id)) return status(400, { message: "Invalid category ID" }); const category = await prisma.category.findUnique({ where: { id, deletedAt: null }, select: { id: true }, }); if (!category) return status(404, { message: "Category not found" }); const includeInactive = query.includeInactive === "true"; const lang = query.lang || "th"; const sections = await prisma.categorySection.findMany({ where: { categoryId: id, ...(includeInactive ? {} : { isActive: true }), }, orderBy: [{ order: "asc" }, { id: "asc" }], }); return await Promise.all( sections.map((section: any) => hydrateSectionPayload(section, lang)) ); }) // Seed default sections .post("/:id/sections/seed-default", async ({ params, status }) => { const id = parseInt(params.id); if (isNaN(id)) return status(400, { message: "Invalid category ID" }); const category = await prisma.category.findUnique({ where: { id, deletedAt: null }, select: { id: true }, }); if (!category) return status(404, { message: "Category not found" }); const existingCount = await prisma.categorySection.count({ where: { categoryId: id }, }); if (existingCount > 0) { return status(409, { success: false, message: "Category sections already exist", }); } await prisma.categorySection.createMany({ data: defaultCategorySections(id), }); const sections = await prisma.categorySection.findMany({ where: { categoryId: id }, orderBy: [{ order: "asc" }, { id: "asc" }], }); return { success: true, sections }; }) // Create a new section .post("/:id/sections", async ({ params, body, status }) => { const id = parseInt(params.id); if (isNaN(id)) return status(400, { message: "Invalid category ID" }); const payload = body as any; const type = String(payload.type || "").toUpperCase(); const sectionPayload = payload.payload || {}; const validationResult = validateCategorySectionPayload(type, sectionPayload); if (!validationResult.success) { return status(400, { success: false, message: "Invalid category section payload", issues: summarizeValidationIssues(validationResult.issues || []), }); } const category = await prisma.category.findUnique({ where: { id, deletedAt: null }, select: { id: true }, }); if (!category) return status(404, { message: "Category not found" }); const section = await prisma.$transaction((tx: Prisma.TransactionClient) => createCategorySection(tx, { categoryId: id, type: validationResult.type, payload: validationResult.data, ...(Number.isFinite(Number(payload.order)) ? { order: Number(payload.order) } : {}), isActive: payload.isActive !== false, isLocked: Boolean(payload.isLocked), }) ); return { success: true, section }; }) // Update a section .put("/sections/:sectionId", async ({ params, body, status }) => { const sectionId = parseInt(params.sectionId); if (isNaN(sectionId)) return status(400, { message: "Invalid section ID" }); const existing = await prisma.categorySection.findUnique({ where: { id: sectionId }, }); if (!existing) return status(404, { message: "Category section not found" }); const payload = body as any; const type = String(payload.type || existing.type).toUpperCase(); const sectionPayload = payload.payload === undefined ? existing.payload : payload.payload; const validationResult = validateCategorySectionPayload(type, sectionPayload); if (!validationResult.success) { return status(400, { success: false, message: "Invalid category section payload", issues: summarizeValidationIssues(validationResult.issues || []), }); } const validatedType = validationResult.type; if (existing.isLocked) { if (payload.type && validatedType !== existing.type) { return status(409, { success: false, message: "Locked category sections cannot change type", }); } if (payload.isActive === false) { return status(409, { success: false, message: "Locked category sections cannot be hidden", }); } if (payload.isLocked === false) { return status(409, { success: false, message: "Locked category sections cannot be unlocked", }); } } const section = await prisma.categorySection.update({ where: { id: sectionId }, data: { type: validatedType, payload: validationResult.data, ...(payload.order !== undefined ? { order: Number(payload.order) } : {}), ...(payload.isActive !== undefined ? { isActive: Boolean(payload.isActive) } : {}), ...(payload.isLocked !== undefined ? { isLocked: Boolean(payload.isLocked) } : {}), }, }); return { success: true, section }; }) // Reorder sections .put("/:id/sections/reorder", async ({ params, body, status }) => { const id = parseInt(params.id); if (isNaN(id)) return status(400, { message: "Invalid category ID" }); const items = (body as any)?.sections; if (!Array.isArray(items)) return status(400, { message: "sections must be an array" }); await prisma.$transaction( items.map((item: any, index: number) => prisma.categorySection.updateMany({ where: { id: Number(item.id), categoryId: id }, data: { order: Number.isFinite(Number(item.order)) ? Number(item.order) : index + 1, }, }) ) ); const sections = await prisma.categorySection.findMany({ where: { categoryId: id }, orderBy: [{ order: "asc" }, { id: "asc" }], }); return { success: true, sections }; }) // Delete a section .delete("/sections/:sectionId", async ({ params, status }) => { const sectionId = parseInt(params.sectionId); if (isNaN(sectionId)) return status(400, { message: "Invalid section ID" }); const existing = await prisma.categorySection.findUnique({ where: { id: sectionId }, }); if (!existing) return status(404, { message: "Category section not found" }); if (existing.isLocked) { return status(409, { success: false, message: "Locked category sections cannot be deleted", }); } await prisma.categorySection.delete({ where: { id: sectionId } }); return { success: true }; });