250e47b06e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
60 lines
3.2 KiB
Markdown
60 lines
3.2 KiB
Markdown
# newwhats.clube67.com — Wasabi & mídias (index + delete em lote)
|
|
|
|
> Estado atual + desenho do que falta. Storage = **Wasabi (S3-compatível)** via
|
|
> `StorageProvider` (`backend/src/core/StorageProvider.ts`). Última revisão: 28/jun/2026.
|
|
|
|
## Como funciona hoje
|
|
- **Upload** (`uploadFile`): fire-and-forget após receber a mídia/avatar; grava no
|
|
bucket e salva no DB **apenas o path relativo** (ex.: avatar →
|
|
`contact.avatarUrl`; mídia de mensagem → caminho no `messages`).
|
|
- **Leitura**: proxy público no `server.ts` (sem authMiddleware, pois `<img>/<audio>/
|
|
<video>` não mandam header) → busca bytes no Wasabi; se não houver, faz fallback
|
|
ao CDN do WhatsApp e re-persiste.
|
|
- **Delete**: só `StorageProvider.deletePrefix(prefix)` → `{ deleted }`. Apaga por
|
|
**prefixo** (ex.: tudo de uma instância). Não há listagem nem delete seletivo.
|
|
|
|
### Convenção de path (prefixos)
|
|
Os paths são hierárquicos por `tenant/instância/...`, o que permite delete em lote
|
|
por prefixo. Confirmar o layout exato no `StorageProvider` antes de implementar
|
|
(avatar usa `avatarWasabiPath(tenantId, instanceId, jid)`).
|
|
|
|
## Implementado (28/jun/2026)
|
|
|
|
### 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 ✅
|
|
`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**
|
|
(a Secretária já "entende" pelo texto). Job de retenção por idade/tamanho.
|
|
|
|
## Cuidados
|
|
- Delete é destrutivo e externo (Wasabi) — **sempre dry-run + confirmação**.
|
|
- Não apagar avatar/mídia ainda referenciada por outra mensagem (dedup por path).
|
|
- Operações em lote: throttle para não estourar limites do S3.
|
|
</content>
|