From ef55d9bb24430610b60c1b77821307cd72c08c55 Mon Sep 17 00:00:00 2001 From: zwlucas Date: Sat, 25 Jul 2026 18:23:29 -0300 Subject: feat: upload of the project Signed-off-by: zwlucas --- .../interactions/interactions.repository.ts | 135 +++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 api/src/modules/interactions/interactions.repository.ts (limited to 'api/src/modules/interactions/interactions.repository.ts') diff --git a/api/src/modules/interactions/interactions.repository.ts b/api/src/modules/interactions/interactions.repository.ts new file mode 100644 index 0000000..1a32dc0 --- /dev/null +++ b/api/src/modules/interactions/interactions.repository.ts @@ -0,0 +1,135 @@ +/** + * interactions.repository.ts + * + * Queries de banco para todas as interações sobre tickets existentes. + * Única camada que toca o Drizzle neste módulo. + */ + +import { eq } from 'drizzle-orm'; +import type { Database } from '@/db/connection.js'; +import { tickets, comments, users } from '@/db/schema.js'; +import type { TicketStatus, TicketPriority } from '@/db/schema.js'; + +// ─── Tickets ────────────────────────────────────────────────────────────────── + +export async function updateTicketStatus( + db: Database, + ticketId: number, + status: TicketStatus, +) { + const now = new Date().toISOString(); + + const [updated] = await db + .update(tickets) + .set({ + status, + updatedAt: now, + // Registra o momento exato da resolução para métricas de SLA + resolvedAt: status === 'resolved' || status === 'closed' ? now : null, + }) + .where(eq(tickets.id, ticketId)) + .returning(); + + return updated ?? null; +} + +export async function assignTicket( + db: Database, + ticketId: number, + assigneeId: number, +) { + const [updated] = await db + .update(tickets) + .set({ + assigneeId, + // Atribuir um agente muda automaticamente o status de 'open' → 'in_progress' + status: 'in_progress', + updatedAt: new Date().toISOString(), + }) + .where(eq(tickets.id, ticketId)) + .returning(); + + return updated ?? null; +} + +export async function updateTicketPriority( + db: Database, + ticketId: number, + priority: TicketPriority, +) { + const [updated] = await db + .update(tickets) + .set({ + priority, + updatedAt: new Date().toISOString(), + }) + .where(eq(tickets.id, ticketId)) + .returning(); + + return updated ?? null; +} + +// ─── Comentários ────────────────────────────────────────────────────────────── + +export async function addComment( + db: Database, + data: { + ticketId: number; + authorId: number; + content: string; + isInternal: boolean; + }, +) { + const now = new Date().toISOString(); + + const [comment] = await db + .insert(comments) + .values({ + ...data, + createdAt: now, + updatedAt: now, + }) + .returning(); + + return comment; +} + +export async function findCommentsByTicketId( + db: Database, + ticketId: number, + // Filtro: agentes veem tudo, solicitantes só veem comentários públicos + includeInternal: boolean = true, +) { + const rows = await db + .select({ + id: comments.id, + ticketId: comments.ticketId, + authorId: comments.authorId, + content: comments.content, + isInternal: comments.isInternal, + createdAt: comments.createdAt, + updatedAt: comments.updatedAt, + authorName: users.name, + }) + .from(comments) + .leftJoin(users, eq(comments.authorId, users.id)) + .where(eq(comments.ticketId, ticketId)) + .orderBy(comments.createdAt); // Cronológico: mais antigos primeiro + + // Filtra notas internas em memória (conjunto pequeno por ticket) + return includeInternal + ? rows + : rows.filter((c) => !c.isInternal); +} + +export async function deleteComment( + db: Database, + commentId: number, +) { + const [deleted] = await db + .delete(comments) + .where(eq(comments.id, commentId)) + .returning(); + + return deleted ?? null; +} -- cgit v1.2.3