Skip to content

Formação — Schema (PR-1)

SSOT do modelo de dados da seção Formação após a re-arquitetura. ADRs relacionadas: ADR-003 (payload dual-format), ADR-018 (Least Privilege).

Tabelas

cursos (existente — estendida)

CampoTipoNotas
iduuid PK
titulotext
descricaotext
categoria_visualtext
imagem_urltext (novo)Referência cursos-imagens/<path> no bucket privado.
video_intro_urltext (novo)URL externa (YouTube/Vimeo).
statustext (novo)publicado | em_breve | rascunho — trigger fn_validar_status_curso.
ativobooleanSoft delete legado.
criado_poruuidFK usuarios.id (especialista).

curso_modulos (novo — PR-1)

CampoTipoNotas
iduuid PK
curso_iduuid FKON DELETE CASCADE.
titulotext
descricaotextnullable
ordemintUNIQUE(curso_id, ordem) — reorder em duas fases.
criado_em / atualizado_emtimestamptz

cursos_videos (existente — estendida)

CampoTipoNotas
modulo_iduuid FKNOT NULL, ON DELETE RESTRICT. UNIQUE(modulo_id, ordem).

Backfill PR-1: cada curso ganhou "Módulo 01" e todos os vídeos existentes foram realocados; 0 órfãos.

trilhas (novo — globais)

CampoTipoNotas
iduuid PK
titulotext
descricaotextnullable
ordemintUNIQUE(ordem)
ativoboolean

trilha_cursos (novo — junção)

CampoTipoNotas
trilha_iduuid FKON DELETE CASCADE
curso_iduuid FKON DELETE CASCADE
ordemintUNIQUE(trilha_id, ordem), PRIMARY KEY(trilha_id, curso_id)

RLS — resumo (ADR-018)

Todas as policies:

  • USING / WITH CHECK baseiam-se em current_role_is('especialista') (CUD) ou current_role_is_any('especialista','coordenador') (SELECT em publicados).
  • Administrador é explicitamente negado (sem policy permissiva).

Storage

  • Bucket cursos-imagensprivado. WRITE só especialista; READ via signed URL pela edge function cursos-imagens-signed-url (especialista + coordenador).

Triggers

  • fn_validar_status_cursoBEFORE INSERT/UPDATE ON cursos. Lança 22023 se status NOT IN ('publicado','em_breve','rascunho').

Constraints & integridade (estado em produção)

Validado em PR-7 via pg_constraint (não alterado por PR-7 — apenas consolidado em doc):

TabelaConstraintTipoDefinição
curso_moduloscurso_modulos_curso_id_fkeyFK(curso_id) REFERENCES cursos(id) ON DELETE CASCADE
curso_moduloscurso_modulos_curso_id_ordem_keyUNIQUE(curso_id, ordem) — reorder two-phase
cursos_videoscursos_videos_modulo_id_fkeyFK(modulo_id) REFERENCES curso_modulos(id) ON DELETE RESTRICT — apagar módulo com aula vinculada falha
cursos_videoscursos_videos_curso_id_fkeyFK(curso_id) REFERENCES cursos(id) ON DELETE CASCADE
cursos_videoscursos_videos_modulo_ordem_uniqueUNIQUE(modulo_id, ordem) — reorder por módulo
cursos_videoscursos_videos_ordem_positiveCHECKordem >= 1
cursos_videoscursos_videos_avaliacao_rangeCHECKavaliacao_media BETWEEN 0 AND 5
cursos_videoscursos_videos_total_avaliacoes_nonnegCHECKtotal_avaliacoes >= 0
cursos_videoscursos_videos_visualizacoes_nonnegCHECKvisualizacoes >= 0
trilha_cursosPK compostaPK(trilha_id, curso_id)
trilha_cursostrilha_cursos_trilha_id_fkeyFK(trilha_id) REFERENCES trilhas(id) ON DELETE CASCADE
trilha_cursostrilha_cursos_curso_id_fkeyFK(curso_id) REFERENCES cursos(id) ON DELETE CASCADE
trilha_cursostrilha_cursos_trilha_id_ordem_keyUNIQUE(trilha_id, ordem)

Semântica das cascatas:

  • Apagar curso → cascade em curso_modulos, cursos_videos, trilha_cursos.
  • Apagar módulo → bloqueado se houver aula (RESTRICT); UI deve mover/excluir aulas antes.
  • Apagar trilha → remove vínculos em trilha_cursos (cursos permanecem).