Files
Projeto-Portaria/prompts/FASE-2-backend-api.md
2026-07-22 15:55:55 -03:00

5.3 KiB

FASE 2 — API do backend

Objetivo

Todos os endpoints REST, WebSocket de sinalização, armazenamento de mídia e o ciclo de vida da visita funcionando ponta a ponta — verificável por teste de integração, ainda sem interface.

Pré-requisitos

FASES 0 e 1 concluídas. Ler docs/03-FLUXOS-E-CONTRATOS.md inteiro e docs/06-LGPD-E-SEGURANCA.md §5.

Tarefas

1. Validação de QR e sessão de visitante

GET /api/v1/visitor/gates/{gateId}/preview?s={sig} — valida assinatura HMAC com gates.qr_secret e confere qr_version. Devolve nome do condomínio e o aviso de tratamento vigente. Sem autenticação.

POST /api/v1/visitor/sessions — recebe coordenadas, valida geofence com ST_DWithin(gates.location, ponto, geofence_meters), registra privacy_notices (versão do aviso, IP, user agent) e emite o JWT efêmero.

Localização ausente ou negada → 403 LOCATION_REQUIRED. Fora do raio → 403 OUTSIDE_GEOFENCE com detail em português pronto para exibir.

2. Criação de visita — busca cega

POST /api/v1/visitor/visits, multipart (JSON + mídia).

Esta é a implementação mais sensível do sistema. Requisitos, todos obrigatórios:

  • Resolve unit_id a partir de bloco + unidade; se não existir, cria a visita com unit_id = NULL e preserva unit_input
  • Resposta idêntica — mesmo corpo, mesmo status, mesmos headers — nos dois casos
  • Tempo de resposta constante: meça o caminho mais lento e aplique delay artificial no mais rápido. Sem isso o ataque vira timing attack e a busca cega não serve para nada
  • Nome de morador nunca aparece na resposta
  • Rate limit por IP e por dispositivo, com bloqueio progressivo

Verifica quiet_hours do condomínio: dentro da janela, a visita pula o toque ao morador e vai direto para FILA_OPERADOR ou RECADO_EM_VIDEO.

Enfileira VisitaCriada no outbox e agenda VISIT_RING_TIMEOUT (20s) e VISIT_EXPIRE (10 min).

3. Ciclo de vida da visita

Serviço de aplicação com as transições de 03-FLUXOS-E-CONTRATOS.md §1, sempre validadas por VisitStateMachine e sempre com optimistic locking em visits.version.

Handlers de job:

Job Ação
VISIT_RING_TIMEOUT ESCALONADA; toca para os demais unit_members por ring_order; agenda VISIT_ESCALATE
VISIT_ESCALATE FILA_OPERADOR se operator_queue ativo, senão RECADO_EM_VIDEO
VISIT_QUEUE_TIMEOUT RECADO_EM_VIDEO
VISIT_EXPIRE EXPIRADA
DELIVERY_DEFAULT_RULE aplica units.delivery_rule

POST /api/v1/app/visits/{id}/resolve exige Idempotency-Key. Conflito de versão devolve 409 VISIT_ALREADY_RESOLVED com resolvedBy e resolvedAt — o app precisa mostrar "Maria já autorizou", não um erro técnico.

4. Armazenamento de mídia

Porta StoragePort com adaptador MinIO/S3. Ao receber mídia:

  • Valida Content-Type por magic bytes, nunca por extensão
  • Limites: 8MB foto, 30MB vídeo
  • Remove EXIF — carrega GPS próprio, que não é o dado que coletamos e não passou pelo aviso
  • Calcula sha256 e grava em media_assets
  • Nome de objeto gerado pelo servidor
  • Define expires_at conforme a retenção do tipo (06-LGPD-E-SEGURANCA.md §4)

URL assinada de 5 min para leitura, sempre registrando acesso em audit_log. Nenhuma URL pública, em nenhuma hipótese.

5. WebSocket

/ws/visitor/{visitId} e /ws/app, com o envelope e os tipos de 03-FLUXOS-E-CONTRATOS.md §7. Heartbeat de 20s.

O WebSocket nunca é fonte de verdade. Toda mudança de estado é confirmada por REST ou push. Se o WS cair, o cliente faz polling — e a portaria continua funcionando.

Com múltiplas réplicas do backend, a difusão de eventos usa Redis pub/sub (só transporte; a durabilidade fica no outbox).

6. Endpoints de app e admin

Todos os de 03-FLUXOS-E-CONTRATOS.md §6, com autorização por método:

@PreAuthorize("@access.canViewVisit(#visitId, authentication)")

Morador vê apenas visitas das suas unidades; operador, apenas as da fila; admin, apenas as do seu tenant.

Importação CSV de unidades com dry-run obrigatório — devolve o que seria criado, alterado e rejeitado antes de gravar.

7. Tratamento de erros

application/problem+json (RFC 7807) com os code da tabela de 03-FLUXOS-E-CONTRATOS.md §9. detail sempre em português e pronto para exibir ao visitante — a tela da portaria não é lugar para mensagem técnica.

Critérios de aceite

  • Testes de integração com Testcontainers cobrindo os fluxos A, B e C ponta a ponta
  • Teste de busca cega: unidade existente e inexistente produzem resposta idêntica, e a diferença de tempo fica abaixo do ruído de medição
  • Teste: geofence rejeita fora do raio e aceita dentro
  • Teste: quiet_hours desvia do morador
  • Teste: dois resolve concorrentes — um vence, outro recebe 409 com o autor
  • Teste: resolve repetido com mesma Idempotency-Key não duplica
  • Teste: escalonamento dispara após restart do backend (job persistido)
  • Teste: EXIF removido da imagem armazenada
  • Teste: acesso a mídia gera linha em audit_log
  • OpenAPI completo em /v3/api-docs

Não faça nesta fase

  • Integração com LiveKit (FASE 4) — room_name fica como placeholder
  • Push real (FASE 5) — NotificationChannel com implementação de log
  • Qualquer interface