Pular para conteúdo

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: SetNull para manter histórico mesmo após remoção da loja.

Itens relacionados

  • NayaxItem: itens do evento; com modifiers, 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 por restaurantId para 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

  1. UUID nativo: use gen_random_uuid() (extensão pgcrypto) já prevista no Restaurant.id.
  2. Índices antes de carga: crie índices para colunas de filtro (status, nextRunAt, restaurantId) antes de popular.
  3. Campos de auditoria: mantenha createdById, receivedAt, processingAt, sentAt atualizados na camada de serviço.
  4. 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ão pgcrypto, permissões de schema, pooling habilitado (PGBouncer/Neon/RDS Proxy).