aboutsummaryrefslogtreecommitdiff
path: root/api/src/modules/interactions/interactions.controller.ts
diff options
context:
space:
mode:
authorzwlucas <lucas.fariamo08@gmail.com>2026-07-25 21:23:29 +0000
committerzwlucas <lucas.fariamo08@gmail.com>2026-07-25 21:23:29 +0000
commitef55d9bb24430610b60c1b77821307cd72c08c55 (patch)
treee6935a810b34370b0a4c821e55f6397aac50c832 /api/src/modules/interactions/interactions.controller.ts
downloadhelpdesk-ef55d9bb24430610b60c1b77821307cd72c08c55.tar.gz
helpdesk-ef55d9bb24430610b60c1b77821307cd72c08c55.zip
feat: upload of the projectHEADmaster
Signed-off-by: zwlucas <lucas.fariamo08@gmail.com>
Diffstat (limited to 'api/src/modules/interactions/interactions.controller.ts')
-rw-r--r--api/src/modules/interactions/interactions.controller.ts190
1 files changed, 190 insertions, 0 deletions
diff --git a/api/src/modules/interactions/interactions.controller.ts b/api/src/modules/interactions/interactions.controller.ts
new file mode 100644
index 0000000..5008272
--- /dev/null
+++ b/api/src/modules/interactions/interactions.controller.ts
@@ -0,0 +1,190 @@
+/**
+ * interactions.controller.ts
+ *
+ * Lógica de negócio para todas as interações sobre tickets.
+ * Orquestra o repository, aplica guards e formata respostas.
+ */
+
+import type { FastifyReply, FastifyRequest } from 'fastify';
+import { findTicketById } from '@/modules/tickets/tickets.repository.js';
+import {
+ updateTicketStatus,
+ assignTicket,
+ updateTicketPriority,
+ addComment,
+ findCommentsByTicketId,
+} from './interactions.repository.js';
+import { getSettings } from '@/modules/settings/settings.repository.js';
+import type {
+ TicketIdParams,
+ UpdateTicketStatusBody,
+ AssignTicketBody,
+ UpdateTicketPriorityBody,
+ AddCommentBody,
+} from './interactions.schema.js';
+
+// ─── Guard reutilizável ───────────────────────────────────────────────────────
+// Verifica se o ticket existe antes de qualquer operação
+
+async function assertTicketExists(
+ request: FastifyRequest,
+ reply: FastifyReply,
+ ticketId: number,
+) {
+ const ticket = await findTicketById(request.server.db, ticketId);
+ if (!ticket) {
+ await reply.notFound(`Ticket #${ticketId} não encontrado`);
+ return null;
+ }
+ return ticket;
+}
+
+// ─── PATCH /tickets/:ticketId/status ─────────────────────────────────────────
+
+export async function updateTicketStatusHandler(
+ request: FastifyRequest<{ Params: TicketIdParams; Body: UpdateTicketStatusBody }>,
+ reply: FastifyReply,
+) {
+ const { ticketId } = request.params;
+ const { status, reason } = request.body;
+ const db = request.server.db;
+
+ const ticket = await assertTicketExists(request, reply, ticketId);
+ if (!ticket) return;
+
+ // Impede mover um ticket já fechado
+ if (ticket.status === 'closed') {
+ return reply.badRequest('Tickets fechados não podem ser reabertos por esta rota.');
+ }
+
+ const updated = await updateTicketStatus(db, ticketId, status);
+
+ // Se um motivo foi fornecido, registra automaticamente como nota interna
+ if (reason && request.body.authorId) {
+ await addComment(db, {
+ ticketId,
+ authorId: (request.body as { authorId?: number }).authorId ?? 0,
+ content: `Status alterado para **${status}**. Motivo: ${reason}`,
+ isInternal: true,
+ });
+ }
+
+ if (updated) {
+ request.server.events.emit('ticket.updated', {
+ id: updated.id,
+ status: updated.status,
+ assigneeId: updated.assigneeId,
+ creatorId: ticket.creatorId,
+ authorId: request.user!.id,
+ });
+ }
+
+ return reply.send(updated);
+}
+
+// ─── PATCH /tickets/:ticketId/assign ─────────────────────────────────────────
+
+export async function assignTicketHandler(
+ request: FastifyRequest<{ Params: TicketIdParams; Body: AssignTicketBody }>,
+ reply: FastifyReply,
+) {
+ const { ticketId } = request.params;
+ const db = request.server.db;
+
+ const ticket = await assertTicketExists(request, reply, ticketId);
+ if (!ticket) return;
+
+ const updated = await assignTicket(db, ticketId, request.body.assigneeId);
+
+ if (updated) {
+ request.server.events.emit('ticket.updated', {
+ id: updated.id,
+ status: updated.status,
+ assigneeId: updated.assigneeId,
+ creatorId: ticket.creatorId,
+ authorId: request.user!.id,
+ });
+ }
+
+ return reply.send(updated);
+}
+
+// ─── PATCH /tickets/:ticketId/priority ───────────────────────────────────────
+
+export async function updateTicketPriorityHandler(
+ request: FastifyRequest<{ Params: TicketIdParams; Body: UpdateTicketPriorityBody }>,
+ reply: FastifyReply,
+) {
+ const { ticketId } = request.params;
+
+ const ticket = await assertTicketExists(request, reply, ticketId);
+ if (!ticket) return;
+
+ const updated = await updateTicketPriority(
+ request.server.db,
+ ticketId,
+ request.body.priority,
+ );
+
+ return reply.send(updated);
+}
+
+// ─── POST /tickets/:ticketId/comments ────────────────────────────────────────
+
+export async function addCommentHandler(
+ request: FastifyRequest<{ Params: TicketIdParams; Body: AddCommentBody }>,
+ reply: FastifyReply,
+) {
+ const { ticketId } = request.params;
+ const { content, isInternal, authorId } = request.body;
+
+ const ticket = await assertTicketExists(request, reply, ticketId);
+ if (!ticket) return;
+
+ // Não permite adicionar comentários em tickets fechados ou resolvidos
+ if (ticket.status === 'closed' || ticket.status === 'resolved') {
+ return reply.badRequest('Não é possível comentar em um ticket fechado ou resolvido.');
+ }
+
+ // Verifica a configuração do sistema para bloqueio de chat de requesters
+ if (request.user?.role === 'requester') {
+ const settings = await getSettings(request.server.db);
+ if (!settings.allowRequesterChat) {
+ return reply.forbidden('O chat de chamados está desativado para usuários padrão no momento.');
+ }
+ }
+
+ const comment = await addComment(request.server.db, {
+ ticketId,
+ authorId,
+ content,
+ isInternal,
+ });
+
+ request.server.events.emit('comment.added', {
+ ticketId: comment.ticketId,
+ authorId: comment.authorId,
+ isInternal: comment.isInternal,
+ creatorId: ticket.creatorId,
+ assigneeId: ticket.assigneeId,
+ });
+
+ return reply.code(201).send(comment);
+}
+
+// ─── GET /tickets/:ticketId/comments ─────────────────────────────────────────
+
+export async function listCommentsHandler(
+ request: FastifyRequest<{ Params: TicketIdParams }>,
+ reply: FastifyReply,
+) {
+ const { ticketId } = request.params;
+
+ const ticket = await assertTicketExists(request, reply, ticketId);
+ if (!ticket) return;
+
+ const includeInternal = request.user!.role !== 'requester';
+ const result = await findCommentsByTicketId(request.server.db, ticketId, includeInternal);
+
+ return reply.send(result);
+}