Database¶
Fonte de verdade do domínio Nayax ↔ Saipos + Outbox. Stack: PostgreSQL + Prisma ORM.
Visão Geral¶
- Provider:
postgresql - Client:
prisma-client-js - URL:
env("DATABASE_URL") - Estratégias:
- Idempotência por chaves naturais (
transactionKey,@@unique([eventKey, action])). - Outbox com job state machine (
PENDING → PROCESSING → SENT | FAILED | DLQ). - Multi-owner por restaurante (User × Restaurant com role).
Diagrama (ASCII)¶
User (id) 1---* Session
User 1---* PasswordReset
User 1---* EmailVerification
User 1---* UserRestaurant
User 1---* AuditLog
Restaurant (id: UUID)
├── * UserRestaurant
├── * OutboxJob
├── * NayaxEvent
├── * NayaxItem
├── * NayaxPayment
├── * NayaxEmployee
├── * NayaxTaxInvoice
├── * NayaxModifier
├── * NayaxPromotion
└── * NayaxTaxInfo / NayaxCoupon / NayaxPartner
NayaxEvent (transactionKey: PK)
├── 1 NayaxTaxInvoice
├── * NayaxItem (modifiers, taxInfo, promotions)
├── * NayaxPayment
├── * NayaxEmployee
├── * NayaxCoupon
├── 0..1 NayaxPartner
└── * OutboxJob (via eventKey; unique per (eventKey, action))
OutboxJob
└── * OutboxAttempt
Enums¶
UserRole¶
OWNER | ADMIN | MANAGER | EMPLOYEE | VIEWER
JobAction¶
CREATE | CANCEL
JobStatus¶
PENDING | PROCESSING | SENT | FAILED | DLQ
Tabelas de Autenticação & Conta¶
User¶
| Campo | Tipo | Regras |
|---|---|---|
id | String | @id @default(cuid()) |
email | String | @unique, @@index([email]) |
passwordHash | String | — |
name | String | — |
emailVerified | Boolean | @default(false) |
active | Boolean | @default(true) |
createdAt | DateTime | @default(now()) |
updatedAt | DateTime | @updatedAt |
lastLoginAt | DateTime? | — |
| Rel. | sessions, userRestaurants, auditLogs |
Session¶
| Campo | Tipo | Regras |
|---|---|---|
id | String | @id @default(cuid()) |
userId | String | @@index([userId]) |
token | String | @unique, @@index([token]) |
expiresAt | DateTime | @@index([expiresAt]) |
ipAddress | String? | — |
userAgent | String? | — |
createdAt | DateTime | @default(now()) |
| Rel. | user @relation(..., onDelete: Cascade) |
PasswordReset / EmailVerification¶
Tokens @unique, expiresAt, usedAt/verifiedAt, relação com User (onDelete: Cascade) e índices em userId/token.
Organização & Acesso¶
Restaurant¶
| Campo | Tipo | Regras |
|---|---|---|
id | String | @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid |
name | String | — |
slug | String | @unique |
createdAt | DateTime | @default(now()) |
updatedAt | DateTime | @updatedAt |
codeStoreNayax | String? | @unique |
codeStoreSaipos | String? | @unique |
saiposIdPartner | String? | — |
saiposSecret | String? | — |
nayaxCode | String? | — |
| Rel. | com Nayax*, OutboxJob, UserRestaurant |
UserRestaurant¶
| Campo | Tipo | Regras |
|---|---|---|
id | String | @id @default(cuid()) |
userId | String | @@index([userId]) |
restaurantId | String | @db.Uuid, @@index([restaurantId]) |
role | UserRole | @default(VIEWER) |
createdAt | DateTime | @default(now()) |
updatedAt | DateTime | @updatedAt |
| Unicidade | @@unique([userId, restaurantId]) | |
| FK | onDelete: Cascade para user e restaurant |
Evento Nayax e Derivados¶
NayaxEvent¶
| Campo | Tipo | Regras/Notas |
|---|---|---|
transactionKey | String | PK (idempotência) |
posCode,storeCode | String | — |
isTestTransaction | Boolean | — |
customerNumber | String | — |
transactionNumber | Int | — |
transactionType | Int | 1=CREATE, outros=CANCEL (regra de app) |
reference | String? | — |
transactionDate | DateTime | — |
transactionStartDateTime | DateTime? | — |
transactionEndDateTime | DateTime? | — |
restaurantId | String? | @db.Uuid, @@index([restaurantId]) |
| Totais/Valores | Float? | totalAmount (obrig.), roundingAmount… |
rawPayload | Json | payload completo |
receivedAt | DateTime | @default(now()) |
createdById | String | auditoria |
| Rel. | outboxJobs @relation("EventOutboxJobs") |
FK Restaurant:
onDelete: SetNullpara manter histórico mesmo após remoção da loja.
Itens relacionados¶
NayaxItem: itens do evento; commodifiers,taxInfo,promotions.NayaxPayment: pagamentos (cartão, pix, voucher etc.).NayaxCoupon: cupons usados.NayaxTaxInvoice: nota fiscal (relation 1–1 com o evento).NayaxEmployee: operador do PDV.NayaxPartner: parceiro/cliente vinculado.
Todas as tabelas Nayax*** possuem restaurantId opcional (SetNull) + índice.
Outbox & Workers¶
OutboxJob¶
| Campo | Tipo | Regras/Notas |
|---|---|---|
id | String | @id @default(cuid()) |
eventKey | String | FK → NayaxEvent.transactionKey, @@index([eventKey]) |
action | JobAction | CREATE / CANCEL |
body | Json | request serializado (quando aplicável) |
codStore | String | store Saipos alvo |
restaurantId | String? | @db.Uuid, relation "RestaurantOutboxJobs" |
status | JobStatus | @default(PENDING) |
attempts | Int | @default(0) |
nextRunAt | DateTime? | @default(now()), scheduler/backoff |
lastError | String? | truncado pelo app |
processingAt | DateTime? | lock time |
sentAt | DateTime? | — |
lockedBy | String? | PID/worker |
createdById | String? | auditoria |
| Índices | — | @@unique([eventKey, action]), @@index([status, nextRunAt]) |
OutboxAttempt¶
Histórico de tentativas (DLQ/FAILED analysis).
| Campo | Tipo | Notas |
|---|---|---|
jobId | String | FK → OutboxJob (onDelete: Cascade) |
triedAt | DateTime | @default(now()) |
durationMs | Int? | — |
httpStatus | Int? | — |
error | String? | — |
OutboxWorker¶
Heartbeat de workers (observabilidade).
Config & Segredos¶
AppConfig:key(PK),value,updatedAt.Secret:key(PK),valueEnc,updatedAt. (armazenar cifrado, app decripta)TokenCache:provider(PK),token,expiresAt,updatedAt. (ex.: Saipos JWT/cache)
Auditoria¶
AuditLog¶
| Campo | Tipo | Notas |
|---|---|---|
id | String | @id @default(cuid()) |
actor | String | user/email/serviço |
action | String | ex.: ORDER_CREATE, WEBHOOK |
key | String | recurso principal (eventKey/id) |
meta | String? | JSON stringificado (curto) |
createdAt | DateTime | @default(now()) |
userId | String? | FK opcional → User |
Índices & Performance (resumo)¶
- Hot paths:
OutboxJob:@@index([status, nextRunAt])para scheduler.Session:@@index([expiresAt])para GC.Nayax*: índices porrestaurantIdpara consultas por loja.- Unicidade crítica:
NayaxEvent.transactionKey(idempotência).OutboxJob (eventKey, action)(um job por ação/evento).Restaurant.slug,codeStoreNayax,codeStoreSaipos.
Boas Práticas de Migração¶
- UUID nativo: use
gen_random_uuid()(extensãopgcrypto) já prevista noRestaurant.id. - Índices antes de carga: crie índices para colunas de filtro (
status,nextRunAt,restaurantId) antes de popular. - Campos de auditoria: mantenha
createdById,receivedAt,processingAt,sentAtatualizados na camada de serviço. - Backfill seguro: para
Restaurant.codeStoreSaipos/Nayax, preencha em janelas pequenas e valide unicidade.
Snippet Prisma (datasource/client)¶
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
Checklist de deploy: variável
DATABASE_URL, extensãopgcrypto, permissões de schema, pooling habilitado (PGBouncer/Neon/RDS Proxy).