diff --git a/projetos/newwhats.clube67.com/tarefas.md b/projetos/newwhats.clube67.com/tarefas.md index 1db5bc1..337a10b 100644 --- a/projetos/newwhats.clube67.com/tarefas.md +++ b/projetos/newwhats.clube67.com/tarefas.md @@ -29,9 +29,12 @@ ## Wasabi / mídias - [x] Upload fire-and-forget + proxy público de leitura (avatares e mídias). - [x] `StorageProvider.deletePrefix(prefix)` (delete por prefixo). -- [ ] **Index de mídias** — listagem por tenant/instância/chat/período (auditoria + UI). -- [ ] **Delete em lote** — por instância, período ou chat, coordenado **DB ↔ Wasabi** - (sem órfãos). Endpoint admin seguro. Ver [wasabi-midias.md](wasabi-midias.md). +- [x] **Index de mídias** — `GET /api/instances/:id/media-index` (chat/tipo/período, + byType, paginado). Ver [wasabi-midias.md](wasabi-midias.md). +- [x] **Delete em lote** — `POST /api/instances/:id/media-batch-delete`, coordenado + **DB ↔ Wasabi**, dryRun por padrão + `deleteObjects` em lote. +- [ ] Index por tamanho (bytes) — falta coluna `mediaSize`. +- [ ] Reconciliação DB ↔ bucket (achar órfãos). - [ ] Política de retenção (arquivar/descartar áudio após transcrição salva). ## Ativação do WhatsApp (trajeto inteligente) diff --git a/projetos/newwhats.clube67.com/wasabi-midias.md b/projetos/newwhats.clube67.com/wasabi-midias.md index f2eaea3..93a59f9 100644 --- a/projetos/newwhats.clube67.com/wasabi-midias.md +++ b/projetos/newwhats.clube67.com/wasabi-midias.md @@ -18,21 +18,35 @@ Os paths são hierárquicos por `tenant/instância/...`, o que permite delete em por prefixo. Confirmar o layout exato no `StorageProvider` antes de implementar (avatar usa `avatarWasabiPath(tenantId, instanceId, jid)`). -## O que falta (pendência) +## Implementado (28/jun/2026) -### 1. Index de mídias -Listagem/auditoria das mídias por **tenant / instância / chat / período**: -- Fonte primária: tabela `messages` (path + metadados) — evita `listObjects` caro. -- Endpoint admin: `GET /admin/media?instanceId=&chatId=&from=&to=` → paginação, - tamanho total, contagem por tipo. -- Reconciliação opcional DB ↔ bucket para achar **órfãos** (no bucket sem registro). +### 1. Index de mídias ✅ +`GET /api/instances/:instanceId/media-index` (auth + escopo tenant/instância). +- Baseado no **DB** (`messages` com `mediaPath != null`) — evita `listObjects` caro. +- Query: `chatId?`, `type?` (IMAGE|VIDEO|AUDIO|DOCUMENT|STICKER), `from?`/`to?` (ISO), + `page`, `pageSize` (≤200). +- Retorna `{ total, page, pageSize, byType, items[] }`. -### 2. Delete em lote -- Por **instância** (offboarding), por **período** (retenção), por **chat** (LGPD). -- Coordenado **DB ↔ Wasabi**: apagar registro e objeto na mesma operação lógica; - nunca deixar órfão nem link quebrado. -- Implementar `deleteObjects` em lote (S3 aceita até 1000/req) além do `deletePrefix`. -- Endpoint admin protegido + dry-run (retorna contagem antes de apagar). +### 2. Delete em lote ✅ +`POST /api/instances/:instanceId/media-batch-delete`. Body: +`{ chatId?, type?, from?, to?, dryRun? }`. +- **dryRun=true é o padrão** — só conta (`matched`, `willDeleteObjects`). Para apagar + de fato é preciso enviar `dryRun:false`. +- Coordenado **DB ↔ Wasabi**: `storageProvider.deleteObjects(paths)` (chunks de 1000, + S3 `DeleteObjectsCommand`) + `updateMany` zera `mediaPath`/`mediaUrl` (sem link + quebrado). Filtra paths `http*` (CDN externo) — só apaga objetos do bucket. +- `StorageProvider.deleteObjects(paths)` adicionado à interface, ao registry e ao + provider concreto (UnifiedStorageProvider), além do `deletePrefix` já existente. + +> Validado em dev: index retornou 289 mídias (byType ok); dry-run de AUDIO casou 23. + +### Pendência restante +- Index por **tamanho em bytes**: o `Message` não guarda `mediaSize` — agregado de + bytes exigiria `HeadObject`/`listObjects`. Avaliar adicionar coluna `mediaSize`. +- **Local fallback** (dev sem Wasabi): `mediaPath` é caminho absoluto local; o + `deleteObjects` só roda quando o storage provider está registrado. Em produção + (Wasabi ativo) o path é a key relativa do bucket — fluxo correto. +- Reconciliação DB ↔ bucket para achar **órfãos** (objeto sem registro). ### 3. Retenção - Após `messages.transcription` salva, o **áudio pode ser arquivado/descartado**