Files
instrucoes_gerais/clube67/correcoes_mds/correcaoWasabi.md
T

9.3 KiB

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)

// 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. 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:

# 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.gz7.7 GB
Dump do banco: /home/deploy/projetos/newwhats_export/newwhats_dump.sql18 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**