# API Reference Servidor Express em Node.js. Todas as rotas autenticadas exigem header: ``` Authorization: Bearer ``` Salvo onde indicado como **público** (sem auth). ## Convenções - Content-Type: `application/json` (exceto `/api/media` que é multipart) - Timestamps: Unix ms (números inteiros) - Coordenadas: `lat`, `lng` (decimais) - Velocidade: m/s no servidor, knots no frontend (conversão x1.94384) - Distância: metros no servidor, milhas náuticas no frontend (m / 1852) --- ## Endpoints públicos ### `GET /api/health` Health check. Útil para monitoramento. **Response:** ```json { "ok": true, "ts": 1730000000000 } ``` ### `GET /share/:token` Página HTML pública com mapa em tempo real do barco. - Renderiza Leaflet com marcador do barco e trilha das últimas posições - Inclui zonas de geofencing (proibidas/atenção) se foram salvas no share - Auto-refresh a cada 15 segundos - Retorna 404 se token inválido, 410 se expirado ### `GET /api/share/:token/info` Metadata do share (sem expor `BOAT_TOKEN`). **Response:** ```json { "boatName": "Shivao", "expiresAt": 1730086400000, "zones": [ { "id": "...", "name": "Pedras", "type": "forbidden", "center": { "lat": -23.0, "lng": -43.0 }, "radius": 100 } ] } ``` ### `GET /api/share/:token/positions` Posições históricas do share (até 500 últimas). **Response:** ```json [ { "lat": -23.0, "lng": -43.0, "speed": 3.2, "ts": 1730000000000 }, ... ] ``` --- ## Server info ### `GET /api/info` Quais canais de notificação estão configurados. **Response:** ```json { "channels": ["telegram", "email", "ntfy"], "heartbeatTimeoutSec": 300, "version": "1.0" } ``` --- ## Sync de dados ### `GET /api/data` Retorna todo o estado armazenado. **Response:** ```json { "data": { ... estado completo ... }, "updated_at": 1730000000000 } ``` ### `POST /api/data` Substitui todo o estado. **Body:** ```json { "data": { ... estado completo ... } } ``` > **Nota**: o servidor não merge nem versiona — substitui inteiro. > Para multi-dispositivo, é responsabilidade do cliente puxar antes de modificar > ou implementar uma estratégia de merge. --- ## Mídia ### `POST /api/media` Upload de arquivo. **Multipart form-data**: - `file`: arquivo binário - `id`: ID único (cliente gera) - `parent_id`: ID da viagem/manutenção pai (opcional) - `parent_type`: `trip`, `maint`, ou `pending` - `kind`: `photo`, `video`, `audio` - `created_at`: timestamp ms Limite: 200 MB por arquivo (revisar antes de produção — ver HANDOFF.md). **Response:** ```json { "ok": true, "id": "abc123", "url": "/api/media/abc123" } ``` ### `GET /api/media/list` Lista todas as mídias (metadata, sem o conteúdo). **Response:** ```json [ { "id": "abc", "parent_id": "trip_1", "kind": "photo", "mime": "image/jpeg", "size": 123456, "created_at": 1730000000000 } ] ``` ### `GET /api/media/:id` Stream do arquivo. Retorna o blob com `Content-Type` original. ### `DELETE /api/media/:id` Remove a mídia (DB + filesystem). --- ## Vigia de fundeio + dead-man switch ### `POST /api/anchor/start` Registra início de uma vigia no servidor. **Body:** ```json { "boat_name": "Shivao", "anchor_lat": -23.0, "anchor_lng": -43.0, "radius": 50 } ``` ### `POST /api/anchor/heartbeat` App envia a cada 30s enquanto vigia ativa. Reseta o timer do dead-man. **Body:** ```json { "lat": -23.001, "lng": -43.001, "distance": 12.5 } ``` ### `POST /api/anchor/alarm` Disparar alarme manualmente. Aciona TODOS os canais configurados em paralelo. **Body:** ```json { "boat_name": "Shivao", "lat": -23.005, "lng": -43.005, "distance": 87.3, "radius": 50, "reason": "drift" } ``` **Response:** ```json { "sent": ["telegram", "email", "ntfy"], "failed": [{ "channel": "sms", "error": "Twilio rate limit" }] } ``` ### `POST /api/anchor/stop` Encerra vigia. Limpa estado no servidor. ### `GET /api/anchor/status` Estado atual da vigia. **Response:** ```json { "active": 1, "boat_name": "Shivao", "anchor_lat": -23.0, "anchor_lng": -43.0, "radius": 50, "started_at": 1730000000000, "last_heartbeat": 1730000300000, "last_lat": -23.001, "last_lng": -43.001, "last_distance": 12.5, "alarm_fired": 0 } ``` ### Dead-man switch Roda em background a cada 30s. Se `last_heartbeat` está há mais de `HEARTBEAT_TIMEOUT_SEC` (padrão 5 min) e `active=1`, dispara alarme com `reason: "heartbeat_lost"`. Não envia spam — só dispara uma vez por episódio (até receber novo heartbeat ou stop). --- ## Compartilhamento ao vivo ### `POST /api/share/create` Cria link público temporário. **Body:** ```json { "durationMinutes": 360, "boatName": "Shivao", "zones": [ ... opcional, snapshot das zonas atuais ... ] } ``` **Response:** ```json { "token": "Aljg29x71kqp...", "expiresAt": 1730086400000, "url": "https://shivao.exemplo.com/share/Aljg29x71kqp..." } ``` ### `GET /api/share/list` Lista shares ativos (não expirados, não revogados). ### `DELETE /api/share/:token` Revoga share (marca como inativo). Página pública passa a retornar 404. ### `POST /api/share/:token/zones` Atualiza zonas do share (caso o dono adicione/remova zonas após criar o share). **Body:** ```json { "zones": [ ... ] } ``` ### `POST /api/share/position` Posta posição atual. App chama a cada 30s enquanto há share ativo. A posição vai para **todos** os shares ativos do barco (filtrados por `boat_name`). **Body:** ```json { "lat": -23.0, "lng": -43.0, "speed": 3.2, "boatName": "Shivao" } ``` **Response:** ```json { "ok": true, "posted": 2 } ``` --- ## Outros ### `POST /api/test` Dispara mensagem de teste em todos os canais configurados (sem urgência). **Response:** ```json { "sent": ["telegram"], "failed": [] } ``` ### `GET /api/alarms` Histórico dos últimos 50 alarmes (incluindo testes). **Response:** ```json [ { "id": 1, "ts": 1730000000000, "type": "drift", "payload": "{...JSON...}", "sent": "[\"telegram\",\"ntfy\"]", "failed": "[]" } ] ``` --- ## Cleanup automático Job rodando 1x/dia: - Remove `share_positions` de shares cujo `expires_at` passou há mais de 7 dias - Remove os próprios `shares` expirados