5.9 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_ida partir de bloco + unidade; se não existir, cria a visita comunit_id = NULLe preservaunit_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
- Visita-sombra: com
unit_idNULL — ou unidade sem nenhum dispositivo ativo — os jobs percorrem a mesma linha do tempo de estados, nos mesmos tempos, sem notificar ninguém; comoperator_queueativo, entra na fila marcada "unidade não cadastrada" (03-FLUXOS-E-CONTRATOS.md§2). Sem isso, a sequência de estados no WebSocket entrega o que o tempo constante escondeu - 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-Typepor 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
sha256e grava emmedia_assets - Nome de objeto gerado pelo servidor
- Define
expires_atconforme 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 de visita-sombra: unidade inexistente percorre a mesma sequência de estados, nos mesmos tempos, que uma visita real não atendida — e ninguém é notificado
- Teste: geofence rejeita fora do raio e aceita dentro
- Teste:
quiet_hoursdesvia do morador - Teste: dois
resolveconcorrentes — um vence, outro recebe409com o autor - Teste:
resolverepetido com mesmaIdempotency-Keynã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_namefica como placeholder - Push real (FASE 5) —
NotificationChannelcom implementação de log - Qualquer interface