/** * categories.controller.ts * Lógica de negócio do CRUD de categorias. */ import type { FastifyReply, FastifyRequest } from 'fastify'; import { findAllCategories, findCategoryById, findCategoryBySlug, createCategory, updateCategory, deleteCategory, } from './categories.repository.js'; import type { CategoryParams, CreateCategoryBody, UpdateCategoryBody, } from './categories.schema.js'; import { eq, count } from 'drizzle-orm'; import { tickets } from '@/db/schema.js'; // ─── GET /categories ────────────────────────────────────────────────────────── export async function listCategoriesHandler( request: FastifyRequest, reply: FastifyReply, ) { const list = await findAllCategories(request.server.db); return reply.send(list); } // ─── GET /categories/:id ────────────────────────────────────────────────────── export async function getCategoryHandler( request: FastifyRequest<{ Params: CategoryParams }>, reply: FastifyReply, ) { const category = await findCategoryById(request.server.db, request.params.id); if (!category) { return reply.notFound(`Categoria #${request.params.id} não encontrada.`); } return reply.send(category); } // ─── POST /categories ───────────────────────────────────────────────────────── export async function createCategoryHandler( request: FastifyRequest<{ Body: CreateCategoryBody }>, reply: FastifyReply, ) { const { name, slug, description } = request.body; const db = request.server.db; // Slug deve ser único const existing = await findCategoryBySlug(db, slug); if (existing) { return reply.conflict(`Já existe uma categoria com o slug "${slug}".`); } const category = await createCategory(db, { name, slug, description: description ?? null, }); return reply.code(201).send(category); } // ─── PATCH /categories/:id ──────────────────────────────────────────────────── export async function updateCategoryHandler( request: FastifyRequest<{ Params: CategoryParams; Body: UpdateCategoryBody }>, reply: FastifyReply, ) { const { id } = request.params; const db = request.server.db; const existing = await findCategoryById(db, id); if (!existing) { return reply.notFound(`Categoria #${id} não encontrada.`); } // Se o slug está sendo alterado, verifica conflito com outro registro if (request.body.slug && request.body.slug !== existing.slug) { const slugConflict = await findCategoryBySlug(db, request.body.slug); if (slugConflict) { return reply.conflict(`Já existe uma categoria com o slug "${request.body.slug}".`); } } const updated = await updateCategory(db, id, request.body); return reply.send(updated); } // ─── DELETE /categories/:id ─────────────────────────────────────────────────── export async function deleteCategoryHandler( request: FastifyRequest<{ Params: CategoryParams }>, reply: FastifyReply, ) { const { id } = request.params; const db = request.server.db; const existing = await findCategoryById(db, id); if (!existing) { return reply.notFound(`Categoria #${id} não encontrada.`); } // Protege contra deleção de categoria em uso const [{ total }] = await db .select({ total: count() }) .from(tickets) .where(eq(tickets.categoryId, id)); if (total > 0) { return reply.conflict( `Categoria em uso por ${total} chamado(s). Reclassifique-os antes de excluir.`, ); } await deleteCategory(db, id); return reply.code(204).send(); }