Pular para conteúdo

Modelo de Dados

Versão: 1.0

  • < Backward:
    • RF-001 a RF-024;
    • RNF-001 a RNF-003;
    • G-000 a G-014.7 (KAOS);
    • ASR-001 a ASR-015.

Modelo Entidade-Relacionamento (MER) — Diagrama ER Lógico

erDiagram
    Usuario ||--o{ Caderno : "possui"
    Usuario ||--o{ Sessao : "possui"
    Usuario ||--o{ Item : "possui (proprietário direto)"
    Caderno ||--o{ Item : "contém (quando classificado)"
    Item ||--o{ SubItem : "possui (apenas type=TODO)"
    Item |o--o| Item : "TODO associa-se a um SCHEDULE (scheduleId)"

    Usuario {
        uuid id PK
        string name
        string email UK
        string passwordHash
        string status "ATIVA | BLOQUEADA_TEMPORARIAMENTE | DESATIVADA | EXCLUIDA"
        int failedLoginAttempts "default 0"
        datetime lockedUntil "nullable"
        datetime createdAt
        datetime updatedAt
    }

    Sessao {
        uuid id PK
        uuid userId FK
        string refreshTokenHash
        string userAgent "nullable"
        datetime createdAt
        datetime revokedAt "nullable"
        datetime expiresAt
    }

    Caderno {
        uuid id PK
        uuid userId FK
        string name
        datetime createdAt
        datetime updatedAt
    }

    Item {
        uuid id PK
        uuid userId FK "proprietário direto - ver ADR-023"
        uuid notebookId FK "nullable, null = Inbox - ver ADR-020"
        string type "SCHEDULE | TODO (discriminador STI - ADR-004)"
        string title
        int priority "nullable, 1 a 4"
        string status "ATIVO | ARQUIVADO - ver ADR-024"
        boolean isCompleted "apenas TODO - ver ADR-024"
        datetime startAt "obrigatório se type=SCHEDULE"
        datetime endAt "obrigatório se type=SCHEDULE"
        datetime dueDate "nullable, apenas TODO"
        string recurrenceFrequency "nullable: DAILY|WEEKLY|MONTHLY"
        datetime recurrenceEndDate "nullable"
        datetime snoozedUntil "nullable - ver ADR-014 (Arquitetura)"
        uuid scheduleId FK "nullable, auto-relacionamento TODO->SCHEDULE - ver ADR-022"
        datetime createdAt
        datetime updatedAt
    }

    SubItem {
        uuid id PK
        uuid itemId FK "obrigatório, aponta para Item type=TODO"
        string title
        boolean isCompleted "default false"
        datetime createdAt
        datetime updatedAt
    }

Relacionamentos

Relacionamento Cardinalidade Descrição
Usuario — Caderno 1:N Um usuário possui zero ou mais cadernos
Usuario — Sessao 1:N Um usuário possui zero ou mais sessões
Usuario — Item 1:N Um usuário é proprietário direto de zero ou mais itens
Caderno — Item 1:N (opcional) Um caderno contém zero ou mais itens; um item pode não pertencer a nenhum caderno (Inbox)
Item — SubItem 1:N Um item (type=TODO) possui zero ou mais sub-itens
Item — Item 0..1:0..1 Um TODO pode associar-se opcionalmente a um SCHEDULE via scheduleId

Dicionário de Dados

Entidade: Usuario

Atributo Tipo Restrições Descrição
id UUID PK, NOT NULL Identificador único do usuário
name VARCHAR NOT NULL Nome do usuário
email VARCHAR PK alternativa (UK), NOT NULL, UNIQUE E-mail do usuário (usado como login)
passwordHash VARCHAR NOT NULL Hash da senha (Argon2id) — nunca armazenada em texto puro
status ENUM NOT NULL Estado da conta: ATIVA, BLOQUEADA_TEMPORARIAMENTE, DESATIVADA, EXCLUIDA
failedLoginAttempts INTEGER NOT NULL, DEFAULT 0 Contador de tentativas de login falhas consecutivas
lockedUntil TIMESTAMP NULLABLE Data/hora até a qual a conta permanece bloqueada
createdAt TIMESTAMP NOT NULL Data/hora de criação do registro
updatedAt TIMESTAMP NOT NULL Data/hora da última atualização

Entidade: Sessao

Atributo Tipo Restrições Descrição
id UUID PK, NOT NULL Identificador único da sessão
userId UUID FK → Usuario.id, NOT NULL, ON DELETE CASCADE Usuário proprietário da sessão
refreshTokenHash VARCHAR NOT NULL, INDEXED Hash do refresh token opaco
userAgent VARCHAR NULLABLE Identificação do dispositivo/navegador
createdAt TIMESTAMP NOT NULL Data/hora de criação da sessão
revokedAt TIMESTAMP NULLABLE Data/hora da revogação (NULL = sessão ativa)
expiresAt TIMESTAMP NOT NULL Data/hora de expiração natural do refresh token

Entidade: Caderno

Atributo Tipo Restrições Descrição
id UUID PK, NOT NULL Identificador único do caderno
userId UUID FK → Usuario.id, NOT NULL, ON DELETE CASCADE Usuário proprietário do caderno
name VARCHAR NOT NULL Nome do caderno
createdAt TIMESTAMP NOT NULL Data/hora de criação
updatedAt TIMESTAMP NOT NULL Data/hora da última atualização

Entidade: Item

Atributo Tipo Restrições Descrição
id UUID PK, NOT NULL Identificador único do item
userId UUID FK → Usuario.id, NOT NULL, ON DELETE CASCADE Proprietário direto (ADR-023)
notebookId UUID FK → Caderno.id, NULLABLE, ON DELETE SET NULL Caderno ao qual pertence; NULL = Inbox (ADR-020)
type ENUM NOT NULL Discriminador STI: SCHEDULE ou TODO (ADR-004)
title VARCHAR NOT NULL, INDEXED (GIN pg_trgm) Título do item
priority INTEGER NULLABLE, CHECK (1–4) Prioridade: 1 (Crítica) a 4 (Baixa); NULL = sem prioridade
status ENUM NOT NULL Estado no ciclo de vida: ATIVO ou ARQUIVADO (ADR-024)
isCompleted BOOLEAN NOT NULL, DEFAULT false Flag de conclusão — apenas para type=TODO (ADR-024)
startAt TIMESTAMP NULLABLE (obrigatório se SCHEDULE) Data/hora de início — obrigatório para SCHEDULE
endAt TIMESTAMP NULLABLE (obrigatório se SCHEDULE) Data/hora de término — obrigatório para SCHEDULE
dueDate TIMESTAMP NULLABLE Data limite — apenas para TODO
recurrenceFrequency ENUM NULLABLE Frequência de recorrência: DAILY, WEEKLY, MONTHLY
recurrenceEndDate TIMESTAMP NULLABLE Data de término da série recorrente
snoozedUntil TIMESTAMP NULLABLE Data/hora até a qual o item fica oculto (Snooze)
scheduleId UUID FK → Item.id (self), NULLABLE, ON DELETE SET NULL Auto-relacionamento TODO→SCHEDULE (ADR-022)
createdAt TIMESTAMP NOT NULL Data/hora de criação
updatedAt TIMESTAMP NOT NULL Data/hora da última atualização

Entidade: SubItem

Atributo Tipo Restrições Descrição
id UUID PK, NOT NULL Identificador único do sub-item
itemId UUID FK → Item.id, NOT NULL, ON DELETE CASCADE Item pai (obrigatoriamente type=TODO)
title VARCHAR NOT NULL Título/descrição do sub-item
isCompleted BOOLEAN NOT NULL, DEFAULT false Flag de conclusão do sub-item
createdAt TIMESTAMP NOT NULL Data/hora de criação
updatedAt TIMESTAMP NOT NULL Data/hora da última atualização

Rastreabilidade Backward por entidade

Entidade Backward
Usuario G-014, RF-016 a RF-024, RNF-003
Sessao G-014.3, RF-021, ASR-003, ADR-006
Caderno G-000, G-001, G-001.1, RF-001, RF-002
Item G-001.2, G-003 a G-012, RF-003, RF-005 a RF-011, RF-015, ASR-009, ASR-010
SubItem G-001.3, G-008.2, RF-003, RF-011

Decisões de Modelagem

  • ADR-020: Estado “Inbox” representado implicitamente por notebookId nulo, sem enum dedicado.
  • ADR-021: Deleção de Item como exclusão física (hard delete), sem estado “Deletado” persistido.
  • ADR-022: Auto-relacionamento unidirecional no Item (TODO → SCHEDULE) via scheduleId.
  • ADR-023: Proprietário direto (userId) no Item, independente do Caderno.
  • ADR-024: “Concluído” modelado como flag booleano (isCompleted), não como valor do enum status.

Notas de Implementação

Índices - Usuario.email — índice único (UK), usado em Login e Cadastro (RF-017, RF-018). - Item.userId, Item.notebookId, Item.status — índices simples para as queries transversais de Todo List, Planner e Calendário (ASR-013, ASR-014). - Item.title — índice GIN trigram (pg_trgm), restrito por WHERE type = 'TODO' na query de Busca (ADR-016). - Sessao.refreshTokenHash — índice para lookup rápido na renovação de Access Token (ADR-006).

Constraints e validações - Item.priority — CHECK (priority IS NULL OR priority BETWEEN 1 AND 4). - Obrigatoriedade condicional por tipo (startAt/endAt obrigatórios se type = SCHEDULE; dueDate/isCompleted só fazem sentido se type = TODO) não é imposta pelo schema — é responsabilidade da camada de domínio (Item rico, ADR-012), conforme trade-off já assumido em ADR-004. - SubItem.itemId deve referenciar exclusivamente um Item com type = TODO — validado no domínio (ASR-009), sem constraint declarativa no banco (Prisma/SQL não expressam “FK condicional por valor de outra coluna”). - Item.scheduleId deve referenciar exclusivamente um Item com type = SCHEDULE, e só pode ser preenchido em itens type = TODO — validado no domínio (ADR-022).

Cascata e integridade referencial - Usuario → Caderno, Usuario → Item, Usuario → Sessao: ON DELETE CASCADE (exclusão de conta, RF-023, ADR-010). - Caderno → Item: sem cascade automático — exclusão de Caderno seta notebookId = NULL explicitamente via transação de aplicação (RF-011, ADR-010), preservando o item na Inbox. - Item → SubItem: ON DELETE CASCADE (deleção de TODO remove seus sub-itens, G-008.2, TASK-023). - Item → Item (via scheduleId): ON DELETE SET NULL — deletar um SCHEDULE não deve impedir nem forçar a deleção do TODO associado; o vínculo simplesmente se desfaz.

Outras observações - Recorrência (recurrenceFrequency, recurrenceEndDate) não gera tabela de instâncias — cálculo sob demanda via rrule, decisão já registrada em ADR-013 (Arquitetura); os dois campos aqui apenas persistem a regra-base. - Sessao.revokedAt nulo = sessão ativa; logout individual (RF-021) seta revokedAt; revogação em massa (ADR-007) atualiza todas as linhas do userId, exceto a sessão corrente quando aplicável (troca de senha). - Usuario.failedLoginAttempts/lockedUntil implementam o bloqueio por conta (ADR-009); o throttler por IP (ADR-009) é infraestrutural e não persiste dado neste modelo.