# 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**
