220 lines
7.1 KiB
TypeScript
220 lines
7.1 KiB
TypeScript
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 };
|
|
});
|