// Schema do domínio de aplicação do B2BCall. Vive no schema "public" do // Postgres — nunca misturado com as tabelas do Asterisk Realtime (schema // "asterisk", ver infrastructure/postgres/init/002-asterisk-realtime.sql). // // Modelado incrementalmente por fase (ver TODO.md): esta primeira migration // cobre apenas autenticação, RBAC e auditoria (Fase 3). Demais entidades // (agentes, troncos, filas, campanhas, leads, ...) chegam em migrations // subsequentes, nunca alteração manual de schema. generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id String @id @default(uuid()) name String email String @unique passwordHash String @map("password_hash") isActive Boolean @default(true) @map("is_active") mustChangePassword Boolean @default(false) @map("must_change_password") lastLoginAt DateTime? @map("last_login_at") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") roles UserRole[] sessions Session[] passwordResetTokens PasswordResetToken[] auditLogs AuditLog[] @@map("users") } model Session { id String @id @default(uuid()) userId String @map("user_id") refreshTokenHash String @map("refresh_token_hash") userAgent String? @map("user_agent") ipAddress String? @map("ip_address") expiresAt DateTime @map("expires_at") revokedAt DateTime? @map("revoked_at") createdAt DateTime @default(now()) @map("created_at") user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([expiresAt]) @@map("sessions") } model PasswordResetToken { id String @id @default(uuid()) userId String @map("user_id") tokenHash String @unique @map("token_hash") expiresAt DateTime @map("expires_at") usedAt DateTime? @map("used_at") createdAt DateTime @default(now()) @map("created_at") user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@map("password_reset_tokens") } model Role { id String @id @default(uuid()) name String @unique description String? isSystem Boolean @default(false) @map("is_system") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") users UserRole[] permissions RolePermission[] @@map("roles") } model Permission { id String @id @default(uuid()) key String @unique description String? roles RolePermission[] @@map("permissions") } model UserRole { userId String @map("user_id") roleId String @map("role_id") user User @relation(fields: [userId], references: [id], onDelete: Cascade) role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) @@id([userId, roleId]) @@map("user_roles") } model RolePermission { roleId String @map("role_id") permissionId String @map("permission_id") role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade) @@id([roleId, permissionId]) @@map("role_permissions") } model AuditLog { id BigInt @id @default(autoincrement()) userId String? @map("user_id") action String entityType String? @map("entity_type") entityId String? @map("entity_id") before Json? after Json? ipAddress String? @map("ip_address") userAgent String? @map("user_agent") createdAt DateTime @default(now()) @map("created_at") user User? @relation(fields: [userId], references: [id], onDelete: SetNull) @@index([userId]) @@index([entityType, entityId]) @@index([createdAt]) @@map("audit_logs") } // =========================================================================== // Fase 4 — Telefonia. CRUD da aplicação; os objetos PJSIP correspondentes // (ps_endpoints/ps_auths/ps_aors/ps_endpoint_id_ips/ps_registrations) são // provisionados no schema "asterisk" pelo TrunksService/ExtensionsService // (packages/telephony), nunca editados manualmente. // =========================================================================== enum TrunkType { IP AUTH REGISTRATION } enum DtmfMode { rfc4733 info inband auto } model Trunk { id String @id @default(uuid()) name String @unique type TrunkType host String port Int @default(5060) transport String @default("udp") username String? // Segredo cifrado em repouso (AES-256-GCM, master key fora do banco — // agente.md seção 55). Nunca retornado em claro pela API após salvar. secretEncrypted String? @map("secret_encrypted") fromUser String? @map("from_user") fromDomain String? @map("from_domain") contactUser String? @map("contact_user") outboundProxy String? @map("outbound_proxy") context String @default("outbound") callerId String? @map("caller_id") codecs String[] @default(["ulaw", "alaw"]) dtmfMode DtmfMode @default(rfc4733) @map("dtmf_mode") qualifyFrequency Int @default(60) @map("qualify_frequency") maxChannels Int? @map("max_channels") maxCps Int @default(5) @map("max_cps") allowedIps String[] @default([]) @map("allowed_ips") enabled Boolean @default(true) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") @@map("trunks") } model Extension { id String @id @default(uuid()) number String @unique name String sipPasswordEncrypted String @map("sip_password_encrypted") callerId String? @map("caller_id") context String @default("b2bcall-agents") codecs String[] @default(["ulaw", "alaw"]) transport String @default("udp") maxContacts Int @default(1) @map("max_contacts") qualifyFrequency Int @default(60) @map("qualify_frequency") enabled Boolean @default(true) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") @@map("extensions") } // Último estado conhecido de cada ramal — alimentado por // apps/asterisk-events a partir de DeviceStateChange/ContactStatus. // Fonte do painel de monitoramento (nunca polling do Asterisk no frontend). model ExtensionState { extension String @id deviceState String? @map("device_state") contactStatus String? @map("contact_status") contactUri String? @map("contact_uri") updatedAt DateTime @updatedAt @map("updated_at") @@map("extension_states") } // =========================================================================== // Dialplan estruturado e versionado (agente.md seção 21). O modo "Advanced" // citado na spec é apenas uma restrição de permissão no frontend sobre os // mesmos dados — não um formato de armazenamento diferente. // =========================================================================== model DialplanEntry { id String @id @default(uuid()) context String exten String priority Int application String argument String? enabled Boolean @default(true) order Int @default(0) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") @@index([context]) @@map("dialplan_entries") } enum DialplanVersionStatus { APPLIED FAILED ROLLED_BACK } model DialplanVersion { id String @id @default(uuid()) generatedConfig String @map("generated_config") status DialplanVersionStatus reloadResult String? @map("reload_result") createdById String? @map("created_by_id") createdAt DateTime @default(now()) @map("created_at") @@map("dialplan_versions") }