aboutsummaryrefslogtreecommitdiff
path: root/api/src/modules/tickets/tickets.controller.ts
diff options
context:
space:
mode:
Diffstat (limited to 'api/src/modules/tickets/tickets.controller.ts')
-rw-r--r--api/src/modules/tickets/tickets.controller.ts112
1 files changed, 112 insertions, 0 deletions
diff --git a/api/src/modules/tickets/tickets.controller.ts b/api/src/modules/tickets/tickets.controller.ts
new file mode 100644
index 0000000..552974a
--- /dev/null
+++ b/api/src/modules/tickets/tickets.controller.ts
@@ -0,0 +1,112 @@
+/**
+ * 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();
+}