# API Reference

Servidor Express em Node.js. Todas as rotas autenticadas exigem header:

```
Authorization: Bearer <BOAT_TOKEN>
```

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
