Backend - Estrutura do Banco de Dados¶
Este documento descreve a estrutura do banco de dados PostgreSQL, incluindo tabelas, relacionamentos, índices e constraints.
Índice¶
Visão Geral¶
Banco de Dados: PostgreSQL 12+
ORM: GORM v1.25.5
Driver: pgx/v5
Localização da Configuração: internal/database/database.go
Características¶
- Soft Deletes: Maioria das tabelas suporta soft delete (
deleted_at) - Timestamps:
created_at,updated_atautomáticos - JSONB: Usado para campos complexos (arrays, metadados)
- DATE: Tipo DATE para datas civis (não TIMESTAMP)
Tabelas Principais¶
users¶
Tabela principal de usuários.
Campos Principais:
- id - Primary Key
- username - Opcional, apenas para exibição
- email - Único, usado para login
- password - Hash bcrypt
- name - Nome completo
- user_type - 'client', 'professional', 'admin'
- is_active - Se está ativo
- email_verified - Se email foi verificado
- email_verified_at - Data de verificação
Índices:
- email - Unique Index
- username - Index (permite NULLs múltiplos)
Relacionamentos:
- 1:N com accounts
- 1:N com subscriptions
- 1:N com cycles
- 1:N com observations
- 1:1 com client_profiles
- 1:1 com professional_profiles
cycles¶
Ciclos menstruais.
Campos Principais:
- id - Primary Key
- user_id - Foreign Key → users
- start_date - DATE (primeiro dia de sangramento)
- end_date - DATE (opcional, último dia)
- length - Duração em dias
- is_active - Se está ativo
- sync_id - Para sincronização mobile
Índices:
- user_id - Index
- sync_id - Index
Constraints: - Apenas um ciclo ativo por usuário (lógica de negócio)
Relacionamentos:
- N:1 com users
- 1:N com observations
- 1:N com cycle_comments
observations¶
Observações diárias do ciclo.
Campos Principais:
- id - Primary Key
- user_id - Foreign Key → users
- cycle_id - Foreign Key → cycles (opcional)
- date - DATE (data da observação)
- type - Tipo de observação
- sensation - Sensação física
- symbol_id - Foreign Key → symbol
- appearance_id - Foreign Key → appearances
- had_intercourse - Teve relação sexual
- is_first_day_of_menstruation - Primeiro dia da menstruação
- professional_notes - Notas do profissional
- sync_id - Para sincronização mobile
Índices:
- user_id - Index
- cycle_id - Index
- symbol_id - Index
- appearance_id - Index
- sync_id - Index
Constraints: - Apenas uma observação por dia por usuário (lógica de negócio)
Relacionamentos:
- N:1 com users
- N:1 com cycles
- N:1 com symbol
- N:1 com appearances
symbol¶
Símbolos do Método Billings.
Campos Principais:
- id - Primary Key
- name - Nome único do símbolo
- description - Descrição
- image_path - Caminho da imagem (R2 ou local)
- relation_image_path - Imagem para relação sexual
Índices:
- name - Unique Index
Relacionamentos:
- 1:N com observations
sensations¶
Sensações físicas.
Campos Principais:
- id - Primary Key
- name - Nome único
- display_name - Nome para exibição
- description - Descrição
- is_active - Se está ativa
- order - Ordem de exibição
Índices:
- name - Unique Index
appearances¶
Aparências visuais do muco.
Campos Principais:
- id - Primary Key
- name - Nome único
- display_name - Nome para exibição
- description - Descrição
- is_active - Se está ativa
- order - Ordem de exibição
Índices:
- name - Unique Index
Relacionamentos:
- 1:N com observations
day_based_rules¶
Regras baseadas em dias do ciclo.
Campos Principais:
- id - Primary Key
- name - Nome da regra
- description - Descrição
- min_day - Dia mínimo (inclusive)
- max_day - Dia máximo (inclusive)
- allowed_symbol_ids - JSONB array de IDs permitidos
- restricted_symbol_ids - JSONB array de IDs restritos
- is_active - Se está ativa
- order - Ordem de aplicação
Tipos Especiais:
- allowed_symbol_ids - JSONB (ex: [1, 2, 3])
- restricted_symbol_ids - JSONB (ex: [4, 5])
client_profiles¶
Perfis detalhados de clientes.
Campos Principais:
- id - Primary Key
- user_id - Foreign Key → users (Unique)
- full_name - Nome completo
- birth_date - DATE
- objective - Objetivo (engravidar, espaçar, conhecimento)
- first_menstruation_date - DATE (menarca)
- average_cycle_length - Duração média do ciclo
- woman_pattern - Padrão Básico de Infertilidade (PBI)
- pbi_sensation_id - Foreign Key → sensations
- pbi_appearance_id - Foreign Key → appearances
- profile_photo_key - Chave da foto no R2
Índices:
- user_id - Unique Index
Constraints:
- user_id é único (garante 1:1)
Relacionamentos:
- 1:1 com users
professional_profiles¶
Perfis detalhados de profissionais.
Campos Principais:
- id - Primary Key
- user_id - Foreign Key → users (Unique)
- full_name - Nome completo
- status - 'pending', 'approved', 'rejected'
- approved_by - ID do admin que aprovou
- approved_at - Data de aprovação
- rejection_reason - Motivo da rejeição
- profile_photo_key - Chave da foto no R2
- professional_certificate_key - Chave do certificado
- document_key - Chave do documento
Índices:
- user_id - Unique Index
Constraints:
- user_id é único (garante 1:1)
Relacionamentos:
- 1:1 com users
professional_patients¶
Vínculos entre profissionais e pacientes.
Campos Principais:
- id - Primary Key
- professional_id - Foreign Key → users
- patient_id - Foreign Key → users (Unique)
- status - 'pending', 'approved', 'rejected'
Índices:
- professional_id - Index
- patient_id - Unique Index
Constraints:
- patient_id é único (garante 1 profissional por paciente)
Relacionamentos:
- N:1 com users (professional)
- N:1 com users (patient)
subscriptions¶
Assinaturas de usuários.
Campos Principais:
- id - Primary Key
- user_id - Foreign Key → users
- plan_id - Foreign Key → plans (opcional)
- name - Nome da assinatura
- amount - Valor
- status - 'active', 'inactive', 'cancelled'
- start_date - DATE
- end_date - DATE (opcional)
- billing_cycle - 'monthly', 'six_months', 'yearly'
- payment_gateway_id - ID no gateway
Índices:
- user_id - Index
- plan_id - Index
Relacionamentos:
- N:1 com users
- N:1 com plans
- 1:N com payments
payments¶
Pagamentos.
Campos Principais:
- id - Primary Key
- user_id - Foreign Key → users
- subscription_id - Foreign Key → subscriptions (opcional)
- amount - Valor
- status - 'pending', 'completed', 'failed', 'cancelled'
- payment_date - DATE
- gateway_provider - Provedor (stripe, pagseguro, etc.)
- gateway_id - ID no gateway
- gateway_response - Resposta completa do gateway
Índices:
- user_id - Index
- subscription_id - Index
- gateway_id - Index
Relacionamentos:
- N:1 com users
- N:1 com subscriptions
plans¶
Planos de assinatura.
Campos Principais:
- id - Primary Key
- name - Nome do plano
- description - Descrição
- status - 'active', 'inactive'
- can_use_app - Pode usar app
- can_use_web - Pode usar web
- can_access_reports - Pode acessar relatórios
- can_access_api - Pode acessar API
- price_monthly - Preço mensal
- price_six_months - Preço semestral
- price_yearly - Preço anual
Relacionamentos:
- 1:N com subscriptions
messages¶
Mensagens entre usuários.
Campos Principais:
- id - Primary Key
- sender_id - Foreign Key → users
- receiver_id - Foreign Key → users
- content - Conteúdo da mensagem
- attachment_url - URL de anexo
- is_read - Se foi lida
- read_at - Data de leitura
Índices:
- sender_id - Index
- receiver_id - Index
Relacionamentos:
- N:1 com users (sender)
- N:1 com users (receiver)
appointments¶
Agendamentos entre profissional e paciente.
Campos Principais:
- id - Primary Key
- professional_id - Foreign Key → users
- patient_id - Foreign Key → users
- scheduled_at - TIMESTAMP (data/hora)
- duration - Duração em minutos
- status - 'pending_confirmation', 'scheduled', 'completed', 'cancelled', 'rejected'
- video_call_url - URL da videochamada
- meeting_id - ID da reunião
- notes - Notas
- rejection_reason - Motivo da rejeição
Índices:
- professional_id - Index
- patient_id - Index
Relacionamentos:
- N:1 com users (professional)
- N:1 com users (patient)
notifications¶
Notificações do sistema.
Campos Principais:
- id - Primary Key
- user_id - Foreign Key → users
- type - Tipo ('message', 'appointment', 'task', 'reminder', 'system')
- title - Título
- message - Mensagem
- is_read - Se foi lida
- read_at - Data de leitura
- metadata - JSONB (metadados adicionais)
- reference_type - Tipo de referência
- reference_id - ID da referência
Índices:
- user_id - Index
Tipos Especiais:
- metadata - JSONB (ex: {"key": "value"})
Relacionamentos:
- N:1 com users
cycle_comments¶
Comentários de profissionais em ciclos.
Campos Principais:
- id - Primary Key
- cycle_id - Foreign Key → cycles
- professional_id - Foreign Key → users
- content - Conteúdo do comentário
Índices:
- cycle_id - Index
- professional_id - Index
Relacionamentos:
- N:1 com cycles
- N:1 com users (professional)
user_auth_providers¶
Identidades OAuth associadas a usuários.
Campos Principais:
- id - Primary Key
- user_id - Foreign Key → users
- provider - 'google', 'apple'
- provider_user_id - ID do usuário no provedor (sub do token)
- email - Email (opcional, apenas informativo)
Índices:
- user_id - Index
- provider - Index
Constraints:
- provider_user_id deve ser único por provider
Relacionamentos:
- N:1 com users
email_verifications¶
Tokens de verificação de email.
Campos Principais:
- id - Primary Key
- token - Token único
- user_id - Foreign Key → users
- expires_at - TIMESTAMP
- used - Se foi usado
- used_at - Data de uso
- last_resend_at - Último reenvio
Índices:
- token - Unique Index
- user_id - Index
- last_resend_at - Index
Relacionamentos:
- N:1 com users
password_resets¶
Tokens de reset de senha.
Campos Principais:
- id - Primary Key
- token - Token único
- user_id - Foreign Key → users
- expires_at - TIMESTAMP
- used - Se foi usado
- used_at - Data de uso
Índices:
- token - Unique Index
- user_id - Index
Relacionamentos:
- N:1 com users
accounts¶
Contas financeiras genéricas.
Campos Principais:
- id - Primary Key
- user_id - Foreign Key → users
- name - Nome da conta
- amount - Valor
- due_date - DATE
- is_paid - Se foi pago
- sync_id - Para sincronização mobile
Índices:
- user_id - Index
- sync_id - Index
Relacionamentos:
- N:1 com users
professional_bank_accounts¶
Contas bancárias de profissionais.
Campos Principais:
- id - Primary Key
- professional_id - Foreign Key → users (Unique)
- bank_name - Nome do banco
- account_type - 'checking', 'savings'
- agency - Agência
- account_number - Número da conta
- cpf_or_cnpj - CPF ou CNPJ
Índices:
- professional_id - Unique Index
Constraints:
- professional_id é único (garante 1 conta por profissional)
Relacionamentos:
- 1:1 com users (professional)
availability_slots¶
Slots de disponibilidade de profissionais.
Campos Principais:
- id - Primary Key
- professional_id - Foreign Key → users
- date - DATE
- start_time - VARCHAR(5) (HH:MM)
- end_time - VARCHAR(5) (HH:MM)
- is_available - Se está disponível
Índices:
- professional_id - Index
Relacionamentos:
- N:1 com users (professional)
work_schedules¶
Horários de trabalho de profissionais.
Campos Principais:
- id - Primary Key
- professional_id - Foreign Key → users (Unique)
- days_of_week - TEXT (JSON array)
- start_time - VARCHAR(5) (HH:MM)
- end_time - VARCHAR(5) (HH:MM)
- appointment_duration - Duração em minutos
- lunch_start_time - VARCHAR(5) (HH:MM)
- lunch_end_time - VARCHAR(5) (HH:MM)
Índices:
- professional_id - Unique Index
Constraints:
- professional_id é único (garante 1 horário por profissional)
Relacionamentos:
- 1:1 com users (professional)
courses¶
Cursos do marketplace (metadados editoriais e publicação).
Campos Principais:
- id - Primary Key
- professional_id - Profissional dono do curso
- title - Título
- description - Descrição
- syllabus - Ementa
- learning_objectives - Objetivos de aprendizagem (JSONB)
- target_audience - Público alvo
- prerequisites - Pré-requisitos
- video_url - URL do vídeo
- thumbnail_url - URL da thumbnail
- duration - Duração em minutos
- order - Ordem de exibição
- price_amount - Preço
- currency - Moeda (BRL)
- access_type - Tipo de acesso (lifetime|timed)
- access_days - Dias de acesso quando timed
- status - Estado editorial (draft|in_review|published|rejected|unpublished)
- rejection_reason - Motivo de rejeição
course_modules¶
Módulos do curso.
Campos Principais:
- id - Primary Key
- course_id - FK para courses
- title - Título do módulo
- order - Ordem no curso
course_lessons¶
Aulas dos módulos do curso, incluindo estado do pipeline de vídeo.
Campos Principais:
- id - Primary Key
- course_id - FK para courses
- module_id - FK para course_modules
- title - Título da aula
- description - Descrição
- mux_upload_id - ID de upload direto no Mux
- mux_asset_id - ID do asset no Mux
- mux_playback_id - ID de playback no Mux
- video_status - pending_upload|processing|ready|errored
- video_error_reason - Motivo de erro quando houver
- processing_started_at - Início do processamento
- ready_at - Momento em que o vídeo ficou pronto
- last_mux_event_at - Último evento Mux processado
- duration_sec - Duração em segundos
- is_preview - Aula de preview
- order - Ordem no módulo
video_webhook_events¶
Eventos de webhook de vídeo (Mux) com controle de idempotência e retries.
Campos Principais:
- provider - Provedor (mux)
- event_id - ID único do evento no provedor
- event_type - Tipo do evento
- status - pending|processed|failed|dead_letter
- retry_count - Quantidade de tentativas
- max_retries - Limite de tentativas
- last_error - Último erro no processamento
- payload_hash - Hash do payload
- payload - Payload bruto (JSONB)
- processed_at - Data/hora de processamento
- dead_lettered_at - Data/hora de ida para dead-letter
course_enrollments¶
Matrículas de usuários em cursos.
Campos Principais:
- id - Primary Key
- user_id - Comprador/aluno
- course_id - Curso
- course_order_id - Pedido que originou a matrícula
- status - active|expired|revoked|refunded
- access_type - lifetime|timed
- started_at - Início do acesso
- ends_at - Fim do acesso (quando timed)
- revoked_at - Revogação
course_lesson_progress¶
Progresso por aula e matrícula.
Campos Principais:
- enrollment_id - Matrícula
- lesson_id - Aula
- progress_pct - Percentual de progresso
- watched_seconds - Segundos assistidos
- last_position_seconds - Última posição no player
- is_watched - Marcador de aula assistida
- last_seen_at - Última visualização
- completed_at - Conclusão da aula
sync_logs¶
Logs de sincronização mobile.
Campos Principais:
- id - Primary Key
- user_id - Foreign Key → users
- device_id - ID do dispositivo
- sync_type - Tipo ('push', 'pull')
- status - Status ('success', 'error')
- records_count - Quantidade de registros
- error - Erro (se houver)
- synced_at - TIMESTAMP
Índices:
- user_id - Index
- device_id - Index
Relacionamentos:
- N:1 com users
settings¶
Configurações globais do sistema.
Campos Principais: - Apenas campos padrão (ID, CreatedAt, UpdatedAt, DeletedAt)
Nota: Estrutura base para configurações futuras.
Relacionamentos¶
Diagrama de Relacionamentos¶
erDiagram
users ||--o{ cycles : "tem"
users ||--o| client_profiles : "tem"
users ||--o| professional_profiles : "tem"
users ||--o{ observations : "faz"
users ||--o{ subscriptions : "tem"
users ||--o{ messages : "envia/recebe"
users ||--o{ appointments : "agenda"
users ||--o{ notifications : "recebe"
users ||--o{ user_auth_providers : "tem"
users ||--o{ professional_patients : "professional"
users ||--o{ professional_patients : "patient"
users ||--o| professional_bank_accounts : "tem"
users ||--o| work_schedules : "tem"
users ||--o{ availability_slots : "tem"
cycles ||--o{ observations : "contém"
cycles ||--o{ cycle_comments : "tem"
plans ||--o{ subscriptions : "gera"
subscriptions ||--o{ payments : "tem"
symbol ||--o{ observations : "usado_em"
appearances ||--o{ observations : "usado_em"
sensations ||--o{ client_profiles : "pbi_sensation"
appearances ||--o{ client_profiles : "pbi_appearance"
Relacionamentos 1:1¶
users↔client_profiles(viauser_idúnico)users↔professional_profiles(viauser_idúnico)users↔professional_bank_accounts(viaprofessional_idúnico)users↔work_schedules(viaprofessional_idúnico)
Relacionamentos 1:N¶
users→cyclesusers→observationsusers→subscriptionsusers→messages(sender e receiver)users→appointments(professional e patient)cycles→observationscycles→cycle_commentsplans→subscriptionssubscriptions→payments
Relacionamentos N:M¶
users↔users(viaprofessional_patients)- Um profissional pode ter vários pacientes
- Um paciente pode estar com apenas um profissional
Índices¶
Índices Únicos¶
users.email- Unique Indexusers.username- Index (permite múltiplos NULLs)symbol.name- Unique Indexsensations.name- Unique Indexappearances.name- Unique Indexemail_verifications.token- Unique Indexpassword_resets.token- Unique Indexclient_profiles.user_id- Unique Indexprofessional_profiles.user_id- Unique Indexprofessional_patients.patient_id- Unique Indexprofessional_bank_accounts.professional_id- Unique Indexwork_schedules.professional_id- Unique Index
Índices de Performance¶
observations.user_id- Para queries por usuárioobservations.cycle_id- Para queries por cicloobservations.date- Para queries por datacycles.user_id- Para buscar ciclos do usuáriocycles.is_active- Para buscar ciclo ativomessages.sender_id- Para buscar mensagens enviadasmessages.receiver_id- Para buscar mensagens recebidasappointments.professional_id- Para buscar agendamentos do profissionalappointments.patient_id- Para buscar agendamentos do paciente
Índices Compostos¶
Alguns índices compostos podem ser criados para otimizar queries específicas:
-- Exemplo: Buscar observações por usuário e data
CREATE INDEX idx_observations_user_date ON observations(user_id, date);
Constraints¶
Foreign Keys¶
Todas as foreign keys são gerenciadas pelo GORM:
UserID uint `gorm:"index;not null" json:"user_id"`
User User `gorm:"foreignKey:UserID" json:"user,omitempty"`
Unique Constraints¶
Campos únicos:
- users.email
- symbol.name
- sensations.name
- appearances.name
- client_profiles.user_id
- professional_profiles.user_id
- professional_patients.patient_id
Unique condicional:
- users.username - Permite múltiplos NULLs (via migração SQL)
Check Constraints¶
Alguns constraints podem ser adicionados via migrações SQL:
-- Exemplo: Validar user_type
ALTER TABLE users ADD CONSTRAINT check_user_type
CHECK (user_type IN ('client', 'professional', 'admin'));
Not Null Constraints¶
Campos obrigatórios são definidos nos modelos:
Email string `gorm:"not null" json:"email"`
Name string `gorm:"not null" json:"name"`
Tipos de Dados Especiais¶
DATE¶
Tipo DATE para datas civis (sem hora/timezone).
Uso:
- cycles.start_date
- cycles.end_date
- observations.date
- client_profiles.birth_date
- subscriptions.start_date
- subscriptions.end_date
- availability_slots.date
Modelo Go:
type Date struct {
time.Time
}
Serialização JSON: "YYYY-MM-DD"
JSONB¶
Tipo JSONB para dados estruturados.
Uso:
- day_based_rule.allowed_symbol_ids - Array de IDs: [1, 2, 3]
- day_based_rule.restricted_symbol_ids - Array de IDs: [4, 5]
- notifications.metadata - Objeto JSON: {"key": "value"}
- work_schedules.days_of_week - Array de números: [1, 2, 3, 4, 5]
Modelo Go:
type UIntArray []uint // Para arrays de uint
type NotificationMetadata map[string]interface{} // Para objetos
Soft Deletes¶
Maioria das tabelas suporta soft delete:
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
Comportamento:
- GORM filtra automaticamente registros deletados
- Use Unscoped() para incluir deletados
Estrutura de Tabelas¶
Padrão de Campos¶
Todas as tabelas têm campos padrão:
id SERIAL PRIMARY KEY
created_at TIMESTAMP NOT NULL DEFAULT NOW()
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
deleted_at TIMESTAMP NULL -- Soft delete
Nomenclatura¶
Tabelas:
- Nome no singular (GORM pluraliza automaticamente)
- Exceção: symbol (tabela nomeada explicitamente)
Colunas:
- snake_case
- Foreign keys: {model}_id (ex: user_id, cycle_id)
Queries Comuns¶
Buscar Ciclo Ativo¶
SELECT * FROM cycles
WHERE user_id = ? AND is_active = true
LIMIT 1;
Buscar Observações do Ciclo¶
SELECT * FROM observations
WHERE cycle_id = ?
ORDER BY date ASC;
Buscar Observação do Dia¶
SELECT * FROM observations
WHERE user_id = ? AND date = ?
LIMIT 1;
Buscar Pacientes do Profissional¶
SELECT u.*, pp.status
FROM users u
INNER JOIN professional_patients pp ON u.id = pp.patient_id
WHERE pp.professional_id = ? AND pp.status = 'approved';
Buscar Perfis Pendentes¶
SELECT * FROM professional_profiles
WHERE status = 'pending'
ORDER BY created_at ASC;
Performance¶
Índices Importantes¶
Para otimizar queries frequentes:
-
Observações por usuário e data:
CREATE INDEX idx_observations_user_date ON observations(user_id, date); -
Ciclos ativos:
CREATE INDEX idx_cycles_user_active ON cycles(user_id, is_active) WHERE is_active = true; -
Mensagens não lidas:
CREATE INDEX idx_messages_receiver_unread ON messages(receiver_id, is_read) WHERE is_read = false;
Otimizações Futuras¶
- Particionamento: Tabelas grandes podem ser particionadas por data
- Materialized Views: Para estatísticas complexas
- Full-Text Search: Para busca de texto (mensagens, comentários)
Backup e Restore¶
Backup¶
pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME > backup.sql
Restore¶
psql -h $DB_HOST -U $DB_USER -d $DB_NAME < backup.sql
Backup com Compressão¶
pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME | gzip > backup.sql.gz