/** * tickets.controller.ts * * Lógica de negócio do módulo de tickets. * Orquestra o repository, aplica regras e formata respostas. * NÃO acessa o banco diretamente — delega ao repository. */ import type { FastifyReply, FastifyRequest } from 'fastify'; import { createTicket, countTickets, findManyTickets } from './tickets.repository.js'; import type { CreateTicketBody, ListTicketsQuery, TicketParams } from './tickets.schema.js'; import { tickets } from '@/db/schema.js'; import { eq } from 'drizzle-orm'; // ─── POST /tickets ──────────────────────────────────────────────────────────── export async function createTicketHandler( request: FastifyRequest<{ Body: CreateTicketBody }>, reply: FastifyReply, ) { const { title, description, priority, categoryId, creatorId } = request.body; const ticket = await createTicket(request.server.db, { title, description, priority, categoryId: categoryId ?? null, assigneeId: null, // Chamados nascem sem agente atribuído creatorId, status: 'open', dueAt: null, resolvedAt: null, }); request.server.events.emit('ticket.created', { id: ticket.id, title: ticket.title, creatorId: ticket.creatorId, }); return reply.code(201).send(ticket); } // ─── GET /tickets ───────────────────────────────────────────────────────────── export async function listTicketsHandler( request: FastifyRequest<{ Querystring: ListTicketsQuery }>, reply: FastifyReply, ) { const filters = request.query; const db = request.server.db; // Executa contagem e listagem em paralelo para não dobrar o tempo de resposta const [total, data] = await Promise.all([ countTickets(db, filters), findManyTickets(db, filters), ]); const totalPages = Math.ceil(total / filters.limit); return reply.send({ data, meta: { page: filters.page, limit: filters.limit, total, totalPages, }, }); } // ─── GET /tickets/:id ───────────────────────────────────────────────────────── export async function getTicketByIdHandler( request: FastifyRequest<{ Params: TicketParams }>, reply: FastifyReply, ) { const { findTicketById } = await import('./tickets.repository.js'); const ticket = await findTicketById(request.server.db, request.params.id); if (!ticket) { return reply.notFound(`Ticket #${request.params.id} não encontrado`); } return reply.send(ticket); } // ─── DELETE /tickets/:id ────────────────────────────────────────────────────── export async function deleteTicketHandler( request: FastifyRequest<{ Params: TicketParams }>, reply: FastifyReply, ) { const db = request.server.db; const { id } = request.params; const callerUser = request.user!; const { findTicketById } = await import('./tickets.repository.js'); const existingTicket = await findTicketById(db, id); if (!existingTicket) { return reply.notFound('Chamado não encontrado.'); } if (callerUser.role !== 'admin' && existingTicket.creatorId !== callerUser.id) { return reply.forbidden('Você não tem permissão para excluir este chamado.'); } await db.delete(tickets).where(eq(tickets.id, id)); return reply.code(204).send(); }