From f4afe83db03fb9a90fe4b76e46f3bf2626e85919 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/middleware/auth.ts | 105 ------------------ .../20260227000000_add_operacoes_pdf.sql | 5 +- src/components/layouts/MainLayout.tsx | 2 + src/components/pdf/PDFManipulationPage.tsx | 20 +++- .../server}/controllers/pdfController.ts | 17 ++- src/server/index.ts | 4 +- {backend => src/server}/routes/pdf.ts | 20 ++-- src/types/express.d.ts | 4 +- 9 files changed, 150 insertions(+), 127 deletions(-) create mode 100644 INSTALL_PDF.md delete mode 100644 backend/middleware/auth.ts rename {backend => src/server}/controllers/pdfController.ts (97%) rename {backend => src/server}/routes/pdf.ts (84%) 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/middleware/auth.ts b/backend/middleware/auth.ts deleted file mode 100644 index a7b5505..0000000 --- a/backend/middleware/auth.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { Request, Response, NextFunction } from 'express'; -import jwt from 'jsonwebtoken'; -import { PrismaClient } from '@prisma/client'; - -const prisma = new PrismaClient(); - -// Adicionar interface para tipo de usuário no Request -declare global { - namespace Express { - interface Request { - user?: { - id: string; - nome: string; - email: string; - perfil: string; - }; - } - } -} - -export const authenticateToken = async ( - req: Request, - res: Response, - next: NextFunction -): Promise => { - try { - // Verificar se há token - const authHeader = req.headers.authorization; - if (!authHeader) { - res.status(401).json({ error: 'Token não fornecido' }); - return; - } - - // Extrair o token do header (remover o Bearer) - const token = authHeader.replace('Bearer ', ''); - - // Verificar o token - const secret = process.env.JWT_SECRET || 'default-secret'; - const decoded = jwt.verify(token, secret) as any; - - // Buscar o usuário no banco - const user = await prisma.usuario.findUnique({ - where: { id: decoded.id }, - select: { - id: true, - nome: true, - email: true, - perfil: true, - ativo: true, - }, - }); - - if (!user || !user.ativo) { - res.status(401).json({ error: 'Usuário inválido ou inativo' }); - return; - } - - // Adicionar o usuário no request - req.user = { - id: user.id, - nome: user.nome, - email: user.email, - perfil: user.perfil, - }; - - next(); - } catch (error) { - console.error('Erro na autenticação:', error); - - if (error instanceof jwt.TokenExpiredError) { - res.status(401).json({ error: 'Token expirado' }); - return; - } - - if (error instanceof jwt.JsonWebTokenError) { - res.status(401).json({ error: 'Token inválido' }); - return; - } - - res.status(500).json({ error: 'Erro ao autenticar' }); - } -}; - -export const requireAdmin = async ( - req: Request, - res: Response, - next: NextFunction -): Promise => { - try { - if (!req.user) { - res.status(401).json({ error: 'Usuário não autenticado' }); - return; - } - - if (req.user.perfil !== 'ADVOGADO_ADMIN') { - res.status(403).json({ error: 'Acesso negado. Apenas administradores.' }); - return; - } - - next(); - } catch (error) { - console.error('Erro na verificação de permissão:', error); - res.status(500).json({ error: 'Erro ao verificar permissões' }); - } -}; 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/components/pdf/PDFManipulationPage.tsx b/src/components/pdf/PDFManipulationPage.tsx index ba99e8d..52892b8 100644 --- a/src/components/pdf/PDFManipulationPage.tsx +++ b/src/components/pdf/PDFManipulationPage.tsx @@ -2,6 +2,7 @@ import React, { useState, useCallback } from 'react'; import { useDropzone } from 'react-dropzone'; import axios from 'axios'; import { toast } from 'react-toastify'; +import { useAuth } from '../../contexts/AuthContext'; import { DocumentIcon, CloudArrowUpIcon, @@ -36,6 +37,7 @@ type OperationType = | 'info'; export const PDFManipulationPage: React.FC = () => { + const { getToken } = useAuth(); const [selectedOperation, setSelectedOperation] = useState('compress'); const [files, setFiles] = useState([]); const [processing, setProcessing] = useState(false); @@ -74,10 +76,6 @@ export const PDFManipulationPage: React.FC = () => { setResult(null); }; - const getToken = () => { - return localStorage.getItem('token'); - }; - const processFiles = async () => { if (files.length === 0) { toast.error('Selecione pelo menos um arquivo'); @@ -645,6 +643,20 @@ export const PDFManipulationPage: React.FC = () => { {op.tempoProcessamento}ms

)} + {op.status === 'CONCLUIDO' && op.arquivosResultado && op.arquivosResultado.length > 0 && ( +
+ {op.arquivosResultado.map((arquivo: string, index: number) => ( + + ))} +
+ )} ))} diff --git a/backend/controllers/pdfController.ts b/src/server/controllers/pdfController.ts similarity index 97% rename from backend/controllers/pdfController.ts rename to src/server/controllers/pdfController.ts index fcd15cb..61335a9 100644 --- a/backend/controllers/pdfController.ts +++ b/src/server/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 '../../services/pdfService'; import multer from 'multer'; import path from 'path'; import fs from 'fs/promises'; @@ -138,9 +138,10 @@ export class PDFController { success: true, operacaoId, results, + outputPaths: arquivosResultado, totalOriginalSize: Number(tamanhoOriginal), totalCompressedSize: Number(tamanhoResultado), - totalReduction: ((Number(tamanhoOriginal) - Number(tamanhoResultado)) / Number(tamanhoOriginal)) * 100, + reduction: ((Number(tamanhoOriginal) - Number(tamanhoResultado)) / Number(tamanhoOriginal)) * 100, processingTime: tempoProcessamento, }); } catch (error) { @@ -346,6 +347,7 @@ export class PDFController { success: true, operacaoId, outputPath, + outputPaths: [outputPath], originalSize: Number(tamanhoOriginal), mergedSize: Number(tamanhoResultado), processingTime: tempoProcessamento, @@ -551,6 +553,7 @@ export class PDFController { success: true, operacaoId, outputPath, + outputPaths: [outputPath], imagesProcessed: files.length, pdfSize: Number(tamanhoResultado), processingTime: tempoProcessamento, @@ -725,6 +728,7 @@ export class PDFController { mensagemErro: true, criadoEm: true, parametros: true, + arquivosResultado: true, }, }), prisma.operacaoPDF.count({ @@ -732,9 +736,16 @@ export class PDFController { }), ]); + // 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, + operacoes: operacoesSerializadas, pagination: { page, limit, 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/backend/routes/pdf.ts b/src/server/routes/pdf.ts similarity index 84% rename from backend/routes/pdf.ts rename to src/server/routes/pdf.ts index 9bcedc5..e80edee 100644 --- a/backend/routes/pdf.ts +++ b/src/server/routes/pdf.ts @@ -1,6 +1,6 @@ import express from 'express'; import { PDFController, uploadMiddleware } from '../controllers/pdfController'; -import { authenticateToken } from '../middleware/auth'; +import { authMiddleware } from '../../middleware/auth'; const router = express.Router(); @@ -12,7 +12,7 @@ const router = express.Router(); // Comprime PDFs router.post( '/compress', - authenticateToken, + authMiddleware, uploadMiddleware.array('files', 10), PDFController.compress ); @@ -20,7 +20,7 @@ router.post( // Divide PDF router.post( '/split', - authenticateToken, + authMiddleware, uploadMiddleware.array('files', 1), PDFController.split ); @@ -28,7 +28,7 @@ router.post( // Mescla PDFs router.post( '/merge', - authenticateToken, + authMiddleware, uploadMiddleware.array('files', 50), PDFController.merge ); @@ -36,7 +36,7 @@ router.post( // Converte PDF para imagens router.post( '/pdf-to-images', - authenticateToken, + authMiddleware, uploadMiddleware.array('files', 1), PDFController.pdfToImages ); @@ -44,7 +44,7 @@ router.post( // Converte imagens para PDF router.post( '/images-to-pdf', - authenticateToken, + authMiddleware, uploadMiddleware.array('files', 100), PDFController.imagesToPDF ); @@ -52,7 +52,7 @@ router.post( // Realiza OCR em PDF router.post( '/ocr', - authenticateToken, + authMiddleware, uploadMiddleware.array('files', 1), PDFController.performOCR ); @@ -60,7 +60,7 @@ router.post( // Obtém informações de um PDF router.post( '/info', - authenticateToken, + authMiddleware, uploadMiddleware.array('files', 1), PDFController.getInfo ); @@ -68,14 +68,14 @@ router.post( // Obtém histórico de operações router.get( '/history', - authenticateToken, + authMiddleware, PDFController.getHistory ); // Download de arquivo processado router.get( '/download/:operacaoId/:fileIndex', - authenticateToken, + authMiddleware, PDFController.downloadFile ); diff --git a/src/types/express.d.ts b/src/types/express.d.ts index 3377337..088cd96 100644 --- a/src/types/express.d.ts +++ b/src/types/express.d.ts @@ -1,5 +1,3 @@ -import { Request } from 'express'; - declare global { namespace Express { interface Request { @@ -11,3 +9,5 @@ declare global { } } } + +export {};