From 928dfc044334d1f6873d96520f141dd93b6be2dc Mon Sep 17 00:00:00 2001 From: "Rodrigo B. de Sousa Martins" Date: Sun, 27 Jul 2025 18:50:37 -0300 Subject: [PATCH] revert commit --- package.json | 17 +- prisma/migrations/add_session_table.sql | 25 -- prisma/schema.prisma | 19 - src/App.tsx | 16 - src/components/Dashboard.tsx | 93 +---- src/components/layouts/MainLayout.tsx | 9 - src/components/provas/SessionRecording.tsx | 387 --------------------- src/components/provas/SessionReport.tsx | 227 ------------ src/middleware/sessionAuth.ts | 51 --- src/server/index.ts | 12 +- src/server/routes/sessions.ts | 254 -------------- src/services/ProvaService.ts | 48 --- src/services/SessionProvaService.ts | 361 ------------------- 13 files changed, 12 insertions(+), 1507 deletions(-) delete mode 100644 prisma/migrations/add_session_table.sql delete mode 100644 src/components/provas/SessionRecording.tsx delete mode 100644 src/components/provas/SessionReport.tsx delete mode 100644 src/middleware/sessionAuth.ts delete mode 100644 src/server/routes/sessions.ts delete mode 100644 src/services/SessionProvaService.ts diff --git a/package.json b/package.json index 7767dc8..d1cd598 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@mui/icons-material": "^5.15.6", "@mui/material": "^5.15.6", "@mui/x-date-pickers": "^6.19.0", - "@prisma/client": "^6.12.0", + "@prisma/client": "^6.5.0", "@tinymce/tinymce-react": "^6.1.0", "@tiptap/extension-link": "^2.11.7", "@tiptap/react": "^2.11.7", @@ -67,12 +67,7 @@ "uuid": "^9.0.1", "webdav": "^5.3.1", "xlsx": "^0.18.5", - "zod": "^3.22.4", - "react-webcam": "^7.2.0", - "recordrtc": "^5.6.2", - "html2canvas": "^1.4.1", - "canvas": "^3.1.2", - "ffmpeg-static": "^5.2.0" + "zod": "^3.22.4" }, "devDependencies": { "@tailwindcss/postcss": "^4.1.1", @@ -98,13 +93,11 @@ "eslint-plugin-react-hooks": "^5.2.0", "postcss": "^8.4.33", "prettier": "^3.5.3", - "prisma": "^6.12.0", + "prisma": "^6.5.0", "tailwindcss": "^3.4.1", "ts-node": "^10.9.2", "typescript": "^5.8.2", - "vite": "^6.2.4", - "@types/recordrtc": "^5.6.11", - "@types/html2canvas": "^1.0.0" + "vite": "^6.2.4" }, "prisma": { "seed": "ts-node prisma/seed.ts" @@ -120,4 +113,4 @@ "--window-size=1920x1080" ] } -} \ No newline at end of file +} diff --git a/prisma/migrations/add_session_table.sql b/prisma/migrations/add_session_table.sql deleted file mode 100644 index e54e546..0000000 --- a/prisma/migrations/add_session_table.sql +++ /dev/null @@ -1,25 +0,0 @@ --- CreateTable -CREATE TABLE "SessaoProva" ( - "id" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'ATIVA', - "dadosSessao" TEXT NOT NULL, - "dataInicio" TIMESTAMP(3) NOT NULL, - "dataFim" TIMESTAMP(3), - "videoPath" TEXT, - "relatorioPath" TEXT, - "hashSessao" TEXT, - "metadata" JSONB, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "SessaoProva_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE INDEX "SessaoProva_hashSessao_idx" ON "SessaoProva"("hashSessao"); - --- CreateIndex -CREATE INDEX "SessaoProva_status_idx" ON "SessaoProva"("status"); - --- CreateIndex -CREATE INDEX "SessaoProva_dataInicio_idx" ON "SessaoProva"("dataInicio"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6ac9bd0..524822d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1067,22 +1067,3 @@ model CalculoPensaoAlimenticia { @@map("calculos_pensao_alimenticia") } - -model SessaoProva { - id String @id @default(cuid()) - status String @default("ATIVA") // ATIVA, PROCESSANDO, CONCLUIDA, ERRO - dadosSessao String // JSON com dados da sessão - dataInicio DateTime - dataFim DateTime? - videoPath String? - relatorioPath String? - hashSessao String? @unique - metadata Json? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - @@index([hashSessao]) - @@index([status]) - @@index([dataInicio]) - @@map("sessao_prova") -} diff --git a/src/App.tsx b/src/App.tsx index 9e3bec9..3a7a7c7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -95,7 +95,6 @@ const router = createBrowserRouter( element={} errorElement={} /> - {/* Rotas para Cálculos Judiciais */} @@ -149,19 +148,4 @@ export const App: React.FC = () => { ); }; -export default App; - return ( - - - - - - - - - - - ); -}; - export default App; diff --git a/src/components/Dashboard.tsx b/src/components/Dashboard.tsx index 1f733e8..82a707a 100644 --- a/src/components/Dashboard.tsx +++ b/src/components/Dashboard.tsx @@ -1,95 +1,6 @@ import React from 'react'; -import { Link } from 'react-router-dom'; +import { DashboardLayout } from './dashboard/DashboardLayout'; export const Dashboard: React.FC = () => { - return ( -
-
-

Dashboard

-

Visão geral do seu escritório

-
- -
- {/* Widget para sessões de prova */} -
-
-
- - - -
-
-
-
- Sessões de Gravação -
-
- Disponível -
-
-
-
-
- - Nova sessão de coleta → - -
-
- - {/* Widget para provas digitais */} -
-
-
- - - -
-
-
-
- Provas Digitais -
-
- Ativo -
-
-
-
-
- - Validar documento → - -
-
-
-
- ); + return ; }; - -export default Dashboard; diff --git a/src/components/layouts/MainLayout.tsx b/src/components/layouts/MainLayout.tsx index 881868c..9c12a6d 100644 --- a/src/components/layouts/MainLayout.tsx +++ b/src/components/layouts/MainLayout.tsx @@ -116,15 +116,6 @@ export const MainLayout: React.FC = () => { icon: MoneyIcon } ] - }, - { - label: 'Provas Digitais', - icon: ShieldCheckIcon, - subitems: [ - { label: 'Upload de Documentos', path: '/provas/upload' }, - { label: 'Gravação de Sessão', path: '/sessions/new' }, - { label: 'Administrar Provas', path: '/provas/admin', roles: ['ADVOGADO_ADMIN'] }, - ] } ]; diff --git a/src/components/provas/SessionRecording.tsx b/src/components/provas/SessionRecording.tsx deleted file mode 100644 index 9732bb0..0000000 --- a/src/components/provas/SessionRecording.tsx +++ /dev/null @@ -1,387 +0,0 @@ -import React, { useState, useRef, useCallback, useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { toast } from 'react-toastify'; - -export const SessionRecording: React.FC = () => { - const [recording, setRecording] = useState(false); - const [sessionId, setSessionId] = useState(null); - const [recordingTime, setRecordingTime] = useState(0); - const [screenshots, setScreenshots] = useState([]); - const [isProcessing, setIsProcessing] = useState(false); - - const mediaRecorderRef = useRef(null); - const streamRef = useRef(null); - const recordedChunksRef = useRef([]); - const timerRef = useRef(null); - - const navigate = useNavigate(); - - useEffect(() => { - return () => { - if (timerRef.current) { - clearInterval(timerRef.current); - } - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - } - }; - }, []); - - const startRecording = useCallback(async () => { - try { - // Coletar informações de contexto - const metadata = await collectContextMetadata(); - - // Criar sessão no backend - const response = await fetch('/api/sessions/create', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(metadata) - }); - - if (!response.ok) { - throw new Error('Erro ao criar sessão'); - } - - const { sessionId: newSessionId } = await response.json(); - setSessionId(newSessionId); - - // Solicitar captura de tela - const stream = await navigator.mediaDevices.getDisplayMedia({ - video: { mediaSource: 'screen' }, - audio: true - }); - - streamRef.current = stream; - - // Configurar MediaRecorder - const mediaRecorder = new MediaRecorder(stream, { - mimeType: 'video/webm;codecs=vp9' - }); - - mediaRecorderRef.current = mediaRecorder; - recordedChunksRef.current = []; - - mediaRecorder.ondataavailable = (event) => { - if (event.data.size > 0) { - recordedChunksRef.current.push(event.data); - } - }; - - mediaRecorder.onstop = () => { - handleRecordingStop(newSessionId); - }; - - // Iniciar gravação - mediaRecorder.start(1000); // Gravar em chunks de 1 segundo - setRecording(true); - setRecordingTime(0); - - // Iniciar timer - timerRef.current = setInterval(() => { - setRecordingTime(prev => prev + 1); - }, 1000); - - // Overlay de informações na tela - createScreenOverlay(metadata); - - toast.success('Gravação de sessão iniciada!'); - - } catch (error) { - console.error('Erro ao iniciar gravação:', error); - toast.error('Erro ao iniciar gravação: ' + error.message); - } - }, []); - - const stopRecording = useCallback(() => { - if (mediaRecorderRef.current && recording) { - mediaRecorderRef.current.stop(); - setRecording(false); - - if (timerRef.current) { - clearInterval(timerRef.current); - timerRef.current = null; - } - - if (streamRef.current) { - streamRef.current.getTracks().forEach(track => track.stop()); - } - - removeScreenOverlay(); - } - }, [recording]); - - const takeScreenshot = useCallback(async () => { - if (!sessionId || !streamRef.current) { - toast.error('Sessão não ativa'); - return; - } - - try { - // Capturar screenshot usando Canvas - const canvas = document.createElement('canvas'); - const video = document.createElement('video'); - - video.srcObject = streamRef.current; - video.play(); - - video.onloadedmetadata = async () => { - canvas.width = video.videoWidth; - canvas.height = video.videoHeight; - - const ctx = canvas.getContext('2d'); - ctx?.drawImage(video, 0, 0); - - // Converter para base64 - const screenshotData = canvas.toDataURL('image/png'); - - // Enviar para o backend - const response = await fetch(`/api/sessions/${sessionId}/screenshot`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ screenshot: screenshotData }) - }); - - if (response.ok) { - const { filename } = await response.json(); - setScreenshots(prev => [...prev, filename]); - toast.success('Screenshot capturado!'); - } - }; - - } catch (error) { - console.error('Erro ao capturar screenshot:', error); - toast.error('Erro ao capturar screenshot'); - } - }, [sessionId]); - - const handleRecordingStop = async (sessionId: string) => { - try { - setIsProcessing(true); - - // Combinar chunks do vídeo - const blob = new Blob(recordedChunksRef.current, { type: 'video/webm' }); - - // Enviar vídeo para o backend - const formData = new FormData(); - formData.append('video', blob, `session_${sessionId}.webm`); - - const uploadResponse = await fetch(`/api/sessions/${sessionId}/video`, { - method: 'POST', - body: formData - }); - - if (!uploadResponse.ok) { - throw new Error('Erro ao enviar vídeo'); - } - - // Finalizar sessão e gerar relatório - const finalizeResponse = await fetch(`/api/sessions/${sessionId}/finalize`, { - method: 'POST' - }); - - if (!finalizeResponse.ok) { - throw new Error('Erro ao finalizar sessão'); - } - - const { reportPath } = await finalizeResponse.json(); - - toast.success('Sessão finalizada e relatório gerado!'); - - // Navegar para página de resultados - navigate(`/sessions/report/${sessionId}`); - - } catch (error) { - console.error('Erro ao processar gravação:', error); - toast.error('Erro ao processar gravação: ' + error.message); - } finally { - setIsProcessing(false); - } - }; - - const formatTime = (seconds: number): string => { - const hours = Math.floor(seconds / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - const secs = seconds % 60; - - return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; - }; - - return ( -
-
-

- Gravação de Sessão de Coleta de Prova -

- -
- {/* Status da gravação */} -
-
-
- - {recording ? 'Gravando' : isProcessing ? 'Processando...' : 'Pronto para gravar'} - - {recording && ( - - {formatTime(recordingTime)} - - )} -
- - {sessionId && ( - - Sessão: {sessionId} - - )} -
- - {/* Controles */} -
- {!recording && !isProcessing && ( - - )} - - {recording && ( - <> - - - - - )} -
- - {/* Informações da sessão */} - {(recording || screenshots.length > 0) && ( -
-

Informações da Sessão

-
-
- Screenshots capturados: - {screenshots.length} -
-
- Tempo de gravação: - {formatTime(recordingTime)} -
-
-
- )} - - {/* Lista de screenshots */} - {screenshots.length > 0 && ( -
-

Screenshots Capturados

-
- {screenshots.map((screenshot, index) => ( -
- {index + 1}. {screenshot} -
- ))} -
-
- )} - - {isProcessing && ( -
-
-

Processando gravação e gerando relatório...

-
- )} -
-
-
- ); -}; - -// Função para coletar metadados de contexto -async function collectContextMetadata() { - const getLocation = (): Promise => { - return new Promise((resolve) => { - if (!navigator.geolocation) { - resolve(null); - return; - } - - navigator.geolocation.getCurrentPosition( - (position) => resolve(position), - () => resolve(null), - { timeout: 5000 } - ); - }); - }; - - const location = await getLocation(); - - return { - userAgent: navigator.userAgent, - language: navigator.language, - platform: navigator.platform, - cookieEnabled: navigator.cookieEnabled, - onlineStatus: navigator.onLine, - screenResolution: `${screen.width}x${screen.height}`, - viewportSize: `${window.innerWidth}x${window.innerHeight}`, - timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, - geolocation: location ? { - latitude: location.coords.latitude, - longitude: location.coords.longitude, - accuracy: location.coords.accuracy - } : null, - timestamp: new Date().toISOString(), - url: window.location.href, - referrer: document.referrer - }; -} - -// Função para criar overlay na tela -function createScreenOverlay(metadata: any) { - const overlay = document.createElement('div'); - overlay.id = 'recording-overlay'; - overlay.style.cssText = ` - position: fixed; - top: 10px; - right: 10px; - background: rgba(0, 0, 0, 0.8); - color: white; - padding: 10px; - border-radius: 5px; - font-family: monospace; - font-size: 12px; - z-index: 10000; - max-width: 300px; - `; - - overlay.innerHTML = ` -
🔴 GRAVAÇÃO ATIVA
-
IP: ${metadata.ipAddress || 'Coletando...'}
-
Timestamp: ${new Date().toLocaleString()}
-
User-Agent: ${metadata.userAgent.substring(0, 50)}...
- `; - - document.body.appendChild(overlay); -} - -// Função para remover overlay -function removeScreenOverlay() { - const overlay = document.getElementById('recording-overlay'); - if (overlay) { - overlay.remove(); - } -} - -export default SessionRecording; diff --git a/src/components/provas/SessionReport.tsx b/src/components/provas/SessionReport.tsx deleted file mode 100644 index 418d857..0000000 --- a/src/components/provas/SessionReport.tsx +++ /dev/null @@ -1,227 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { useParams, Link } from 'react-router-dom'; -import { format } from 'date-fns'; -import { ptBR } from 'date-fns/locale'; - -interface SessionData { - id: string; - startTime: string; - endTime?: string; - ipAddress: string; - dnsInfo: string; - userAgent: string; - geolocation?: { - latitude: number; - longitude: number; - }; - screenshots: string[]; - videoPath?: string; - metadata: any; -} - -export const SessionReport: React.FC = () => { - const { sessionId } = useParams<{ sessionId: string }>(); - const [session, setSession] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - const fetchSession = async () => { - try { - const response = await fetch(`/api/sessions/${sessionId}`); - - if (!response.ok) { - throw new Error('Sessão não encontrada'); - } - - const data = await response.json(); - setSession(data.session); - } catch (error) { - console.error('Erro ao buscar sessão:', error); - setError(error.message); - } finally { - setLoading(false); - } - }; - - if (sessionId) { - fetchSession(); - } - }, [sessionId]); - - const formatDuration = (start: string, end?: string) => { - if (!end) return 'Em andamento'; - - const startTime = new Date(start); - const endTime = new Date(end); - const diffMs = endTime.getTime() - startTime.getTime(); - - const hours = Math.floor(diffMs / (1000 * 60 * 60)); - const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60)); - const seconds = Math.floor((diffMs % (1000 * 60)) / 1000); - - return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; - }; - - if (loading) { - return ( -
-
-
-
-
-
-
-
-
-
-
- ); - } - - if (error || !session) { - return ( -
-
-
-
- Erro ao carregar sessão -
-

{error || 'Sessão não encontrada'}

- - Nova Sessão - -
-
-
- ); - } - - return ( -
-
-
- {/* Header */} -
-

Relatório de Sessão de Coleta de Prova

-

ID da Sessão: {session.id}

-
- - {/* Resumo da sessão */} -
-

Resumo da Sessão

-
-
-

Data/Hora de Início

-

- {format(new Date(session.startTime), 'dd/MM/yyyy HH:mm:ss', { locale: ptBR })} -

-
- - {session.endTime && ( -
-

Data/Hora de Fim

-

- {format(new Date(session.endTime), 'dd/MM/yyyy HH:mm:ss', { locale: ptBR })} -

-
- )} - -
-

Duração

-

- {formatDuration(session.startTime, session.endTime)} -

-
- -
-

Screenshots Capturados

-

{session.screenshots.length}

-
-
-
- - {/* Informações de rede */} -
-

Informações de Rede

-
-
-

Endereço IP

-

{session.ipAddress}

-
- -
-

DNS Reverso

-

{session.dnsInfo}

-
- -
-

User-Agent

-

{session.userAgent}

-
-
-
- - {/* Geolocalização */} - {session.geolocation && ( -
-

Geolocalização

-
-
-

Latitude

-

{session.geolocation.latitude}

-
-
-

Longitude

-

{session.geolocation.longitude}

-
-
-
- )} - - {/* Metadados adicionais */} -
-

Metadados do Sistema

-
-
-                {JSON.stringify(session.metadata, null, 2)}
-              
-
-
- - {/* Ações */} -
- - Baixar Relatório PDF - - - {session.videoPath && ( - - )} - - - Nova Sessão - -
-
- -
- Relatório gerado pelo sistema AdvX Provas - Coleta de Evidências Digitais -
-
-
- ); -}; - -export default SessionReport; diff --git a/src/middleware/sessionAuth.ts b/src/middleware/sessionAuth.ts deleted file mode 100644 index cdbb3ae..0000000 --- a/src/middleware/sessionAuth.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Request, Response, NextFunction } from 'express'; -import jwt from 'jsonwebtoken'; -import { prisma } from '../config/database'; - -export interface SessionRequest extends Request { - user?: { - id: string; - email: string; - perfil: string; - }; -} - -export const sessionAuthMiddleware = async ( - req: SessionRequest, - res: Response, - next: NextFunction -) => { - try { - const token = req.headers.authorization?.replace('Bearer ', ''); - - if (!token) { - // Para sessões, permitir acesso sem autenticação em certas rotas - if (req.path.includes('/public/') || req.method === 'GET') { - return next(); - } - return res.status(401).json({ error: 'Token não fornecido' }); - } - - const decoded = jwt.verify(token, process.env.JWT_SECRET || 'default-secret') as any; - - const user = await prisma.usuario.findUnique({ - where: { id: decoded.id }, - select: { - id: true, - email: true, - perfil: true, - ativo: true - } - }); - - if (!user || !user.ativo) { - return res.status(401).json({ error: 'Usuário inválido ou inativo' }); - } - - req.user = user; - next(); - } catch (error) { - console.error('Erro na autenticação de sessão:', error); - res.status(401).json({ error: 'Token inválido' }); - } -}; diff --git a/src/server/index.ts b/src/server/index.ts index 65e52da..96bf2e4 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -24,10 +24,9 @@ import processosAdministrativosRoutes from './routes/processosAdministrativos'; import provasRouter from './routes/provas'; import calculosJudiciaisRoutes from './routes/calculosJudiciais'; import indicesMonetariosRoutes from './routes/indicesMonetarios'; -import pensaoAlimenticiaRouter from './routes/pensaoAlimenticia'; -import financeiroPainelRoutes from './routes/financeiroPainel'; -import audienciasRoutes from './routes/audiencias'; -import sessionsRouter from './routes/sessions'; // Adicionar importação das rotas de sessão +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 const app = express(); @@ -1472,7 +1471,6 @@ app.patch("/api/atendimentos/:id/status", authMiddleware, async (req, res) => { app.use('/api/anexos', anexosRoutes); app.use('/api/atendimentos', atendimentosRoutes); app.use('/api/provas', provasRouter); -app.use('/api/sessions', sessionsRouter); // Adicionar rotas de sessão app.use('/api/tarefas', tarefasRoutes); app.use('/api/dashboard', authMiddleware, dashboardRoutes); @@ -1482,5 +1480,5 @@ app.use('/api/processos-administrativos', processosAdministrativosRoutes); app.use('/api/calculos', calculosJudiciaisRoutes); app.use('/api/indices', indicesMonetariosRoutes); app.use('/api/pensao-alimenticia', pensaoAlimenticiaRouter); -app.use('/api/financeiro-painel', financeiroPainelRoutes); -app.use('/api/audiencias', audienciasRoutes); \ No newline at end of file +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 diff --git a/src/server/routes/sessions.ts b/src/server/routes/sessions.ts deleted file mode 100644 index 400a93c..0000000 --- a/src/server/routes/sessions.ts +++ /dev/null @@ -1,254 +0,0 @@ -import express from 'express'; -import multer from 'multer'; -import { SessionProvaService } from '../../services/SessionProvaService'; -import { prisma } from '../prismaClient'; -import * as path from 'path'; -import * as fs from 'fs'; - -const router = express.Router(); -const upload = multer({ storage: multer.memoryStorage() }); -const sessionService = new SessionProvaService(); - -// Criar nova sessão de gravação -router.post('/create', async (req, res) => { - try { - // Validação de entrada - if (!req.body || typeof req.body !== 'object') { - return res.status(400).json({ error: 'Dados de entrada inválidos' }); - } - - // Coletar IP real - const realIP = req.headers['x-forwarded-for']?.toString().split(',')[0].trim() || - req.ip || - req.socket?.remoteAddress || - '127.0.0.1'; - - const metadata = { - ...req.body, - ipAddress: realIP, - serverTimestamp: new Date().toISOString(), - headers: { - 'user-agent': req.headers['user-agent'], - 'accept-language': req.headers['accept-language'], - 'referer': req.headers['referer'], - 'host': req.headers['host'] - } - }; - - const sessionData = await sessionService.createSession(metadata); - - res.json({ - success: true, - sessionId: sessionData.id, - metadata: sessionData.metadata - }); - } catch (error) { - console.error('Erro ao criar sessão:', error); - res.status(500).json({ - error: 'Erro ao criar sessão de gravação', - details: error.message - }); - } -}); - -// Adicionar screenshot à sessão -router.post('/:sessionId/screenshot', async (req, res) => { - try { - const { sessionId } = req.params; - const { screenshot } = req.body; - - if (!screenshot) { - return res.status(400).json({ error: 'Screenshot não fornecido' }); - } - - if (!sessionId || typeof sessionId !== 'string') { - return res.status(400).json({ error: 'ID de sessão inválido' }); - } - - const filename = await sessionService.addScreenshot(sessionId, screenshot); - - res.json({ - success: true, - filename, - timestamp: new Date().toISOString() - }); - } catch (error) { - console.error('Erro ao salvar screenshot:', error); - res.status(500).json({ - error: 'Erro ao salvar screenshot', - details: error.message - }); - } -}); - -// Upload do vídeo da sessão -router.post('/:sessionId/video', upload.single('video'), async (req, res) => { - try { - const { sessionId } = req.params; - - if (!req.file) { - return res.status(400).json({ error: 'Vídeo não fornecido' }); - } - - if (!sessionId || typeof sessionId !== 'string') { - return res.status(400).json({ error: 'ID de sessão inválido' }); - } - - const filename = await sessionService.saveSessionVideo(sessionId, req.file.buffer); - - res.json({ - success: true, - filename, - size: req.file.size, - timestamp: new Date().toISOString() - }); - } catch (error) { - console.error('Erro ao salvar vídeo:', error); - res.status(500).json({ - error: 'Erro ao salvar vídeo', - details: error.message - }); - } -}); - -// Finalizar sessão e gerar relatório -router.post('/:sessionId/finalize', async (req, res) => { - try { - const { sessionId } = req.params; - - if (!sessionId || typeof sessionId !== 'string') { - return res.status(400).json({ error: 'ID de sessão inválido' }); - } - - const reportPath = await sessionService.finalizeSession(sessionId); - - res.json({ - success: true, - sessionId, - reportPath, - timestamp: new Date().toISOString() - }); - } catch (error) { - console.error('Erro ao finalizar sessão:', error); - res.status(500).json({ - error: 'Erro ao finalizar sessão', - details: error.message - }); - } -}); - -// Obter informações da sessão -router.get('/:sessionId', async (req, res) => { - try { - const { sessionId } = req.params; - - if (!sessionId || typeof sessionId !== 'string') { - return res.status(400).json({ error: 'ID de sessão inválido' }); - } - - const sessionData = await sessionService.getSession(sessionId); - - if (!sessionData) { - return res.status(404).json({ error: 'Sessão não encontrada' }); - } - - res.json({ - success: true, - session: sessionData - }); - } catch (error) { - console.error('Erro ao buscar sessão:', error); - res.status(500).json({ - error: 'Erro ao buscar sessão', - details: error.message - }); - } -}); - -// Baixar relatório da sessão -router.get('/:sessionId/report', async (req, res) => { - try { - const { sessionId } = req.params; - - if (!sessionId || typeof sessionId !== 'string') { - return res.status(400).json({ error: 'ID de sessão inválido' }); - } - - const session = await prisma.sessaoProva.findUnique({ - where: { id: sessionId } - }); - - if (!session || !session.relatorioPath) { - return res.status(404).json({ error: 'Relatório não encontrado' }); - } - - const reportPath = session.relatorioPath; - - if (!fs.existsSync(reportPath)) { - return res.status(404).json({ error: 'Arquivo do relatório não encontrado' }); - } - - res.setHeader('Content-Type', 'application/pdf'); - res.setHeader('Content-Disposition', `attachment; filename="relatorio_sessao_${sessionId}.pdf"`); - - const fileStream = fs.createReadStream(reportPath); - fileStream.pipe(res); - } catch (error) { - console.error('Erro ao servir relatório:', error); - res.status(500).json({ - error: 'Erro ao baixar relatório', - details: error.message - }); - } -}); - -// Verificar sessão por hash -router.get('/verify/:hash', async (req, res) => { - try { - const { hash } = req.params; - - if (!hash || typeof hash !== 'string') { - return res.status(400).json({ error: 'Hash inválido' }); - } - - const result = await sessionService.verifySession(hash); - - res.json(result); - } catch (error) { - console.error('Erro ao verificar sessão:', error); - res.status(500).json({ - error: 'Erro ao verificar sessão', - details: error.message - }); - } -}); - -// Listar sessões (admin) -router.get('/admin/list', async (req, res) => { - try { - const sessions = await prisma.sessaoProva.findMany({ - orderBy: { dataInicio: 'desc' }, - take: 50 - }); - - res.json({ - success: true, - sessions: sessions.map(session => ({ - id: session.id, - status: session.status, - dataInicio: session.dataInicio, - dataFim: session.dataFim, - hashSessao: session.hashSessao, - temRelatorio: !!session.relatorioPath - })) - }); - } catch (error) { - console.error('Erro ao listar sessões:', error); - res.status(500).json({ - error: 'Erro ao listar sessões', - details: error.message - }); - } -}); - -export default router; diff --git a/src/services/ProvaService.ts b/src/services/ProvaService.ts index 6b6ebc8..e80cb3c 100644 --- a/src/services/ProvaService.ts +++ b/src/services/ProvaService.ts @@ -4,7 +4,6 @@ import { createHash } from 'crypto'; import { PDFDocument, StandardFonts } from 'pdf-lib'; import * as OpenTimestamps from 'opentimestamps'; import sharp from 'sharp'; -import { promises as fs } from 'fs'; export class ProvaService { private fileValidator: FileValidator; @@ -97,51 +96,4 @@ export class ProvaService { buffer: await pdfDoc.save() }; } - - async verifyFile(hash: string) { - try { - const prova = await prisma.prova.findFirst({ - where: { checksumSha256: hash } - }); - - if (!prova) { - return { - verified: false, - message: 'Documento não encontrado ou hash inválido' - }; - } - - // Verificar se o arquivo ainda existe - const filePath = prova.filePath; - if (filePath) { - try { - await fs.access(filePath); - } catch { - return { - verified: false, - message: 'Arquivo original não encontrado' - }; - } - } - - return { - verified: true, - message: 'Documento verificado com sucesso', - prova: { - fileName: prova.fileName, - checksumSha256: prova.checksumSha256, - checksumSha512: prova.checksumSha512, - checksumMd5: prova.checksumMd5, - timestamp: prova.timestamp, - metadata: prova.metadata, - dataCriacao: prova.dataCriacao, - dataValida: prova.dataAtualizacao - }, - timestamp: prova.timestamp ? JSON.parse(prova.timestamp) : null - }; - } catch (error) { - console.error('Erro ao verificar arquivo:', error); - throw new Error(`Erro ao verificar arquivo: ${error.message}`); - } - } } \ No newline at end of file diff --git a/src/services/SessionProvaService.ts b/src/services/SessionProvaService.ts deleted file mode 100644 index 6c2a1d7..0000000 --- a/src/services/SessionProvaService.ts +++ /dev/null @@ -1,361 +0,0 @@ -import { prisma } from '../server/prismaClient'; -import { createHash } from 'crypto'; -import { PDFDocument, StandardFonts, rgb } from 'pdf-lib'; -import * as fs from 'fs/promises'; -import * as path from 'path'; -import { format } from 'date-fns'; -import { ptBR } from 'date-fns/locale'; - -export interface SessionData { - id: string; - userAgent: string; - ipAddress: string; - dnsInfo: string; - geolocation?: { - latitude: number; - longitude: number; - }; - startTime: Date; - endTime?: Date; - screenshots: string[]; - videoPath?: string; - metadata: any; -} - -export class SessionProvaService { - private storageBasePath = path.join(process.cwd(), 'storage', 'sessions'); - - constructor() { - this.ensureStorageDirectory(); - } - - private async ensureStorageDirectory() { - try { - await fs.access(this.storageBasePath); - } catch { - await fs.mkdir(this.storageBasePath, { recursive: true }); - } - } - - async createSession(metadata: any): Promise { - const sessionId = this.generateSessionId(); - - // Coletar informações de DNS - const dnsInfo = await this.collectDNSInfo(metadata.ipAddress); - - const sessionData: SessionData = { - id: sessionId, - userAgent: metadata.userAgent, - ipAddress: metadata.ipAddress, - dnsInfo, - geolocation: metadata.geolocation, - startTime: new Date(), - screenshots: [], - metadata: { - ...metadata, - sessionId, - browserInfo: this.parseBrowserInfo(metadata.userAgent), - timestamp: new Date().toISOString() - } - }; - - // Salvar no banco de dados - await prisma.sessaoProva.create({ - data: { - id: sessionId, - status: 'ATIVA', - dadosSessao: JSON.stringify(sessionData), - dataInicio: sessionData.startTime, - metadata: sessionData.metadata - } - }); - - return sessionData; - } - - async addScreenshot(sessionId: string, screenshotData: string): Promise { - const timestamp = Date.now(); - const filename = `screenshot_${timestamp}.png`; - const filepath = path.join(this.storageBasePath, sessionId, filename); - - // Criar diretório da sessão se não existir - await fs.mkdir(path.dirname(filepath), { recursive: true }); - - // Converter base64 para buffer e salvar - const buffer = Buffer.from(screenshotData.replace(/^data:image\/png;base64,/, ''), 'base64'); - await fs.writeFile(filepath, buffer); - - // Atualizar sessão no banco - const session = await prisma.sessaoProva.findUnique({ - where: { id: sessionId } - }); - - if (session) { - const sessionData = JSON.parse(session.dadosSessao) as SessionData; - sessionData.screenshots.push(filename); - - await prisma.sessaoProva.update({ - where: { id: sessionId }, - data: { - dadosSessao: JSON.stringify(sessionData) - } - }); - } - - return filename; - } - - async saveSessionVideo(sessionId: string, videoBlob: Buffer): Promise { - const filename = `session_${sessionId}.webm`; - const filepath = path.join(this.storageBasePath, sessionId, filename); - - await fs.mkdir(path.dirname(filepath), { recursive: true }); - await fs.writeFile(filepath, videoBlob); - - // Atualizar sessão - await prisma.sessaoProva.update({ - where: { id: sessionId }, - data: { - videoPath: filename, - status: 'PROCESSANDO' - } - }); - - return filename; - } - - async finalizeSession(sessionId: string): Promise { - const session = await prisma.sessaoProva.findUnique({ - where: { id: sessionId } - }); - - if (!session) { - throw new Error('Sessão não encontrada'); - } - - const sessionData = JSON.parse(session.dadosSessao) as SessionData; - sessionData.endTime = new Date(); - - // Gerar relatório PDF - const reportPath = await this.generateSessionReport(sessionId, sessionData); - - // Gerar hash da sessão - const sessionHash = this.generateSessionHash(sessionData); - - // Finalizar no banco - await prisma.sessaoProva.update({ - where: { id: sessionId }, - data: { - status: 'CONCLUIDA', - dataFim: sessionData.endTime, - dadosSessao: JSON.stringify(sessionData), - relatorioPath: reportPath, - hashSessao: sessionHash - } - }); - - return reportPath; - } - - private async generateSessionReport(sessionId: string, sessionData: SessionData): Promise { - try { - const pdfDoc = await PDFDocument.create(); - const font = await pdfDoc.embedFont(StandardFonts.Helvetica); - const boldFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold); - - // Primeira página - Screenshots (se houver) - if (sessionData.screenshots.length > 0) { - for (const screenshot of sessionData.screenshots) { - const page = pdfDoc.addPage(); - const screenshotPath = path.join(this.storageBasePath, sessionId, screenshot); - - try { - const imageBuffer = await fs.readFile(screenshotPath); - const pngImage = await pdfDoc.embedPng(imageBuffer); - - const { width, height } = page.getSize(); - const imgDims = pngImage.scale(Math.min(width / pngImage.width, height / pngImage.height) * 0.8); - - page.drawImage(pngImage, { - x: (width - imgDims.width) / 2, - y: (height - imgDims.height) / 2, - width: imgDims.width, - height: imgDims.height - }); - - // Adicionar timestamp no screenshot - const timestampFromFilename = screenshot.split('_')[1]?.split('.')[0]; - if (timestampFromFilename) { - const screenshotDate = new Date(parseInt(timestampFromFilename)); - page.drawText(`Capturado em: ${format(screenshotDate, 'dd/MM/yyyy HH:mm:ss', { locale: ptBR })}`, { - x: 50, - y: 50, - size: 10, - font, - color: rgb(0, 0, 0) - }); - } - } catch (error) { - console.error('Erro ao processar screenshot:', error); - } - } - } - - // Página de relatório detalhado - const reportPage = pdfDoc.addPage(); - const { width, height } = reportPage.getSize(); - let yPosition = height - 50; - - // Título - reportPage.drawText('RELATÓRIO DE SESSÃO DE COLETA DE PROVA DIGITAL', { - x: 50, - y: yPosition, - size: 16, - font: boldFont, - color: rgb(0, 0, 0.8) - }); - yPosition -= 40; - - // Informações da sessão - evitar JSON.stringify que pode causar problemas - const sessionInfo = [ - `ID da Sessão: ${sessionData.id}`, - `Data/Hora de Início: ${format(sessionData.startTime, 'dd/MM/yyyy HH:mm:ss', { locale: ptBR })}`, - `Data/Hora de Fim: ${sessionData.endTime ? format(sessionData.endTime, 'dd/MM/yyyy HH:mm:ss', { locale: ptBR }) : 'Em andamento'}`, - `Duração: ${sessionData.endTime ? this.calculateDuration(sessionData.startTime, sessionData.endTime) : 'Em andamento'}`, - ``, - `INFORMAÇÕES DE REDE:`, - `Endereço IP: ${sessionData.ipAddress}`, - `DNS: ${sessionData.dnsInfo}`, - `User-Agent: ${sessionData.userAgent.substring(0, 100)}...`, - `` - ]; - - if (sessionData.geolocation) { - sessionInfo.push( - `GEOLOCALIZAÇÃO:`, - `Latitude: ${sessionData.geolocation.latitude}`, - `Longitude: ${sessionData.geolocation.longitude}`, - `` - ); - } - - sessionInfo.push( - `CAPTURAS DE TELA: ${sessionData.screenshots.length}`, - `VÍDEO DA SESSÃO: ${sessionData.videoPath ? 'Disponível' : 'Não disponível'}`, - ``, - `DADOS TÉCNICOS:`, - `Browser: ${sessionData.metadata?.browserInfo?.name || 'N/A'} ${sessionData.metadata?.browserInfo?.version || ''}`, - `Timestamp: ${sessionData.metadata?.timestamp || 'N/A'}` - ); - - // Desenhar informações - for (const info of sessionInfo) { - if (yPosition < 100) { - const newPage = pdfDoc.addPage(); - yPosition = newPage.getSize().height - 50; - } - - reportPage.drawText(info, { - x: 50, - y: yPosition, - size: 10, - font: info.includes(':') && info.endsWith(':') ? boldFont : font, - color: rgb(0, 0, 0) - }); - yPosition -= 15; - } - - // Salvar PDF - const pdfBytes = await pdfDoc.save(); - const reportPath = path.join(this.storageBasePath, sessionId, `relatorio_${sessionId}.pdf`); - await fs.writeFile(reportPath, pdfBytes); - - return reportPath; - } catch (error) { - console.error('Erro ao gerar relatório da sessão:', error); - throw new Error(`Erro ao gerar relatório: ${error.message}`); - } - } - - private generateSessionId(): string { - return `sess_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - } - - private generateSessionHash(sessionData: SessionData): string { - const dataToHash = JSON.stringify({ - id: sessionData.id, - startTime: sessionData.startTime, - endTime: sessionData.endTime, - ipAddress: sessionData.ipAddress, - userAgent: sessionData.userAgent, - screenshots: sessionData.screenshots - }); - - return createHash('sha256').update(dataToHash).digest('hex'); - } - - private async collectDNSInfo(ipAddress: string): Promise { - try { - const dns = require('dns').promises; - const hostnames = await dns.reverse(ipAddress); - return hostnames.join(', '); - } catch (error) { - return 'DNS não resolvido'; - } - } - - private parseBrowserInfo(userAgent: string): any { - // Parser simples de user agent - const browsers = [ - { name: 'Chrome', pattern: /Chrome\/([0-9.]+)/ }, - { name: 'Firefox', pattern: /Firefox\/([0-9.]+)/ }, - { name: 'Safari', pattern: /Safari\/([0-9.]+)/ }, - { name: 'Edge', pattern: /Edge\/([0-9.]+)/ } - ]; - - for (const browser of browsers) { - const match = userAgent.match(browser.pattern); - if (match) { - return { name: browser.name, version: match[1] }; - } - } - - return { name: 'Desconhecido', version: 'N/A' }; - } - - private calculateDuration(start: Date, end: Date): string { - const diffMs = end.getTime() - start.getTime(); - const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); - const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60)); - const diffSeconds = Math.floor((diffMs % (1000 * 60)) / 1000); - - return `${diffHours.toString().padStart(2, '0')}:${diffMinutes.toString().padStart(2, '0')}:${diffSeconds.toString().padStart(2, '0')}`; - } - - async getSession(sessionId: string): Promise { - const session = await prisma.sessaoProva.findUnique({ - where: { id: sessionId } - }); - - if (!session) return null; - - return JSON.parse(session.dadosSessao) as SessionData; - } - - async verifySession(sessionHash: string): Promise { - const session = await prisma.sessaoProva.findFirst({ - where: { hashSessao: sessionHash } - }); - - if (!session) { - return { verified: false, message: 'Sessão não encontrada' }; - } - - return { - verified: true, - session: JSON.parse(session.dadosSessao), - status: session.status, - reportPath: session.relatorioPath - }; - } -}