Files
Documentos-Unicos/projetos/newwhats.clube67.com/wasabi-midias.md
T
VPS 4 Builder 6b5ebd48ca docs(newwhats): runbook de migração de mídia local → Wasabi + limpeza de disco
- novo: wasabi-migracao-e-limpeza.md (fluxo executado 04/jul, v1.0.70)
- wasabi-midias.md: ver-também cruzado

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 19:18:39 +02:00

3.3 KiB

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.

Ver também: wasabi-migracao-e-limpeza.md — runbook de migração da mídia local /media/ → Wasabi + esvaziamento do disco (executado 04/jul/2026, v1.0.70).

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.