feat: sistema de figurinhas, emoji PT-BR e preloader inteligente
- Stickers: captura automática por fileSha256 (Baileys) no MessageHandler,
tabela Prisma 'stickers' com deduplicação por [tenantId, sha256],
API REST /api/stickers (list + favorite toggle), cache IndexedDB permanente
(sem TTL — conteúdo imutável), StickerPanel.tsx com abas Recentes/Favoritos
- Emoji picker: migrado de emoji-picker-react para @emoji-mart/react com
locale="pt" (tradução completa: categorias, busca, mensagem de sem resultado)
- Preloader: aparece apenas em hard reload (F5/URL direta) via
performance.getEntriesByType('navigation').type + sessionStorage flag,
navegação client-side Next.js pula o overlay instantaneamente
- Avatar cache: Wasabi como storage persistente + IndexedDB client-side,
avatar_version via MD5(avatarUrl) sem coluna extra no banco,
rota /api/storage/view/* para servir arquivos Wasabi ao frontend
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -292,10 +292,11 @@ class UnifiedStorageProviderPlugin {
|
||||
}
|
||||
const safeBase = originalName.replace(/[^a-zA-Z0-9._-]/g, '_');
|
||||
const fileName = `${Date.now()}-${safeBase}`;
|
||||
// Path no bucket: whatsapp/{tenantId}/{instanceId}/{file} ou {category}/{file}
|
||||
const relativePath = (category === 'whatsapp' && payload.usuarioSis && payload.sessionJID)
|
||||
? `whatsapp/${payload.usuarioSis}/${payload.sessionJID}/${fileName}`
|
||||
: `${category}/${fileName}`;
|
||||
// Path no bucket: customPath (quando fornecido) > whatsapp/{t}/{i}/{file} > {cat}/{file}
|
||||
const relativePath = payload.customPath
|
||||
?? ((category === 'whatsapp' && payload.usuarioSis && payload.sessionJID)
|
||||
? `whatsapp/${payload.usuarioSis}/${payload.sessionJID}/${fileName}`
|
||||
: `${category}/${fileName}`);
|
||||
if (!this.s3 || !this.bucket) {
|
||||
return this.saveToLocalFallback(relativePath, buffer, originalName, category);
|
||||
}
|
||||
@@ -317,6 +318,26 @@ class UnifiedStorageProviderPlugin {
|
||||
return this.saveToLocalFallback(relativePath, buffer, originalName, category);
|
||||
}
|
||||
}
|
||||
async getBuffer(relativePath) {
|
||||
// 1. Fallback local
|
||||
const localPath = path_1.default.join(this.localFallbackDir, relativePath);
|
||||
if (fs_1.default.existsSync(localPath)) {
|
||||
return fs_1.default.readFileSync(localPath);
|
||||
}
|
||||
// 2. Wasabi
|
||||
if (!this.s3 || !this.bucket)
|
||||
return null;
|
||||
try {
|
||||
const result = await this.s3.send(new client_s3_1.GetObjectCommand({ Bucket: this.bucket, Key: relativePath }));
|
||||
if (!result.Body)
|
||||
return null;
|
||||
const bytes = await result.Body.transformToByteArray();
|
||||
return Buffer.from(bytes);
|
||||
}
|
||||
catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/** Salva buffer no diretório de fallback local, criando subpastas se necessário */
|
||||
saveToLocalFallback(relativePath, buffer, originalName, _category) {
|
||||
const dest = path_1.default.join(this.localFallbackDir, relativePath);
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user