# Correção Wasabi Storage — newwhats Data: 2026-05-07 Investigado e corrigido por: Claude Code --- ## Contexto O plugin `UnifiedStorageProvider` estava configurado para enviar mídias do WhatsApp ao Wasabi, porém os arquivos continuavam acumulando localmente em `backend/media`, chegando a **7.8 GB** de mídia que deveria estar na nuvem. A causa raiz foi uma combinação de 4 bugs no código e uma inconsistência de path descoberta na revisão final. --- ## Diagnóstico — 4 bugs + 1 inconsistência corrigidos ### Bug 1 — Arquivo local nunca era deletado após upload Wasabi (crítico) **Arquivo:** `backend/src/modules/whatsapp/handlers/MessageHandler.ts` O fluxo era: 1. Arquivo de mídia chegava → salvo em `backend/media/{instanceId}/{msgId}.ext` ✅ 2. StorageProvider registrado → upload para Wasabi ✅ 3. ❌ **Arquivo local NUNCA era deletado após upload bem-sucedido** Resultado: disco enchia indefinidamente mesmo com Wasabi funcionando. **Correção:** Após upload bem-sucedido (`provider !== 'local_fallback'`), o arquivo local é removido com `fs.unlink()` assíncrono. --- ### Bug 2 — `mediaPath` apontava para arquivo local inexistente (crítico) **Arquivo:** `backend/src/modules/whatsapp/handlers/MessageHandler.ts` Mesmo após o upload para Wasabi, o campo `mediaPath` no banco continuava sendo salvo com o caminho local completo (`/home/deploy/.../media/...`), que após o Bug 1 corrigido deixa de existir no disco. **Correção:** Quando `uploadedToRemote = true`, `mediaPath` é salvo como `null` no banco. O acesso à mídia passa a ser exclusivamente pelo `mediaUrl` que aponta para o Wasabi. --- ### Bug 3 — Rotas HTTP duplicadas ao reativar plugin via admin (médio) **Arquivo:** `plugins/UnifiedStorageProvider/index.ts` O `pluginRegistry.reactivate()` chama `activate()` novamente a cada salvamento de config. O `activate()` registrava as rotas Express (`/api/storage/view/*`, `/api/storage/wasabi/list-buckets`, etc.) a cada chamada, acumulando handlers duplicados em memória. **Correção:** Adicionada flag `private routesRegistered = false`. As rotas Express são registradas apenas na primeira chamada de `activate()`. Reativações subsequentes reinicializam apenas o S3 client e o registro do StorageProvider. --- ### Bug 4 — Deleção de instância não limpava mídias no Wasabi (funcionalidade ausente) **Arquivo:** `backend/src/modules/whatsapp/whatsapp.routes.ts` Ao deletar uma instância pelo admin, o fluxo só desconectava o socket Baileys, removia os arquivos de sessão e apagava o registro do banco. As mídias no Wasabi (e no fallback local) ficavam permanentemente órfãs. **Implementação:** - Adicionado `deletePrefix(prefix: string): Promise<{ deleted: number }>` à interface `StorageProviderInterface` em `types.ts` - Exposto `deletePrefix()` no `StorageProviderRegistry` em `StorageProvider.ts` (silencioso se provider não registrado) - Implementado no plugin com `ListObjectsV2Command` paginado em batches de 1000 + `DeleteObjectsCommand`, limpando também o fallback local - Chamado em background na rota `DELETE /:id` após `repo.delete(id)`, sem bloquear a resposta --- ### Inconsistência 5 — Path incorreto no deletePrefix (crítico) **Arquivo:** `backend/src/modules/whatsapp/whatsapp.routes.ts` O plugin grava arquivos no Wasabi com o path: ``` whatsapp/{tenantId}/{instanceId}/{fileName} ``` A implementação inicial do `deletePrefix` usava: ``` whatsapp/{instanceId}/ ← errado, nunca encontraria os arquivos ``` **Correção:** Prefix corrigido para `whatsapp/${tenantId}/${id}/`, que bate exatamente com a estrutura gravada pelo plugin. --- ## Arquivos modificados | Arquivo | Mudança | |---|---| | `backend/src/core/types.ts` | Adicionado `deletePrefix()` à interface `StorageProviderInterface` | | `backend/src/core/StorageProvider.ts` | Exposto `deletePrefix()` no registry com fallback silencioso | | `backend/src/modules/whatsapp/handlers/MessageHandler.ts` | `fs.unlink()` após upload + `mediaPath = null` quando remoto | | `backend/src/modules/whatsapp/whatsapp.routes.ts` | `deletePrefix("whatsapp/{tenantId}/{id}/")` ao deletar instância | | `plugins/UnifiedStorageProvider/index.ts` | Flag `routesRegistered` + import `DeleteObjectsCommand` + implementação de `deletePrefix()` | --- ## Configuração atual (verificada) ```json // DragonflyDB — plugin:config:UnifiedStorageProvider { "wasabiAccessKey": "OFLCYUUZRYJFKHLB81QC", "wasabiSecretKey": "R4GI2s7W...", "wasabiBucket": "whatsbasemidia", "wasabiRegion": "us-east-1" } ``` Endpoint padrão: `https://s3.wasabisys.com` --- ## Situação das mídias antigas (pós-investigação) | Situação | Quantidade | Ação | |---|---|---| | Migradas para Wasabi pelo script | 231 arquivos | ✅ no Wasabi (path: `whatsapp/{instanceId}/`) | | Deletadas do servidor após migração | 231 arquivos | ✅ removidas de `backend/media` | | Banco atualizado (mediaUrl → Wasabi) | 231 registros | ✅ `mediaPath = null`, `mediaUrl = whatsapp/...` | | Ainda locais em `backend/media` | ~1.430 registros | Serão refeitos ao reconectar sessões | | Novas mídias (pós-correção) | todas | ✅ vão direto para Wasabi e local é deletado | > ⚠️ Os 231 arquivos migrados pelo script têm path `whatsapp/{instanceId}/` (sem tenantId) pois foram lidos direto do disco. O plugin novo grava como `whatsapp/{tenantId}/{instanceId}/`. Isso não quebra o funcionamento mas o `deletePrefix` automático não os apagará quando a instância for deletada — precisarão ser removidos manualmente do bucket se necessário. --- ## Script de migração criado **Arquivo:** `migrate-media-wasabi.mjs` (raiz do projeto) Script Node.js criado para migrar os arquivos acumulados em `backend/media` para o Wasabi. Suporta `--dry-run` para simulação sem alterações. **O que o script faz por arquivo:** 1. Lê `messages` do banco onde `mediaUrl LIKE '/media/%'` 2. Verifica se o arquivo já existe no Wasabi (`HeadObjectCommand`) — pula upload se sim 3. Faz upload para `whatsapp/{instanceId}/{fileName}` no bucket `whatsbasemidia` 4. Atualiza o banco: `mediaUrl = 'whatsapp/...'`, `mediaPath = NULL` 5. Remove o arquivo local com `fs.unlinkSync()` 6. Ao final, remove diretórios vazios de `backend/media` **Execução:** ```bash # Simulação (sem alterar nada): node migrate-media-wasabi.mjs --dry-run # Migração real: node migrate-media-wasabi.mjs ``` **Status da execução em 2026-05-07:** - Migração iniciada e interrompida manualmente aos 231/1.661 arquivos - Motivo da parada: decisão de deletar sessões manualmente e reconectar para que as mídias sejam baixadas novamente do WhatsApp já com o Wasabi funcionando --- ## Backup gerado antes das operações **Arquivo:** `/home/deploy/projetos/newwhats_export/newwhats_completo.tar.gz` — **7.7 GB** **Dump do banco:** `/home/deploy/projetos/newwhats_export/newwhats_dump.sql` — **18 MB** O tar.gz contém o projeto completo (incluindo `backend/media` com todas as mídias locais no estado anterior à migração) e o dump PostgreSQL. Gerado em 2026-05-07 como snapshot de segurança antes de qualquer alteração. --- ## Fluxo completo após as correções ### Upload de nova mídia: ``` WhatsApp recebe mídia → salva buffer temporariamente em backend/media/{instanceId}/{msgId}.ext → storageProvider.uploadFile() → Wasabi: whatsapp/{tenantId}/{instanceId}/{fileName} → fs.unlink(localFile) ← arquivo local removido → banco: mediaPath = null, mediaUrl = whatsapp/{tenantId}/{instanceId}/{fileName} → frontend acessa via /api/storage/view/whatsapp/... (proxy → Wasabi) ``` ### Se Wasabi falhar: ``` → arquivo local mantido em backend/media → banco: mediaPath = /caminho/local, mediaUrl = /media/{instanceId}/{fileName} → SyncWorker tenta reenviar a cada 2 minutos → quando enviado: arquivo local removido automaticamente ``` ### Ao deletar instância: ``` DELETE /api/whatsapp/{id} → manager.disconnect() → desconecta Baileys → repo.delete() → remove do banco (cascade: chats, mensagens, contatos) → responde 200 ← não bloqueia → [background] storageProvider.deletePrefix("whatsapp/{tenantId}/{id}/") → ListObjectsV2 paginado (batches de 1000) → DeleteObjects em cada batch → Remove fallback local se existir → Loga total removidos --- ## Status da Implementação Física em Produção (VPS 1) — 2026-05-07 Todas as correções descritas neste documento foram fisicamente implementadas, compiladas, testadas e implantadas com sucesso na VPS 1 de Produção. ### Ações Executadas: 1. **Ajuste de Interfaces e Código Core:** Modificados `types.ts`, `StorageProvider.ts`, `MessageHandler.ts`, e `whatsapp.routes.ts`. 2. **Implementação de Limpeza Automática:** `UnifiedStorageProvider/index.ts` agora conta com `deletePrefix` S3 e deleção local robusta. 3. **Correção de Arquitetura de Container (Crítica):** Foi detectado que nenhum plugin era carregado em produção por falta de mapeamento. Montamos o volume de plugins e criamos os links simbólicos necessários (`/plugins`, `/app/backend`, `/app/src`) no Dockerfile para que os imports relativos funcionem na perfeição. 4. **Re-deploy Completo:** O container `newwhats-backend` foi totalmente rebuildado e reiniciado com `docker compose up -d --build`. **Status Final:** ✅ **CORRIGIDO E IMPLEMENTADO EM PRODUÇÃO**