Pular para o conteúdo

Histórico

Changelog

Derivado de Keep a Changelog e SemVer.

Não lançado

Em desenvolvimento

Added

  • Página `/conta` com DSAR self-service: download do export de dados (route handler com `Content-Disposition` e `no-store`) e exclusão de conta com confirmação digitada validada no servidor; link "Minha conta" no dashboard. E2E `dsar.spec.ts` + baseline a11y. (#11)
  • Playback de vídeo Bunny Stream: `BunnyService` com Token Authentication HMAC-SHA256 (`node:crypto`, fixado em unit test com fixture), endpoint unificado `GET /videos/lesson/:lessonId/playback` com validação de matrícula, componente `VideoPlayer` com hls.js dinâmico, renovação de token e estados de processamento do `video_jobs`. (#9)
  • Schema de billing agnóstico de provedor (ADR 0016): tabelas `plans`, `subscriptions`, `payments` com RLS FORCE, FK composta anti-cross-tenant, grants por coluna e testes de isolamento; decisão de provedor adiada. (#8)
  • Testes de isolamento RLS para `badge_classes` e `certificates` — 100% das tabelas com `tenant_id` cobertas. (#5)
  • E2E para fluxos críticos: quiz (aprovação/reprovação/tentativas), matrícula via catálogo e gestão de membros admin. (#10)
  • Coverage v8 no CI com artifact e thresholds ratchet; unit tests em `packages/shared` (sanitizeContentRef, schemas Zod), `packages/auth` (CASL) e `apps/web` (PKCE/OIDC); job `ai-workers-lint` (ruff). (#6)

Fixed

  • Build standalone do Next.js não copiava `.next/static`/`public`: o servidor E2E respondia 404 para todos os chunks e nenhuma página hidratava — testes de cliente eram impossíveis. Alinhado ao comportamento do Dockerfile de produção. (#10)
  • Job Semgrep quebrado silenciosamente: a imagem `latest` (1.164.0) falha na validação de regras do registry e o `continue-on-error` mascarava; imagem pinada em `semgrep/semgrep:1.116.0`. (#7)
  • Duas violações sérias de contraste (WCAG AA) em `/app` e `/cursos`, detectáveis após a correção de hidratação. (#10)
  • `PRIVACY.md` descrevia export/exclusão com comportamento que o código não tem (email assíncrono; anonimização com agregados); alinhado ao tratamento real e ao canal `/conta`. (#11)

Security

  • Jobs Trivy e Semgrep do CI tornados bloqueantes após triagem completa (Trivy: 0 CVEs HIGH/CRITICAL; Semgrep: 2 findings resolvidos). (#7)
  • `verify-publish-guards.cjs`: `execSync` com interpolação de path trocado por `execFileSync` sem shell (elimina injeção de comando). (#7)
  • Endpoint unificado de playback valida matrícula ativa do aluno — o endpoint antigo validava apenas tenant. (#9)

Changed

  • RELEASE_NOTES.md atualizado com backfill narrativo v0.1.0 → v1.0.0. (#6)
  • Links de comparação do changelog apontam para o repositório real e incluem a v1.0.0.

v1.0.0

Added

  • Fase 1: Quiz authoring + sanitização P0 — `sanitizeContentRef` em `packages/shared/lib/quiz.ts`, `QuizEditor` client component, `POST /enrollments/:id/quiz/:lessonId/submit`, `QuizPlayer` com score server-side.
  • Fase 2: Certificado imprimível — rota `/certificates/:id/print` com QR code gerado server-side (biblioteca `qrcode`), layout A4 landscape, `PrintButton` client component, link "Imprimir / Salvar PDF" na página de matrículas.
  • Fase 3: Catálogo público no marketing — `GET /courses/public/:slug` sem auth, `apps/marketing/src/lib/api.ts`, `trilhas.astro` com SSG + fallback, `trilhas/[slug].astro` com `getStaticPaths()`, `API_BASE_URL` como build arg no Dockerfile.
  • Fase 4: Observabilidade básica — bootstrap OTEL com `NodeSDK`, `GET /api/v1/health/live` e `/health/ready` (ping pg + redis), `GET /metrics` Prometheus via `@willsoto/nestjs-prometheus`.
  • Fase 5: Backup automatizado — container `capacita-backup` com `pg_dump | gzip | gpg AES-256 | mc`, retenção 14 dias, cron configurável via `BACKUP_SCHEDULE_CRON`.
  • Fase 6: Rate limiting com Redis — `ThrottlerStorageRedisService`, overrides por endpoint: data-export/delete-account (5/h), invite (30/min), upload-url (20/min).
  • Fase 7: CLI de criação de tenant — `pnpm tenant:create --name --admin-email` via `apps/api/src/cli/tenant-create.ts`.
  • Fase 8: Thumbnails de curso — migration `0013`, `POST /courses/:id/thumbnail/upload-url`, `PATCH /courses/:id/thumbnail`, `ThumbnailUpload` component no admin.
  • Fase 9: Smoke E2E Playwright — `auth.spec.ts`, `admin-publish.spec.ts`, `learner-complete.spec.ts`; mock API com novas rotas admin e quiz.

Changed

  • Headers de segurança: HSTS 1 ano + preload, X-Frame-Options DENY, Referrer-Policy strict-origin-when-cross-origin via `@fastify/helmet`.
  • Docker-compose healthcheck da API atualizado para `/api/v1/health/ready`.

Removed

  • Meilisearch removido de todos os compose files (ver ADR 0013 — adiado para catálogo > 200 cursos).

v0.3.0

Added

  • Pipeline de vídeo self-hosted: upload via URL pré-assinada MinIO → BullMQ → worker ffmpeg 720p libx264 → bucket de assets; `VideoUploadWidget` no admin com fases idle/uploading/processing/ready.
  • `VideoUploadWidget` (Client Component): barra de progresso XHR, polling de status a cada 4s, reprodução inline após transcodificação.
  • Módulo `media` (NestJS): `S3Service` (presigned PUT/GET, MinIO `forcePathStyle`), `MediaService` (BullMQ queue), endpoints `POST /videos/upload-url`, `POST /videos/:jobId/process`, `GET /videos/:jobId/status`, `GET /videos/:jobId/playback-url`.
  • `apps/api/src/worker.ts`: worker BullMQ independente, concorrência 2, download MinIO → ffmpeg → upload, atualiza `video_jobs` e `lessons.contentRef`.
  • Migration `0010`: tabela `video_jobs` com enum `video_job_status` (`pending`, `processing`, `ready`, `error`) e chave estrangeira para `lessons`.
  • Migration `0009`: colunas `deleted_at` (soft-delete) em `enrollments` e `certificates`.
  • Soft-delete de matrículas: endpoint `DELETE /enrollments/admin/:id` (admin) marca `deleted_at`; queries de listagem filtram com `isNull(deletedAt)`.
  • Member CRUD: `POST /admin/members/invite` (convite por e-mail + criação de usuário), `PATCH /admin/members/:userId/role`, `DELETE /admin/members/:userId` com guarda de último `tenant_admin`.
  • Formulário de convite, seletor de papel e botão de remoção na página `/admin/members`.
  • `InviteMemberInputSchema`, `UpdateMemberRoleInputSchema`, `MemberRoleSchema` em `@capacita/shared`.
  • Stitching de convite no login: `upsertFromJwt` passa a conflitar em `email` (não em `zitadelSubject`), vinculando identidade Zitadel ao usuário pré-criado pelo convite.
  • Reordenação de módulos e aulas no admin: botões ↑/↓ com endpoints `PATCH .../position` e algoritmo swap com clamp (ADR 0011).
  • Exclusão de módulo e aula no admin: endpoints `DELETE` e botão ✕ com confirmação visual no painel.
  • Página `/admin/members` listando membros do tenant com papel e data de entrada, via `GET /api/v1/identity/admin/members`.
  • Console B2B: dashboard com visão por curso, lista de matrículas com busca por nome/e-mail no admin.
  • Newsletter opt-in: endpoint público `POST /api/v1/newsletter/subscribe`, página marketing `/newsletter`, tabela `newsletter_subscribers` com DSAR completo (export + delete).
  • Página marketing `/descadastrar` (SSR Astro) com confirmação de descadastro via token.
  • Módulo `email` com Resend: templates transacionais para matrícula confirmada, certificado emitido, descadastro de newsletter e DSAR.
  • Matrícula administrativa: `AdminEnrollInputSchema` e endpoint dedicado.
  • Editor de conteúdo de aula com `PATCH /lessons/:id`.
  • Testes de contrato em `apps/api/test/contract/`: validação de shapes Zod para read queries e testes de mutação (reorder, clamp, delete cascade).
  • `UpdatePositionInputSchema` e tipo `UpdatePositionInput` em `@capacita/shared`.
  • Migration `0008`: índices compostos `(tenant_id, course_id)` em `enrollments`, `(tenant_id)` em `modules`, `(tenant_id)` em `lessons`.
  • `tenant_id` em `modules` e `lessons` com RLS por comparação direta de coluna (ADR 0010, migration `0007`).
  • `docs/backlog.md`: registro de trigger features (SCORM, LTI 1.3, SAML 2.0, Bunny Stream, DnD, verificação pública de certificado) com critérios de ativação.

Changed

  • `CatalogService` refatorado em 4 sub-serviços por bounded context: courses, modules, lessons, enrollments.
  • Tipos de resposta da API exportados de `@capacita/shared`, eliminando interfaces inline no frontend.
  • `@Throttle` aplicado a `POST /newsletter/subscribe` (proteção contra spam de inscrição).

Fixed

  • Link de unsubscribe no e-mail de boas-vindas usa `PUBLIC_SITE_URL/descadastrar?token=` em vez da URL bruta do endpoint de API.
  • DSAR export e delete passaram a incluir registros de `newsletter_subscribers`.

Security

  • RLS em `modules` e `lessons` migrada de subquery correlacionada para comparação direta de `tenant_id`, alinhando com o padrão de `courses` e `enrollments` (ADR 0010).
  • Migration `0011`: RLS habilitada em `video_jobs` (`ENABLE/FORCE ROW LEVEL SECURITY` + policy `WITH CHECK`), fechando lacuna da migration `0010`.
  • Worker BullMQ usa `withTenant(tenantId, fn)` para todas as queries em `video_jobs` e `lessons`, preservando isolamento de tenant mesmo fora do contexto de request HTTP.
  • Teste de isolamento RLS `apps/api/test/rls/video-jobs.test.ts` cobre SELECT, UPDATE cross-tenant e INSERT com tenant_id alheio.
  • TTL de URL presignada de playback de vídeo reduzido de 3600s para 300s.
  • `Cache-Control: no-store, private` adicionado à resposta de download do MinIO, evitando cache local do arquivo.
  • Player `<video>` com `controlsList="nodownload"`, `disablePictureInPicture` e bloqueio de menu de contexto.
  • Watermark visual com e-mail e tenant do aluno sobreposto ao vídeo, mudando de posição a cada 8s (traceability de vazamento).
  • `docs/security/threats/video.md`: threat model explícito documentando o que está e o que não está protegido (Camada 1).
  • `MediaService` e `IdentityService` (funções tenant-scoped) alinhados ao padrão `withTenant`; antes apoiavam-se apenas em `WHERE tenant_id = ?`, insuficiente sob RLS forçado com role `app`.
  • ADR 0012 aceito: `DATABASE_URL` → role `app` (RLS ativo em produção); `ADMIN_DATABASE_URL` → superuser para operações cross-tenant via `withAdmin(fn)`. `getMemberships`, `exportData` e `deleteAccount` migrados para `withAdmin`.
  • Migration `0012_app_role_grants.sql`: `GRANT ... ON ALL TABLES IN SCHEMA public TO app` + `ALTER DEFAULT PRIVILEGES` — `app` acessa todas as tabelas presentes e futuras.

v0.2.0

Added

  • Player de aula (`/learn/[enrollmentId]/[lessonId]`): renderização de conteúdo por tipo (texto, vídeo placeholder, quiz placeholder) com sidebar de progresso, check de lições concluídas e navegação Anterior/Próxima.
  • Server action `markLessonCompleteAction`: chama `PUT /enrollments/:id/progress/:lid` e revalida rotas de aprendizado e matrícula.
  • Auto-conclusão de enrollment: quando todas as lições de um curso têm `completedAt`, o enrollment é atualizado para `status=completed` automaticamente no mesmo request de `updateProgress`.
  • Endpoint `GET /api/v1/enrollments/:id`: retorna enrollment com curso, módulos, lições e progresso por lição para o usuário autenticado.
  • Endpoint `GET /api/v1/enrollments/:id/certificate`: retorna `{ certificateId }` ou `null`, permitindo à UI saber se já foi emitido.
  • Botão "Emitir certificado" em `/enrollments` para matrículas concluídas sem certificado; link "Ver certificado" se já emitido.
  • Link direto para `/learn/[enrollmentId]/[lessonId]` na lista de aulas do catálogo quando o usuário tem matrícula ativa.
  • `ZodValidationPipe` (`apps/api/src/common/zod-validation.pipe.ts`): valida corpo de mutations com schema Zod; erros retornam 400 com `issues`.
  • Input schemas Zod em `packages/shared/src/schemas/catalog.ts`: `CreateCourseInputSchema`, `UpdateCourseStatusInputSchema`, `CreateModuleInputSchema`, `CreateLessonInputSchema`, `EnrollInputSchema`, `UpdateProgressInputSchema`.
  • `AuditInterceptor` registrado como `APP_INTERCEPTOR` global: eventos são gravados em `audit_events` automaticamente.
  • `@Audited` aplicado a 8 endpoints: `course.created`, `course.status_changed`, `module.created`, `lesson.created`, `enrollment.created`, `progress.updated`, `certificate.issued`, `account.deleted`.
  • `docs/security/threats/identity.md`: threat model do fluxo de identidade (Zitadel OIDC, TenantGuard, RLS, cookie httpOnly).

Changed

  • `updateProgress` agora auto-completa enrollment quando todas as lições estão concluídas.
  • Página `/enrollments` exibe ações de certificado e links diretos para o player.

v0.1.0

Added

  • Scaffolding inicial do monorepo com workspaces pnpm + Turborepo.
  • Documentação raiz: README, CLAUDE.md, DESIGN.md, AGENTS.md, ARCHITECTURE.md, SECURITY.md, PRIVACY.md, CONTRIBUTING.md, CODE_OF_CONDUCT.md.
  • Configuração de toolchain: Biome para lint e format, TypeScript strict, .editorconfig, .nvmrc.
  • ADRs iniciais: 0001–0007 (modular monolith, self-hosted, NestJS/Fastify, Postgres RLS, Zitadel, AGPL-3.0, Bunny Stream).
  • `infra/docker-compose.dev.yml` com Postgres 16 + pgvector, Redis, Meilisearch, MinIO, Zitadel, Mailpit.
  • Schema mínimo de banco: `tenants`, `users`, `memberships` com Drizzle ORM.
  • Migration `0000_init.sql` com `FORCE ROW LEVEL SECURITY`, roles `app` (não-superuser) e `admin`.
  • Testes de isolamento RLS via testcontainers (`pnpm test:rls`): dois tenants, queries com contexto A retornam 0 rows de B.
  • `packages/db/src/tenant.ts`: `withTenant(tenantId, fn)` usando `set_config` transacional.
  • `packages/auth/src/zitadel.ts`: verificação de JWT via JWKS Zitadel com cache de 10 min.
  • `packages/auth/src/abilities.ts`: bases CASL para autorização.
  • `apps/api`: módulo `identity` com `GET /api/v1/me`, `GET /api/v1/me/data-export` e `DELETE /api/v1/me/delete-account`.
  • Guard global de autenticação NestJS com decorator `@Public()`.
  • Middleware de tenant context integrado ao guard de auth.
  • `apps/web`: fluxo OIDC PKCE completo (login → Zitadel → callback → sessão httpOnly cookie).
  • `apps/web/src/middleware.ts`: proteção de rotas autenticadas via `jose.jwtVerify`.
  • Primeira tela autenticada `/app` exibindo nome, email e tenant ativo.
  • `apps/api/src/logging/sanitize.ts`: redação de PII em logs Pino (email, IP completo, tokens).
  • 9 testes unitários para `sanitize.ts` cobrindo redação de URL, mascaramento de IP, truncamento de UA.
  • `packages/shared`: tipo `LoggableUserId` com hash SHA-256 determinístico para logs identificáveis.
  • CI completo: `ci.yml` (lint+typecheck+test+test:rls+build), `security.yml` (Trivy bloqueante + Semgrep), `sbom.yml` (CycloneDX + cosign + attest).

v0.0.1

Added

  • Inicialização do repositório.
  • Definição da licença AGPL-3.0.
  • Estrutura de diretórios para apps, packages, services, infra e docs.