import { Request, Response } from 'express'; import { PrismaClient } from '@prisma/client'; import { PDFService } from '../../services/pdfService'; import multer from 'multer'; import path from 'path'; import fs from 'fs/promises'; import { v4 as uuidv4 } from 'uuid'; const prisma = new PrismaClient(); // Configuração do multer para upload de arquivos const storage = multer.diskStorage({ destination: async (req, file, cb) => { const uploadDir = path.join(process.cwd(), 'storage', 'temp', 'uploads'); await fs.mkdir(uploadDir, { recursive: true }); cb(null, uploadDir); }, filename: (req, file, cb) => { const uniqueName = `${uuidv4()}${path.extname(file.originalname)}`; cb(null, uniqueName); }, }); const upload = multer({ storage, limits: { fileSize: 100 * 1024 * 1024, // 100MB }, fileFilter: (req, file, cb) => { const allowedMimes = [ 'application/pdf', 'image/png', 'image/jpeg', 'image/jpg', 'image/webp', ]; if (allowedMimes.includes(file.mimetype)) { cb(null, true); } else { cb(new Error('Tipo de arquivo não permitido. Use PDF ou imagens (PNG, JPG, WEBP).')); } }, }); export const uploadMiddleware = upload; /** * Controller para operações de PDF */ export class PDFController { /** * Comprime um ou mais PDFs */ static async compress(req: Request, res: Response): Promise { const startTime = Date.now(); let operacaoId: string | null = null; try { const files = req.files as Express.Multer.File[]; const userId = (req as any).user?.id; if (!files || files.length === 0) { res.status(400).json({ error: 'Nenhum arquivo enviado' }); return; } if (!userId) { res.status(401).json({ error: 'Usuário não autenticado' }); return; } const quality = req.body.quality ? parseInt(req.body.quality) : 50; // Padrão 50 para mais compressão const maxSizeMB = req.body.maxSizeMB ? parseInt(req.body.maxSizeMB) : undefined; const enableOCR = req.body.enableOCR === 'true' || req.body.enableOCR === true; const arquivosOriginais: string[] = []; const arquivosResultado: string[] = []; let tamanhoOriginal = BigInt(0); let tamanhoResultado = BigInt(0); // Cria registro da operação no banco const operacao = await prisma.operacaoPDF.create({ data: { tipoOperacao: 'COMPRIMIR', status: 'PROCESSANDO', usuarioId: userId, arquivosOriginais: [], arquivosResultado: [], tamanhoOriginal: BigInt(0), parametros: { quality, maxSizeMB }, }, }); operacaoId = operacao.id; // Processa cada arquivo const results = []; for (const file of files) { arquivosOriginais.push(file.path); tamanhoOriginal += BigInt(file.size); let processedPath = file.path; // Adiciona OCR se solicitado if (enableOCR) { try { const ocrResult = await PDFService.addOCRLayerToPDF(file.path); processedPath = ocrResult.outputPath; // Remove arquivo temporário anterior if (processedPath !== file.path) { await fs.unlink(file.path).catch(() => {}); } } catch (ocrError) { console.warn('Erro ao adicionar OCR, continuando sem OCR:', ocrError); } } const result = await PDFService.compressPDF(processedPath, undefined, { quality, maxSizeMB, }); arquivosResultado.push(result.outputPath); tamanhoResultado += BigInt(result.compressedSize); results.push({ originalName: file.originalname, originalSize: result.originalSize, compressedSize: result.compressedSize, reduction: result.reduction, outputPath: result.outputPath, }); // Remove o arquivo intermediário if (processedPath !== file.path && processedPath !== result.outputPath) { await fs.unlink(processedPath).catch(() => {}); } // Remove o arquivo original await fs.unlink(file.path).catch(() => {}); } const tempoProcessamento = Date.now() - startTime; // Atualiza a operação no banco await prisma.operacaoPDF.update({ where: { id: operacaoId }, data: { status: 'CONCLUIDO', arquivosOriginais, arquivosResultado, tamanhoOriginal, tamanhoResultado, tempoProcessamento, }, }); res.json({ success: true, operacaoId, results, outputPaths: arquivosResultado, totalOriginalSize: Number(tamanhoOriginal), totalCompressedSize: Number(tamanhoResultado), reduction: ((Number(tamanhoOriginal) - Number(tamanhoResultado)) / Number(tamanhoOriginal)) * 100, processingTime: tempoProcessamento, }); } catch (error) { console.error('Erro ao comprimir PDF:', error); if (operacaoId) { await prisma.operacaoPDF.update({ where: { id: operacaoId }, data: { status: 'ERRO', mensagemErro: error instanceof Error ? error.message : 'Erro desconhecido', tempoProcessamento: Date.now() - startTime, }, }); } res.status(500).json({ error: 'Falha ao comprimir PDF', message: error instanceof Error ? error.message : 'Erro desconhecido', }); } } /** * Divide um PDF */ static async split(req: Request, res: Response): Promise { const startTime = Date.now(); let operacaoId: string | null = null; try { const file = (req.files as Express.Multer.File[])?.[0]; const userId = (req as any).user?.id; if (!file) { res.status(400).json({ error: 'Nenhum arquivo enviado' }); return; } if (!userId) { res.status(401).json({ error: 'Usuário não autenticado' }); return; } const { splitType, pages, ranges, chunkSize } = req.body; if (!splitType) { res.status(400).json({ error: 'Tipo de divisão não especificado' }); return; } const options: any = { splitType }; if (splitType === 'pages' && pages) { options.pages = JSON.parse(pages); } else if (splitType === 'range' && ranges) { options.ranges = JSON.parse(ranges); } else if (splitType === 'chunks' && chunkSize) { options.chunkSize = parseInt(chunkSize); } // Cria registro da operação const operacao = await prisma.operacaoPDF.create({ data: { tipoOperacao: 'DIVIDIR', status: 'PROCESSANDO', usuarioId: userId, arquivosOriginais: [file.path], arquivosResultado: [], tamanhoOriginal: BigInt(file.size), parametros: options, }, }); operacaoId = operacao.id; // Divide o PDF const outputPaths = await PDFService.splitPDF(file.path, options); // Cria ZIP se houver múltiplos arquivos let zipPath: string | undefined; if (outputPaths.length > 1) { try { zipPath = await PDFService.createZipFromFiles(outputPaths); } catch (zipError) { console.warn('Erro ao criar ZIP, disponibilizando arquivos individuais:', zipError); } } // Calcula tamanho total dos arquivos resultantes let tamanhoResultado = BigInt(0); for (const outputPath of outputPaths) { const stats = await fs.stat(outputPath); tamanhoResultado += BigInt(stats.size); } const tempoProcessamento = Date.now() - startTime; // Atualiza a operação await prisma.operacaoPDF.update({ where: { id: operacaoId }, data: { status: 'CONCLUIDO', arquivosResultado: zipPath ? [zipPath, ...outputPaths] : outputPaths, tamanhoResultado, tempoProcessamento, }, }); // Remove o arquivo original await fs.unlink(file.path).catch(() => {}); res.json({ success: true, operacaoId, filesCreated: outputPaths.length, outputPaths, zipPath, processingTime: tempoProcessamento, }); } catch (error) { console.error('Erro ao dividir PDF:', error); if (operacaoId) { await prisma.operacaoPDF.update({ where: { id: operacaoId }, data: { status: 'ERRO', mensagemErro: error instanceof Error ? error.message : 'Erro desconhecido', tempoProcessamento: Date.now() - startTime, }, }); } res.status(500).json({ error: 'Falha ao dividir PDF', message: error instanceof Error ? error.message : 'Erro desconhecido', }); } } /** * Mescla múltiplos PDFs */ static async merge(req: Request, res: Response): Promise { const startTime = Date.now(); let operacaoId: string | null = null; try { const files = req.files as Express.Multer.File[]; const userId = (req as any).user?.id; if (!files || files.length < 2) { res.status(400).json({ error: 'São necessários pelo menos 2 arquivos para mesclar' }); return; } if (!userId) { res.status(401).json({ error: 'Usuário não autenticado' }); return; } const enableOCR = req.body.enableOCR === 'true' || req.body.enableOCR === true; const filePaths = files.map((f) => f.path); let tamanhoOriginal = BigInt(0); for (const file of files) { tamanhoOriginal += BigInt(file.size); } // Cria registro da operação const operacao = await prisma.operacaoPDF.create({ data: { tipoOperacao: 'MESCLAR', status: 'PROCESSANDO', usuarioId: userId, arquivosOriginais: filePaths, arquivosResultado: [], tamanhoOriginal, parametros: { filesCount: files.length }, }, }); operacaoId = operacao.id; // Mescla os PDFs let outputPath = await PDFService.mergePDFs({ filePaths, outputPath: path.join(process.cwd(), 'storage', 'pdf-output', `merged-${uuidv4()}.pdf`), }); // Adiciona OCR se solicitado if (enableOCR) { try { const ocrResult = await PDFService.addOCRLayerToPDF(outputPath); // Remove arquivo anterior await fs.unlink(outputPath).catch(() => {}); outputPath = ocrResult.outputPath; } catch (ocrError) { console.warn('Erro ao adicionar OCR ao PDF mesclado:', ocrError); } } // Calcula tamanho do arquivo resultante const stats = await fs.stat(outputPath); const tamanhoResultado = BigInt(stats.size); const tempoProcessamento = Date.now() - startTime; // Atualiza a operação await prisma.operacaoPDF.update({ where: { id: operacaoId }, data: { status: 'CONCLUIDO', arquivosResultado: [outputPath], tamanhoResultado, tempoProcessamento, }, }); // Remove os arquivos originais for (const filePath of filePaths) { await fs.unlink(filePath).catch(() => {}); } res.json({ success: true, operacaoId, outputPath, outputPaths: [outputPath], originalSize: Number(tamanhoOriginal), mergedSize: Number(tamanhoResultado), processingTime: tempoProcessamento, }); } catch (error) { console.error('Erro ao mesclar PDFs:', error); if (operacaoId) { await prisma.operacaoPDF.update({ where: { id: operacaoId }, data: { status: 'ERRO', mensagemErro: error instanceof Error ? error.message : 'Erro desconhecido', tempoProcessamento: Date.now() - startTime, }, }); } res.status(500).json({ error: 'Falha ao mesclar PDFs', message: error instanceof Error ? error.message : 'Erro desconhecido', }); } } /** * Converte PDF em imagens */ static async pdfToImages(req: Request, res: Response): Promise { const startTime = Date.now(); let operacaoId: string | null = null; try { const file = (req.files as Express.Multer.File[])?.[0]; const userId = (req as any).user?.id; if (!file) { res.status(400).json({ error: 'Nenhum arquivo enviado' }); return; } if (!userId) { res.status(401).json({ error: 'Usuário não autenticado' }); return; } const format = req.body.format || 'png'; const quality = req.body.quality ? parseInt(req.body.quality) : 95; const density = req.body.density ? parseInt(req.body.density) : 300; const pages = req.body.pages ? JSON.parse(req.body.pages) : 'all'; // Cria registro da operação const operacao = await prisma.operacaoPDF.create({ data: { tipoOperacao: 'PDF_PARA_IMAGEM', status: 'PROCESSANDO', usuarioId: userId, arquivosOriginais: [file.path], arquivosResultado: [], tamanhoOriginal: BigInt(file.size), parametros: { format, quality, density, pages }, }, }); operacaoId = operacao.id; // Converte para imagens const outputPaths = await PDFService.pdfToImages(file.path, { format, quality, density, pages, }); // Cria ZIP com todas as imagens let zipPath: string | undefined; if (outputPaths.length > 0) { try { zipPath = await PDFService.createZipFromFiles(outputPaths); } catch (zipError) { console.warn('Erro ao criar ZIP, disponibilizando imagens individuais:', zipError); } } // Calcula tamanho total let tamanhoResultado = BigInt(0); for (const outputPath of outputPaths) { const stats = await fs.stat(outputPath); tamanhoResultado += BigInt(stats.size); } const tempoProcessamento = Date.now() - startTime; // Atualiza a operação await prisma.operacaoPDF.update({ where: { id: operacaoId }, data: { status: 'CONCLUIDO', arquivosResultado: zipPath ? [zipPath, ...outputPaths] : outputPaths, tamanhoResultado, tempoProcessamento, }, }); // Remove o arquivo original await fs.unlink(file.path).catch(() => {}); res.json({ success: true, operacaoId, imagesCreated: outputPaths.length, outputPaths, zipPath, processingTime: tempoProcessamento, }); } catch (error) { console.error('Erro ao converter PDF para imagens:', error); if (operacaoId) { await prisma.operacaoPDF.update({ where: { id: operacaoId }, data: { status: 'ERRO', mensagemErro: error instanceof Error ? error.message : 'Erro desconhecido', tempoProcessamento: Date.now() - startTime, }, }); } res.status(500).json({ error: 'Falha ao converter PDF para imagens', message: error instanceof Error ? error.message : 'Erro desconhecido', }); } } /** * Converte imagens em PDF */ static async imagesToPDF(req: Request, res: Response): Promise { const startTime = Date.now(); let operacaoId: string | null = null; try { const files = req.files as Express.Multer.File[]; const userId = (req as any).user?.id; if (!files || files.length === 0) { res.status(400).json({ error: 'Nenhuma imagem enviada' }); return; } if (!userId) { res.status(401).json({ error: 'Usuário não autenticado' }); return; } const imagePaths = files.map((f) => f.path); let tamanhoOriginal = BigInt(0); for (const file of files) { tamanhoOriginal += BigInt(file.size); } const fitToPage = req.body.fitToPage === 'true'; const pageSize = req.body.pageSize ? JSON.parse(req.body.pageSize) : undefined; const enableOCR = req.body.enableOCR === 'true' || req.body.enableOCR === true; // Cria registro da operação const operacao = await prisma.operacaoPDF.create({ data: { tipoOperacao: 'IMAGEM_PARA_PDF', status: 'PROCESSANDO', usuarioId: userId, arquivosOriginais: imagePaths, arquivosResultado: [], tamanhoOriginal, parametros: { fitToPage, pageSize, imagesCount: files.length }, }, }); operacaoId = operacao.id; // Converte para PDF let outputPath = await PDFService.imagesToPDF({ imagePaths, outputPath: path.join(process.cwd(), 'storage', 'pdf-output', `images-to-pdf-${uuidv4()}.pdf`), fitToPage, pageSize, }); // Adiciona OCR se solicitado if (enableOCR) { try { const ocrResult = await PDFService.addOCRLayerToPDF(outputPath); // Remove arquivo anterior await fs.unlink(outputPath).catch(() => {}); outputPath = ocrResult.outputPath; } catch (ocrError) { console.warn('Erro ao adicionar OCR ao PDF:', ocrError); } } // Calcula tamanho do arquivo resultante const stats = await fs.stat(outputPath); const tamanhoResultado = BigInt(stats.size); const tempoProcessamento = Date.now() - startTime; // Atualiza a operação await prisma.operacaoPDF.update({ where: { id: operacaoId }, data: { status: 'CONCLUIDO', arquivosResultado: [outputPath], tamanhoResultado, tempoProcessamento, }, }); // Remove os arquivos originais for (const imagePath of imagePaths) { await fs.unlink(imagePath).catch(() => {}); } res.json({ success: true, operacaoId, outputPath, outputPaths: [outputPath], imagesProcessed: files.length, pdfSize: Number(tamanhoResultado), processingTime: tempoProcessamento, }); } catch (error) { console.error('Erro ao converter imagens para PDF:', error); if (operacaoId) { await prisma.operacaoPDF.update({ where: { id: operacaoId }, data: { status: 'ERRO', mensagemErro: error instanceof Error ? error.message : 'Erro desconhecido', tempoProcessamento: Date.now() - startTime, }, }); } res.status(500).json({ error: 'Falha ao converter imagens para PDF', message: error instanceof Error ? error.message : 'Erro desconhecido', }); } } /** * Realiza OCR em PDF */ static async performOCR(req: Request, res: Response): Promise { const startTime = Date.now(); let operacaoId: string | null = null; try { const file = (req.files as Express.Multer.File[])?.[0]; const userId = (req as any).user?.id; if (!file) { res.status(400).json({ error: 'Nenhum arquivo enviado' }); return; } if (!userId) { res.status(401).json({ error: 'Usuário não autenticado' }); return; } const language = req.body.language || 'por'; const pages = req.body.pages ? JSON.parse(req.body.pages) : 'all'; // Cria registro da operação const operacao = await prisma.operacaoPDF.create({ data: { tipoOperacao: 'OCR', status: 'PROCESSANDO', usuarioId: userId, arquivosOriginais: [file.path], arquivosResultado: [], tamanhoOriginal: BigInt(file.size), parametros: { language, pages }, }, }); operacaoId = operacao.id; // Realiza OCR const ocrResult = await PDFService.performOCR(file.path, { language, pages, }); const tempoProcessamento = Date.now() - startTime; // Atualiza a operação await prisma.operacaoPDF.update({ where: { id: operacaoId }, data: { status: 'CONCLUIDO', detalhesOCR: ocrResult as any, tempoProcessamento, }, }); // Remove o arquivo original await fs.unlink(file.path).catch(() => {}); res.json({ success: true, operacaoId, result: ocrResult, processingTime: tempoProcessamento, }); } catch (error) { console.error('Erro ao realizar OCR:', error); if (operacaoId) { await prisma.operacaoPDF.update({ where: { id: operacaoId }, data: { status: 'ERRO', mensagemErro: error instanceof Error ? error.message : 'Erro desconhecido', tempoProcessamento: Date.now() - startTime, }, }); } res.status(500).json({ error: 'Falha ao realizar OCR', message: error instanceof Error ? error.message : 'Erro desconhecido', }); } } /** * Obtém informações sobre um PDF */ static async getInfo(req: Request, res: Response): Promise { try { const file = (req.files as Express.Multer.File[])?.[0]; if (!file) { res.status(400).json({ error: 'Nenhum arquivo enviado' }); return; } const info = await PDFService.getPDFInfo(file.path); // Remove o arquivo await fs.unlink(file.path).catch(() => {}); res.json({ success: true, info, }); } catch (error) { console.error('Erro ao obter informações do PDF:', error); res.status(500).json({ error: 'Falha ao obter informações do PDF', message: error instanceof Error ? error.message : 'Erro desconhecido', }); } } /** * Obtém histórico de operações do usuário */ static async getHistory(req: Request, res: Response): Promise { try { const userId = (req as any).user?.id; if (!userId) { res.status(401).json({ error: 'Usuário não autenticado' }); return; } const page = parseInt(req.query.page as string) || 1; const limit = parseInt(req.query.limit as string) || 20; const skip = (page - 1) * limit; const [operacoes, total] = await Promise.all([ prisma.operacaoPDF.findMany({ where: { usuarioId: userId }, orderBy: { criadoEm: 'desc' }, skip, take: limit, select: { id: true, tipoOperacao: true, status: true, tamanhoOriginal: true, tamanhoResultado: true, tempoProcessamento: true, mensagemErro: true, criadoEm: true, parametros: true, arquivosResultado: true, }, }), prisma.operacaoPDF.count({ where: { usuarioId: userId }, }), ]); // Converter BigInt para Number para evitar erro de serialização JSON const operacoesSerializadas = operacoes.map(op => ({ ...op, tamanhoOriginal: Number(op.tamanhoOriginal), tamanhoResultado: op.tamanhoResultado ? Number(op.tamanhoResultado) : null, })); res.json({ success: true, operacoes: operacoesSerializadas, pagination: { page, limit, total, pages: Math.ceil(total / limit), }, }); } catch (error) { console.error('Erro ao obter histórico:', error); res.status(500).json({ error: 'Falha ao obter histórico', message: error instanceof Error ? error.message : 'Erro desconhecido', }); } } /** * Download de arquivo processado */ static async downloadFile(req: Request, res: Response): Promise { try { const { operacaoId, fileIndex } = req.params; const userId = (req as any).user?.id; if (!userId) { res.status(401).json({ error: 'Usuário não autenticado' }); return; } const operacao = await prisma.operacaoPDF.findUnique({ where: { id: operacaoId }, }); if (!operacao) { res.status(404).json({ error: 'Operação não encontrada' }); return; } if (operacao.usuarioId !== userId) { res.status(403).json({ error: 'Acesso negado' }); return; } const index = parseInt(fileIndex); const filePath = operacao.arquivosResultado[index]; if (!filePath) { res.status(404).json({ error: 'Arquivo não encontrado' }); return; } // Verifica se o arquivo existe try { await fs.access(filePath); } catch { res.status(404).json({ error: 'Arquivo não encontrado no sistema' }); return; } // Detecta o Content-Type correto baseado na extensão const ext = path.extname(filePath).toLowerCase(); const mimeTypes: { [key: string]: string } = { '.pdf': 'application/pdf', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.webp': 'image/webp', '.zip': 'application/zip', }; const contentType = mimeTypes[ext] || 'application/octet-stream'; const fileName = path.basename(filePath); console.log(`Download solicitado: ${fileName} (${contentType})`); // Define o Content-Type correto res.setHeader('Content-Type', contentType); res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`); // Garante caminho absoluto e envia o arquivo const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(filePath); res.sendFile(absolutePath); } catch (error) { console.error('Erro ao fazer download:', error); res.status(500).json({ error: 'Falha ao fazer download', message: error instanceof Error ? error.message : 'Erro desconhecido', }); } } /** * Deleta uma operação específica e seus arquivos */ static async deleteOperation(req: Request, res: Response): Promise { try { const { operacaoId } = req.params; const userId = (req as any).user?.id; if (!userId) { res.status(401).json({ error: 'Usuário não autenticado' }); return; } const operacao = await prisma.operacaoPDF.findUnique({ where: { id: operacaoId }, }); if (!operacao) { res.status(404).json({ error: 'Operação não encontrada' }); return; } if (operacao.usuarioId !== userId) { res.status(403).json({ error: 'Acesso negado' }); return; } // Deleta arquivos físicos const allFiles = [...operacao.arquivosOriginais, ...operacao.arquivosResultado]; for (const filePath of allFiles) { try { // Verifica se o arquivo existe antes de deletar await fs.access(filePath); await fs.unlink(filePath); } catch (error: any) { // Ignora erro se o arquivo não existe (já foi deletado) if (error.code !== 'ENOENT') { console.warn(`Erro ao deletar arquivo ${filePath}:`, error); } } } // Deleta registro do banco await prisma.operacaoPDF.delete({ where: { id: operacaoId }, }); res.json({ success: true, message: 'Operação deletada com sucesso', }); } catch (error) { console.error('Erro ao deletar operação:', error); res.status(500).json({ error: 'Falha ao deletar operação', message: error instanceof Error ? error.message : 'Erro desconhecido', }); } } /** * Limpa todo o histórico do usuário */ static async clearHistory(req: Request, res: Response): Promise { try { const userId = (req as any).user?.id; if (!userId) { res.status(401).json({ error: 'Usuário não autenticado' }); return; } // Busca todas as operações do usuário const operacoes = await prisma.operacaoPDF.findMany({ where: { usuarioId: userId }, }); let filesDeleted = 0; let filesError = 0; // Deleta todos os arquivos for (const operacao of operacoes) { const allFiles = [...operacao.arquivosOriginais, ...operacao.arquivosResultado]; for (const filePath of allFiles) { try { // Verifica se o arquivo existe antes de deletar await fs.access(filePath); await fs.unlink(filePath); filesDeleted++; } catch (error: any) { // Ignora erro se o arquivo não existe if (error.code !== 'ENOENT') { filesError++; console.warn(`Erro ao deletar arquivo ${filePath}:`, error); } } } } // Deleta todos os registros const deleted = await prisma.operacaoPDF.deleteMany({ where: { usuarioId: userId }, }); res.json({ success: true, message: 'Histórico limpo com sucesso', operacoesRemovidas: deleted.count, arquivosDeletados: filesDeleted, arquivosComErro: filesError, }); } catch (error) { console.error('Erro ao limpar histórico:', error); res.status(500).json({ error: 'Falha ao limpar histórico', message: error instanceof Error ? error.message : 'Erro desconhecido', }); } } /** * Limpa operações antigas (mais de X horas) */ static async cleanupOldOperations(req: Request, res: Response): Promise { try { const userId = (req as any).user?.id; const hours = parseInt(req.query.hours as string) || 24; if (!userId) { res.status(401).json({ error: 'Usuário não autenticado' }); return; } const cutoffDate = new Date(Date.now() - hours * 60 * 60 * 1000); // Busca operações antigas const operacoes = await prisma.operacaoPDF.findMany({ where: { usuarioId: userId, criadoEm: { lt: cutoffDate, }, }, }); let filesDeleted = 0; let filesError = 0; // Deleta arquivos for (const operacao of operacoes) { const allFiles = [...operacao.arquivosOriginais, ...operacao.arquivosResultado]; for (const filePath of allFiles) { try { // Verifica se o arquivo existe antes de deletar await fs.access(filePath); await fs.unlink(filePath); filesDeleted++; } catch (error: any) { // Ignora erro se o arquivo não existe if (error.code !== 'ENOENT') { filesError++; } } } } // Deleta registros const deleted = await prisma.operacaoPDF.deleteMany({ where: { usuarioId: userId, criadoEm: { lt: cutoffDate, }, }, }); res.json({ success: true, message: `Operações com mais de ${hours}h removidas`, operacoesRemovidas: deleted.count, arquivosDeletados: filesDeleted, arquivosComErro: filesError, }); } catch (error) { console.error('Erro ao limpar operações antig as:', error); res.status(500).json({ error: 'Falha ao limpar operações antigas', message: error instanceof Error ? error.message : 'Erro desconhecido', }); } } }