aboutsummaryrefslogtreecommitdiff
path: root/api/src/modules/interactions/interactions.repository.ts
diff options
context:
space:
mode:
Diffstat (limited to 'api/src/modules/interactions/interactions.repository.ts')
-rw-r--r--api/src/modules/interactions/interactions.repository.ts135
1 files changed, 135 insertions, 0 deletions
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;
+}