chore: reorganize project structure and ensure default queues

This commit is contained in:
Esdras Renan 2025-10-06 22:59:35 -03:00
parent 854887f499
commit 1cccb852a5
201 changed files with 417 additions and 838 deletions

View file

@ -0,0 +1,215 @@
-- CreateTable
CREATE TABLE "Team" (
"id" TEXT NOT NULL PRIMARY KEY,
"tenantId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
-- CreateTable
CREATE TABLE "TeamMember" (
"teamId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"isLead" BOOLEAN NOT NULL DEFAULT false,
"assignedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY ("teamId", "userId"),
CONSTRAINT "TeamMember_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "TeamMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL PRIMARY KEY,
"tenantId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"email" TEXT NOT NULL,
"role" TEXT NOT NULL,
"timezone" TEXT NOT NULL DEFAULT 'America/Sao_Paulo',
"avatarUrl" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
-- CreateTable
CREATE TABLE "Queue" (
"id" TEXT NOT NULL PRIMARY KEY,
"tenantId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"teamId" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Queue_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "Ticket" (
"id" TEXT NOT NULL PRIMARY KEY,
"tenantId" TEXT NOT NULL,
"reference" INTEGER NOT NULL DEFAULT 0,
"subject" TEXT NOT NULL,
"summary" TEXT,
"status" TEXT NOT NULL DEFAULT 'NEW',
"priority" TEXT NOT NULL DEFAULT 'MEDIUM',
"channel" TEXT NOT NULL DEFAULT 'EMAIL',
"queueId" TEXT,
"requesterId" TEXT NOT NULL,
"assigneeId" TEXT,
"slaPolicyId" TEXT,
"dueAt" DATETIME,
"firstResponseAt" DATETIME,
"resolvedAt" DATETIME,
"closedAt" DATETIME,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Ticket_requesterId_fkey" FOREIGN KEY ("requesterId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "Ticket_assigneeId_fkey" FOREIGN KEY ("assigneeId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT "Ticket_queueId_fkey" FOREIGN KEY ("queueId") REFERENCES "Queue" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT "Ticket_slaPolicyId_fkey" FOREIGN KEY ("slaPolicyId") REFERENCES "SlaPolicy" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "TicketEvent" (
"id" TEXT NOT NULL PRIMARY KEY,
"ticketId" TEXT NOT NULL,
"type" TEXT NOT NULL,
"payload" JSONB NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "TicketEvent_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "Ticket" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "TicketComment" (
"id" TEXT NOT NULL PRIMARY KEY,
"ticketId" TEXT NOT NULL,
"authorId" TEXT NOT NULL,
"visibility" TEXT NOT NULL DEFAULT 'INTERNAL',
"body" TEXT NOT NULL,
"attachments" JSONB,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "TicketComment_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "Ticket" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "TicketComment_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "SlaPolicy" (
"id" TEXT NOT NULL PRIMARY KEY,
"tenantId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"timeToFirstResponse" INTEGER,
"timeToResolution" INTEGER,
"calendar" JSONB,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
-- CreateTable
CREATE TABLE "AuthUser" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT,
"email" TEXT NOT NULL,
"emailVerified" BOOLEAN NOT NULL DEFAULT false,
"image" TEXT,
"role" TEXT NOT NULL DEFAULT 'agent',
"tenantId" TEXT,
"avatarUrl" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
-- CreateTable
CREATE TABLE "AuthSession" (
"id" TEXT NOT NULL PRIMARY KEY,
"userId" TEXT NOT NULL,
"token" TEXT NOT NULL,
"expiresAt" DATETIME NOT NULL,
"ipAddress" TEXT,
"userAgent" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "AuthSession_userId_fkey" FOREIGN KEY ("userId") REFERENCES "AuthUser" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "AuthAccount" (
"id" TEXT NOT NULL PRIMARY KEY,
"userId" TEXT NOT NULL,
"accountId" TEXT NOT NULL,
"providerId" TEXT NOT NULL,
"accessToken" TEXT,
"refreshToken" TEXT,
"accessTokenExpiresAt" DATETIME,
"refreshTokenExpiresAt" DATETIME,
"scope" TEXT,
"idToken" TEXT,
"password" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "AuthAccount_userId_fkey" FOREIGN KEY ("userId") REFERENCES "AuthUser" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "AuthVerification" (
"id" TEXT NOT NULL PRIMARY KEY,
"identifier" TEXT NOT NULL,
"value" TEXT NOT NULL,
"expiresAt" DATETIME NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
-- CreateIndex
CREATE INDEX "Team_tenantId_name_idx" ON "Team"("tenantId", "name");
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE INDEX "User_tenantId_role_idx" ON "User"("tenantId", "role");
-- CreateIndex
CREATE UNIQUE INDEX "Queue_tenantId_slug_key" ON "Queue"("tenantId", "slug");
-- CreateIndex
CREATE INDEX "Ticket_tenantId_status_idx" ON "Ticket"("tenantId", "status");
-- CreateIndex
CREATE INDEX "Ticket_tenantId_queueId_idx" ON "Ticket"("tenantId", "queueId");
-- CreateIndex
CREATE INDEX "Ticket_tenantId_assigneeId_idx" ON "Ticket"("tenantId", "assigneeId");
-- CreateIndex
CREATE INDEX "TicketEvent_ticketId_createdAt_idx" ON "TicketEvent"("ticketId", "createdAt");
-- CreateIndex
CREATE INDEX "TicketComment_ticketId_visibility_idx" ON "TicketComment"("ticketId", "visibility");
-- CreateIndex
CREATE UNIQUE INDEX "SlaPolicy_tenantId_name_key" ON "SlaPolicy"("tenantId", "name");
-- CreateIndex
CREATE UNIQUE INDEX "AuthUser_email_key" ON "AuthUser"("email");
-- CreateIndex
CREATE UNIQUE INDEX "AuthSession_token_key" ON "AuthSession"("token");
-- CreateIndex
CREATE INDEX "AuthSession_userId_idx" ON "AuthSession"("userId");
-- CreateIndex
CREATE INDEX "AuthSession_token_idx" ON "AuthSession"("token");
-- CreateIndex
CREATE INDEX "AuthAccount_userId_idx" ON "AuthAccount"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "AuthAccount_providerId_accountId_key" ON "AuthAccount"("providerId", "accountId");
-- CreateIndex
CREATE INDEX "AuthVerification_identifier_idx" ON "AuthVerification"("identifier");

View file

@ -0,0 +1,121 @@
-- CreateTable
CREATE TABLE "Company" (
"id" TEXT NOT NULL PRIMARY KEY,
"tenantId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"cnpj" TEXT,
"domain" TEXT,
"phone" TEXT,
"description" TEXT,
"address" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
-- CreateTable
CREATE TABLE "AuthInvite" (
"id" TEXT NOT NULL PRIMARY KEY,
"email" TEXT NOT NULL,
"name" TEXT,
"role" TEXT NOT NULL DEFAULT 'agent',
"tenantId" TEXT NOT NULL,
"token" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'pending',
"expiresAt" DATETIME NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
"createdById" TEXT,
"acceptedAt" DATETIME,
"acceptedById" TEXT,
"revokedAt" DATETIME,
"revokedById" TEXT,
"revokedReason" TEXT
);
-- CreateTable
CREATE TABLE "AuthInviteEvent" (
"id" TEXT NOT NULL PRIMARY KEY,
"inviteId" TEXT NOT NULL,
"type" TEXT NOT NULL,
"payload" JSONB,
"actorId" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "AuthInviteEvent_inviteId_fkey" FOREIGN KEY ("inviteId") REFERENCES "AuthInvite" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Ticket" (
"id" TEXT NOT NULL PRIMARY KEY,
"tenantId" TEXT NOT NULL,
"reference" INTEGER NOT NULL DEFAULT 0,
"subject" TEXT NOT NULL,
"summary" TEXT,
"status" TEXT NOT NULL DEFAULT 'NEW',
"priority" TEXT NOT NULL DEFAULT 'MEDIUM',
"channel" TEXT NOT NULL DEFAULT 'EMAIL',
"queueId" TEXT,
"requesterId" TEXT NOT NULL,
"assigneeId" TEXT,
"slaPolicyId" TEXT,
"companyId" TEXT,
"dueAt" DATETIME,
"firstResponseAt" DATETIME,
"resolvedAt" DATETIME,
"closedAt" DATETIME,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Ticket_requesterId_fkey" FOREIGN KEY ("requesterId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "Ticket_assigneeId_fkey" FOREIGN KEY ("assigneeId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT "Ticket_queueId_fkey" FOREIGN KEY ("queueId") REFERENCES "Queue" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT "Ticket_slaPolicyId_fkey" FOREIGN KEY ("slaPolicyId") REFERENCES "SlaPolicy" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT "Ticket_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
INSERT INTO "new_Ticket" ("assigneeId", "channel", "closedAt", "createdAt", "dueAt", "firstResponseAt", "id", "priority", "queueId", "reference", "requesterId", "resolvedAt", "slaPolicyId", "status", "subject", "summary", "tenantId", "updatedAt") SELECT "assigneeId", "channel", "closedAt", "createdAt", "dueAt", "firstResponseAt", "id", "priority", "queueId", "reference", "requesterId", "resolvedAt", "slaPolicyId", "status", "subject", "summary", "tenantId", "updatedAt" FROM "Ticket";
DROP TABLE "Ticket";
ALTER TABLE "new_Ticket" RENAME TO "Ticket";
CREATE INDEX "Ticket_tenantId_status_idx" ON "Ticket"("tenantId", "status");
CREATE INDEX "Ticket_tenantId_queueId_idx" ON "Ticket"("tenantId", "queueId");
CREATE INDEX "Ticket_tenantId_assigneeId_idx" ON "Ticket"("tenantId", "assigneeId");
CREATE INDEX "Ticket_tenantId_companyId_idx" ON "Ticket"("tenantId", "companyId");
CREATE TABLE "new_User" (
"id" TEXT NOT NULL PRIMARY KEY,
"tenantId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"email" TEXT NOT NULL,
"role" TEXT NOT NULL,
"timezone" TEXT NOT NULL DEFAULT 'America/Sao_Paulo',
"avatarUrl" TEXT,
"companyId" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "User_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
INSERT INTO "new_User" ("avatarUrl", "createdAt", "email", "id", "name", "role", "tenantId", "timezone", "updatedAt") SELECT "avatarUrl", "createdAt", "email", "id", "name", "role", "tenantId", "timezone", "updatedAt" FROM "User";
DROP TABLE "User";
ALTER TABLE "new_User" RENAME TO "User";
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
CREATE INDEX "User_tenantId_role_idx" ON "User"("tenantId", "role");
CREATE INDEX "User_tenantId_companyId_idx" ON "User"("tenantId", "companyId");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
-- CreateIndex
CREATE INDEX "Company_tenantId_name_idx" ON "Company"("tenantId", "name");
-- CreateIndex
CREATE UNIQUE INDEX "Company_tenantId_slug_key" ON "Company"("tenantId", "slug");
-- CreateIndex
CREATE UNIQUE INDEX "AuthInvite_token_key" ON "AuthInvite"("token");
-- CreateIndex
CREATE INDEX "AuthInvite_tenantId_status_idx" ON "AuthInvite"("tenantId", "status");
-- CreateIndex
CREATE INDEX "AuthInvite_tenantId_email_idx" ON "AuthInvite"("tenantId", "email");
-- CreateIndex
CREATE INDEX "AuthInviteEvent_inviteId_createdAt_idx" ON "AuthInviteEvent"("inviteId", "createdAt");

View file

@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "sqlite"

307
prisma/schema.prisma Normal file
View file

@ -0,0 +1,307 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
enum UserRole {
ADMIN
MANAGER
AGENT
COLLABORATOR
CUSTOMER
}
enum TicketStatus {
NEW
OPEN
PENDING
ON_HOLD
RESOLVED
CLOSED
}
enum TicketPriority {
LOW
MEDIUM
HIGH
URGENT
}
enum TicketChannel {
EMAIL
WHATSAPP
CHAT
PHONE
API
MANUAL
}
enum CommentVisibility {
PUBLIC
INTERNAL
}
model Team {
id String @id @default(cuid())
tenantId String
name String
description String?
members TeamMember[]
queues Queue[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([tenantId, name])
}
model TeamMember {
teamId String
userId String
isLead Boolean @default(false)
assignedAt DateTime @default(now())
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([teamId, userId])
}
model Company {
id String @id @default(cuid())
tenantId String
name String
slug String
cnpj String?
domain String?
phone String?
description String?
address String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
users User[]
tickets Ticket[]
@@unique([tenantId, slug])
@@index([tenantId, name])
}
model User {
id String @id @default(cuid())
tenantId String
name String
email String @unique
role UserRole
timezone String @default("America/Sao_Paulo")
avatarUrl String?
companyId String?
teams TeamMember[]
requestedTickets Ticket[] @relation("TicketRequester")
assignedTickets Ticket[] @relation("TicketAssignee")
comments TicketComment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
company Company? @relation(fields: [companyId], references: [id])
@@index([tenantId, role])
@@index([tenantId, companyId])
}
model Queue {
id String @id @default(cuid())
tenantId String
name String
slug String
teamId String?
tickets Ticket[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
team Team? @relation(fields: [teamId], references: [id])
@@unique([tenantId, slug])
}
model Ticket {
id String @id @default(cuid())
tenantId String
reference Int @default(0)
subject String
summary String?
status TicketStatus @default(NEW)
priority TicketPriority @default(MEDIUM)
channel TicketChannel @default(EMAIL)
queueId String?
requesterId String
assigneeId String?
slaPolicyId String?
companyId String?
dueAt DateTime?
firstResponseAt DateTime?
resolvedAt DateTime?
closedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
requester User @relation("TicketRequester", fields: [requesterId], references: [id])
assignee User? @relation("TicketAssignee", fields: [assigneeId], references: [id])
queue Queue? @relation(fields: [queueId], references: [id])
slaPolicy SlaPolicy? @relation(fields: [slaPolicyId], references: [id])
company Company? @relation(fields: [companyId], references: [id])
events TicketEvent[]
comments TicketComment[]
@@index([tenantId, status])
@@index([tenantId, queueId])
@@index([tenantId, assigneeId])
@@index([tenantId, companyId])
}
model TicketEvent {
id String @id @default(cuid())
ticketId String
type String
payload Json
createdAt DateTime @default(now())
ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade)
@@index([ticketId, createdAt])
}
model TicketComment {
id String @id @default(cuid())
ticketId String
authorId String
visibility CommentVisibility @default(INTERNAL)
body String
attachments Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade)
author User @relation(fields: [authorId], references: [id])
@@index([ticketId, visibility])
}
model SlaPolicy {
id String @id @default(cuid())
tenantId String
name String
description String?
timeToFirstResponse Int?
timeToResolution Int?
calendar Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tickets Ticket[]
@@unique([tenantId, name])
}
model AuthUser {
id String @id @default(cuid())
name String?
email String @unique
emailVerified Boolean @default(false)
image String?
role String @default("agent")
tenantId String?
avatarUrl String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
sessions AuthSession[]
accounts AuthAccount[]
}
model AuthSession {
id String @id @default(cuid())
userId String
token String @unique
expiresAt DateTime
ipAddress String?
userAgent String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user AuthUser @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([token])
}
model AuthAccount {
id String @id @default(cuid())
userId String
accountId String
providerId String
accessToken String?
refreshToken String?
accessTokenExpiresAt DateTime?
refreshTokenExpiresAt DateTime?
scope String?
idToken String?
password String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user AuthUser @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([providerId, accountId])
@@index([userId])
}
model AuthInvite {
id String @id @default(cuid())
email String
name String?
role String @default("agent")
tenantId String
token String @unique
status String @default("pending")
expiresAt DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdById String?
acceptedAt DateTime?
acceptedById String?
revokedAt DateTime?
revokedById String?
revokedReason String?
events AuthInviteEvent[]
@@index([tenantId, status])
@@index([tenantId, email])
}
model AuthInviteEvent {
id String @id @default(cuid())
inviteId String
type String
payload Json?
actorId String?
createdAt DateTime @default(now())
invite AuthInvite @relation(fields: [inviteId], references: [id], onDelete: Cascade)
@@index([inviteId, createdAt])
}
model AuthVerification {
id String @id @default(cuid())
identifier String
value String
expiresAt DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([identifier])
}