feat(media): index e delete em lote de mídias (Wasabi, coordenado DB↔storage)
- StorageProvider.deleteObjects(paths): delete seletivo em lote (chunks de 1000), no provider concreto (Wasabi DeleteObjectsCommand + fallback local). - GET /api/instances/:id/media-index: index/auditoria por chat/tipo/período (baseado no DB, com agregado byType, paginado). - POST /api/instances/:id/media-batch-delete: delete coordenado DB↔Wasabi, dryRun por padrão (precisa dryRun:false p/ apagar); limpa mediaPath/mediaUrl. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -271,6 +271,42 @@ class UnifiedStorageProviderPlugin {
|
||||
}
|
||||
return { deleted: deletedCount };
|
||||
}
|
||||
/** Delete em lote seletivo: apaga exatamente os paths informados (não por prefixo). */
|
||||
async deleteObjects(paths) {
|
||||
const keys = paths.filter((p) => p && !p.startsWith('http'));
|
||||
if (keys.length === 0)
|
||||
return { deleted: 0 };
|
||||
let deletedCount = 0;
|
||||
// 1. Wasabi — em chunks de 1000 (limite do DeleteObjectsCommand)
|
||||
if (this.s3 && this.bucket) {
|
||||
for (let i = 0; i < keys.length; i += 1000) {
|
||||
const chunk = keys.slice(i, i + 1000);
|
||||
try {
|
||||
await this.s3.send(new client_s3_1.DeleteObjectsCommand({
|
||||
Bucket: this.bucket,
|
||||
Delete: { Objects: chunk.map((Key) => ({ Key })), Quiet: true },
|
||||
}));
|
||||
deletedCount += chunk.length;
|
||||
this.ctx.logger.info(`[UnifiedStorageProvider] ${chunk.length} objetos deletados do Wasabi (lote seletivo)`);
|
||||
}
|
||||
catch (err) {
|
||||
this.ctx.logger.error(`[UnifiedStorageProvider] Erro no delete em lote do Wasabi: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2. Fallback local
|
||||
for (const key of keys) {
|
||||
const localPath = path_1.default.join(this.localFallbackDir, key);
|
||||
try {
|
||||
if (fs_1.default.existsSync(localPath))
|
||||
fs_1.default.unlinkSync(localPath);
|
||||
}
|
||||
catch (err) {
|
||||
this.ctx.logger.error(`[UnifiedStorageProvider] Erro ao remover fallback local ${key}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
return { deleted: deletedCount };
|
||||
}
|
||||
async upload(payload) {
|
||||
const { category, file } = payload;
|
||||
if (!VALID_CATEGORIES.includes(category))
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -301,6 +301,43 @@ class UnifiedStorageProviderPlugin implements PluginInstance, StorageProviderInt
|
||||
return { deleted: deletedCount };
|
||||
}
|
||||
|
||||
/** Delete em lote seletivo: apaga exatamente os paths informados (não por prefixo). */
|
||||
async deleteObjects(paths: string[]): Promise<{ deleted: number }> {
|
||||
const keys = paths.filter((p) => p && !p.startsWith('http'));
|
||||
if (keys.length === 0) return { deleted: 0 };
|
||||
|
||||
let deletedCount = 0;
|
||||
|
||||
// 1. Wasabi — em chunks de 1000 (limite do DeleteObjectsCommand)
|
||||
if (this.s3 && this.bucket) {
|
||||
for (let i = 0; i < keys.length; i += 1000) {
|
||||
const chunk = keys.slice(i, i + 1000);
|
||||
try {
|
||||
await this.s3.send(new DeleteObjectsCommand({
|
||||
Bucket: this.bucket,
|
||||
Delete: { Objects: chunk.map((Key) => ({ Key })), Quiet: true },
|
||||
}));
|
||||
deletedCount += chunk.length;
|
||||
this.ctx.logger.info(`[UnifiedStorageProvider] ${chunk.length} objetos deletados do Wasabi (lote seletivo)`);
|
||||
} catch (err: any) {
|
||||
this.ctx.logger.error(`[UnifiedStorageProvider] Erro no delete em lote do Wasabi: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Fallback local
|
||||
for (const key of keys) {
|
||||
const localPath = path.join(this.localFallbackDir, key);
|
||||
try {
|
||||
if (fs.existsSync(localPath)) fs.unlinkSync(localPath);
|
||||
} catch (err: any) {
|
||||
this.ctx.logger.error(`[UnifiedStorageProvider] Erro ao remover fallback local ${key}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return { deleted: deletedCount };
|
||||
}
|
||||
|
||||
async upload(payload: StorageUploadPayload): Promise<{ provider: string; path: string }> {
|
||||
const { category, file } = payload;
|
||||
if (!VALID_CATEGORIES.includes(category)) throw new Error(`Categoria inválida: ${category}`);
|
||||
|
||||
Reference in New Issue
Block a user