From 0e3a1815b36a59605db57165cab8dee20111069d Mon Sep 17 00:00:00 2001 From: Rodrigo Bertoldo de Sousa Martins Date: Fri, 27 Feb 2026 17:40:56 -0300 Subject: [PATCH] pdf: fix route Signed-off-by: Rodrigo Bertoldo de Sousa Martins --- INSTALL_PDF.md | 100 +++ backend/controllers/pdfController.ts | 2 +- .../20260227000000_add_operacoes_pdf.sql | 5 +- src/components/layouts/MainLayout.tsx | 2 + src/server/controllers/pdfController.ts | 798 ++++++++++++++++++ src/server/index.ts | 4 +- src/server/routes/pdf.ts | 82 ++ 7 files changed, 989 insertions(+), 4 deletions(-) create mode 100644 INSTALL_PDF.md create mode 100644 src/server/controllers/pdfController.ts create mode 100644 src/server/routes/pdf.ts diff --git a/INSTALL_PDF.md b/INSTALL_PDF.md new file mode 100644 index 0000000..996d843 --- /dev/null +++ b/INSTALL_PDF.md @@ -0,0 +1,100 @@ +# Passos para ativar a funcionalidade de PDF + +## 1. Instalar Dependências + +Execute o seguinte comando para instalar todas as dependências necessárias: + +```bash +npm install pdf-lib pdf-parse pdf2pic tesseract.js +npm install --save-dev @types/pdf-parse +``` + +## 2. Executar Migration do Banco de Dados + +Execute a migration para criar a tabela de operações de PDF: + +```bash +npx prisma migrate dev --name add_operacoes_pdf +``` + +Ou simplesmente: + +```bash +npm run prisma:migrate +``` + +## 3. Gerar Cliente Prisma + +Gere o cliente Prisma atualizado: + +```bash +npm run prisma:generate +``` + +## 4. Criar Diretórios + +Os diretórios serão criados automaticamente, mas você pode criá-los manualmente: + +**No Windows (PowerShell):** +```powershell +New-Item -Path "storage\temp\pdf-ops" -ItemType Directory -Force +New-Item -Path "storage\pdf-output" -ItemType Directory -Force +New-Item -Path "storage\temp\uploads" -ItemType Directory -Force +``` + +**No Linux/Mac:** +```bash +mkdir -p storage/temp/pdf-ops +mkdir -p storage/pdf-output +mkdir -p storage/temp/uploads +``` + +## 5. Reiniciar o Servidor + +Após instalar as dependências e executar as migrations, reinicie o servidor: + +```bash +npm run dev:full +``` + +Ou apenas o backend: + +```bash +npm run server +``` + +## Verificação + +Após seguir todos os passos: + +1. Acesse a aplicação no navegador +2. Faça login +3. Clique no item "PDFs" no menu lateral +4. Teste a funcionalidade de compressão enviando um PDF + +## Troubleshooting + +### Se continuar dando erro 404: + +1. Verifique se o servidor foi reiniciado após as mudanças +2. Verifique se as dependências foram instaladas corretamente +3. Verifique os logs do servidor para erros de importação + +### Se der erro de módulo não encontrado: + +Execute: +```bash +npm install +npm run prisma:generate +``` + +### Se der erro no banco de dados: + +Execute: +```bash +npx prisma migrate reset +npx prisma migrate dev +npm run prisma:generate +``` + +**ATENÇÃO:** O comando `migrate reset` apaga todos os dados do banco! diff --git a/backend/controllers/pdfController.ts b/backend/controllers/pdfController.ts index fcd15cb..77e69fc 100644 --- a/backend/controllers/pdfController.ts +++ b/backend/controllers/pdfController.ts @@ -1,6 +1,6 @@ import { Request, Response } from 'express'; import { PrismaClient } from '@prisma/client'; -import { PDFService } from '../services/pdfService'; +import { PDFService } from '../../src/services/pdfService'; import multer from 'multer'; import path from 'path'; import fs from 'fs/promises'; diff --git a/prisma/migrations/20260227000000_add_operacoes_pdf.sql b/prisma/migrations/20260227000000_add_operacoes_pdf.sql index 4102d31..c6f29d1 100644 --- a/prisma/migrations/20260227000000_add_operacoes_pdf.sql +++ b/prisma/migrations/20260227000000_add_operacoes_pdf.sql @@ -22,12 +22,13 @@ CREATE TABLE "operacoes_pdf" ( "detalhesOCR" JSONB, "tempoProcessamento" INTEGER, "criadoEm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "atualizadoEm" TIMESTAMP(3) NOT NULL, + "atualizadoEm" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "operacoes_pdf_pkey" PRIMARY KEY ("id") ); --- Adicionar foreign key para usuariosALTER TABLE "operacoes_pdf" ADD CONSTRAINT "operacoes_pdf_usuarioId_fkey" FOREIGN KEY ("usuarioId") REFERENCES "usuarios"("id") ON DELETE RESTRICT ON UPDATE CASCADE; +-- Adicionar foreign key para usuarios +ALTER TABLE "operacoes_pdf" ADD CONSTRAINT "operacoes_pdf_usuarioId_fkey" FOREIGN KEY ("usuarioId") REFERENCES "usuarios"("id") ON DELETE RESTRICT ON UPDATE CASCADE; -- Criar índice para melhor performance nas consultas CREATE INDEX "operacoes_pdf_usuarioId_idx" ON "operacoes_pdf"("usuarioId"); diff --git a/src/components/layouts/MainLayout.tsx b/src/components/layouts/MainLayout.tsx index 9c12a6d..7c7a46b 100644 --- a/src/components/layouts/MainLayout.tsx +++ b/src/components/layouts/MainLayout.tsx @@ -18,6 +18,7 @@ import { CurrencyDollarIcon, CalculatorIcon, BriefcaseIcon, + DocumentDuplicateIcon, } from "@heroicons/react/24/outline"; import { Menu, Transition } from "@headlessui/react"; import api from "../../services/api"; @@ -91,6 +92,7 @@ export const MainLayout: React.FC = () => { { path: "/atendimentos", label: "Atendimentos", icon: UserIcon }, { path: "/tarefas", label: "Tarefas", icon: ClipboardDocumentCheckIcon }, { path: "/documentos", label: "Documentos", icon: DocumentTextIcon }, + { path: "/pdf", label: "PDFs", icon: DocumentDuplicateIcon }, { path: null, label: "Cálculos", diff --git a/src/server/controllers/pdfController.ts b/src/server/controllers/pdfController.ts new file mode 100644 index 0000000..76d9fce --- /dev/null +++ b/src/server/controllers/pdfController.ts @@ -0,0 +1,798 @@ +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) : 95; + const maxSizeMB = req.body.maxSizeMB ? parseInt(req.body.maxSizeMB) : undefined; + + 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); + + const result = await PDFService.compressPDF(file.path, 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 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, + totalOriginalSize: Number(tamanhoOriginal), + totalCompressedSize: Number(tamanhoResultado), + totalReduction: ((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); + + // 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: outputPaths, + tamanhoResultado, + tempoProcessamento, + }, + }); + + // Remove o arquivo original + await fs.unlink(file.path).catch(() => {}); + + res.json({ + success: true, + operacaoId, + filesCreated: outputPaths.length, + outputPaths, + 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 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 + const outputPath = await PDFService.mergePDFs({ + filePaths, + outputPath: path.join(process.cwd(), 'storage', 'pdf-output', `merged-${uuidv4()}.pdf`), + }); + + // 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, + 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, + }); + + // 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: outputPaths, + tamanhoResultado, + tempoProcessamento, + }, + }); + + // Remove o arquivo original + await fs.unlink(file.path).catch(() => {}); + + res.json({ + success: true, + operacaoId, + imagesCreated: outputPaths.length, + outputPaths, + 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; + + // 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 + const outputPath = await PDFService.imagesToPDF({ + imagePaths, + outputPath: path.join(process.cwd(), 'storage', 'pdf-output', `images-to-pdf-${uuidv4()}.pdf`), + fitToPage, + pageSize, + }); + + // 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, + 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, + }, + }), + prisma.operacaoPDF.count({ + where: { usuarioId: userId }, + }), + ]); + + res.json({ + success: true, + operacoes, + 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; + } + + res.download(filePath, path.basename(filePath)); + } 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', + }); + } + } +} diff --git a/src/server/index.ts b/src/server/index.ts index 96bf2e4..9463539 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -27,6 +27,7 @@ import indicesMonetariosRoutes from './routes/indicesMonetarios'; import pensaoAlimenticiaRouter from './routes/pensaoAlimenticia'; // Importar router de pensão alimentícia import financeiroPainelRoutes from './routes/financeiroPainel'; // Importar rotas do financeiro painel import audienciasRoutes from './routes/audiencias'; // Importar rotas de audiências +import pdfRoutes from './routes/pdf'; // Importar rotas de PDF const app = express(); @@ -1481,4 +1482,5 @@ app.use('/api/calculos', calculosJudiciaisRoutes); app.use('/api/indices', indicesMonetariosRoutes); app.use('/api/pensao-alimenticia', pensaoAlimenticiaRouter); app.use('/api/financeiro-painel', financeiroPainelRoutes); // Adicionar uso das rotas do financeiro painel -app.use('/api/audiencias', audienciasRoutes); // Adicionar uso das rotas de audiências \ No newline at end of file +app.use('/api/audiencias', audienciasRoutes); // Adicionar uso das rotas de audiências +app.use('/api/pdf', pdfRoutes); // Adicionar uso das rotas de PDF \ No newline at end of file diff --git a/src/server/routes/pdf.ts b/src/server/routes/pdf.ts new file mode 100644 index 0000000..e80edee --- /dev/null +++ b/src/server/routes/pdf.ts @@ -0,0 +1,82 @@ +import express from 'express'; +import { PDFController, uploadMiddleware } from '../controllers/pdfController'; +import { authMiddleware } from '../../middleware/auth'; + +const router = express.Router(); + +/** + * Rotas para manipulação de PDF + * Todas as rotas requerem autenticação + */ + +// Comprime PDFs +router.post( + '/compress', + authMiddleware, + uploadMiddleware.array('files', 10), + PDFController.compress +); + +// Divide PDF +router.post( + '/split', + authMiddleware, + uploadMiddleware.array('files', 1), + PDFController.split +); + +// Mescla PDFs +router.post( + '/merge', + authMiddleware, + uploadMiddleware.array('files', 50), + PDFController.merge +); + +// Converte PDF para imagens +router.post( + '/pdf-to-images', + authMiddleware, + uploadMiddleware.array('files', 1), + PDFController.pdfToImages +); + +// Converte imagens para PDF +router.post( + '/images-to-pdf', + authMiddleware, + uploadMiddleware.array('files', 100), + PDFController.imagesToPDF +); + +// Realiza OCR em PDF +router.post( + '/ocr', + authMiddleware, + uploadMiddleware.array('files', 1), + PDFController.performOCR +); + +// Obtém informações de um PDF +router.post( + '/info', + authMiddleware, + uploadMiddleware.array('files', 1), + PDFController.getInfo +); + +// Obtém histórico de operações +router.get( + '/history', + authMiddleware, + PDFController.getHistory +); + +// Download de arquivo processado +router.get( + '/download/:operacaoId/:fileIndex', + authMiddleware, + PDFController.downloadFile +); + +export default router;