aboutsummaryrefslogtreecommitdiff
path: root/api/src/modules/interactions/interactions.repository.ts
blob: 1a32dc0b96bc4e761753e35edaad7e2359ca21b6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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;
}