first commit
This commit is contained in:
commit
c9fb67e751
423 files changed
+53287
No files matched your search
@@ -0,0 +1,51 @@
|
||||
import { UsuarioRepository } from "../repositories/UsuarioRepository";
|
||||
import bcrypt from "bcryptjs";
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
const JWT_SECRET = "seu-segredo-super-secreto"; // Em produção, use variável de ambiente
|
||||
|
||||
export class AuthService {
|
||||
constructor(private usuarioRepository: UsuarioRepository) {}
|
||||
|
||||
async login(email: string, senha: string) {
|
||||
console.log("AuthService - Login tentativa:", { email }); // Debug
|
||||
|
||||
const usuario = await this.usuarioRepository.findByEmail(email);
|
||||
console.log("AuthService - Usuário encontrado:", usuario); // Debug
|
||||
|
||||
if (!usuario) {
|
||||
throw new Error("Usuário não encontrado");
|
||||
}
|
||||
|
||||
if (!usuario.ativo) {
|
||||
throw new Error("Usuário inativo");
|
||||
}
|
||||
|
||||
const senhaValida = await bcrypt.compare(senha, usuario.senha);
|
||||
console.log("AuthService - Senha válida:", senhaValida); // Debug
|
||||
|
||||
if (!senhaValida) {
|
||||
throw new Error("Senha inválida");
|
||||
}
|
||||
|
||||
const token = jwt.sign(
|
||||
{
|
||||
id: usuario.id,
|
||||
perfil: usuario.perfil,
|
||||
email: usuario.email,
|
||||
},
|
||||
process.env.JWT_SECRET || "default-secret",
|
||||
{ expiresIn: "1d" },
|
||||
);
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: usuario.id,
|
||||
nome: usuario.nome,
|
||||
email: usuario.email,
|
||||
perfil: usuario.perfil,
|
||||
},
|
||||
token,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import OpenTimestamps from 'opentimestamps-javascript'; // Atualizar import
|
||||
import { createHash } from 'crypto';
|
||||
import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
|
||||
export class BlockchainService {
|
||||
private readonly storageDir: string;
|
||||
|
||||
constructor() {
|
||||
this.storageDir = path.join(process.cwd(), 'storage', 'timestamps');
|
||||
this.ensureStorageDir();
|
||||
}
|
||||
|
||||
private async ensureStorageDir() {
|
||||
try {
|
||||
await fs.mkdir(this.storageDir, { recursive: true });
|
||||
} catch (error) {
|
||||
console.error('Erro ao criar diretório de timestamps:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async createTimestamp(buffer: Buffer) {
|
||||
try {
|
||||
// 1. Criar hash do arquivo
|
||||
const fileHash = createHash('sha256').update(buffer).digest();
|
||||
|
||||
// 2. Criar timestamp
|
||||
const timestamp = await OpenTimestamps.stamp(fileHash);
|
||||
|
||||
// 3. Salvar arquivo .ots
|
||||
const otsPath = path.join(this.storageDir, `${fileHash.toString('hex')}.ots`);
|
||||
await fs.writeFile(otsPath, Buffer.from(timestamp));
|
||||
|
||||
return {
|
||||
hash: fileHash.toString('hex'),
|
||||
timestamp,
|
||||
otsPath
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Erro ao criar timestamp:', error);
|
||||
throw new Error('Falha ao criar timestamp na blockchain');
|
||||
}
|
||||
}
|
||||
|
||||
async verifyTimestamp(hash: string, timestamp: Buffer) {
|
||||
try {
|
||||
// Usar a API correta do OpenTimestamps
|
||||
const result = await OpenTimestamps.verify(
|
||||
Buffer.from(timestamp),
|
||||
Buffer.from(hash, 'hex')
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
return {
|
||||
verified: false,
|
||||
message: 'Timestamp ainda não confirmado na blockchain'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
verified: true,
|
||||
timestamp: new Date(result * 1000),
|
||||
message: 'Timestamp verificado na blockchain do Bitcoin'
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Erro ao verificar timestamp:', error);
|
||||
return {
|
||||
verified: false,
|
||||
message: 'Erro ao verificar timestamp'
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { google } from "googleapis";
|
||||
import { Client } from "@microsoft/microsoft-graph-client";
|
||||
|
||||
export class CalendarioService {
|
||||
async agendarAudiencia(audiencia: any): Promise<string> {
|
||||
// Implementar integração com Google Calendar ou Outlook
|
||||
const eventoId = await this.criarEvento({
|
||||
titulo: `Audiência - ${audiencia.processo.numero}`,
|
||||
inicio: audiencia.data,
|
||||
fim: new Date(audiencia.data.getTime() + 3600000),
|
||||
descricao: audiencia.observacoes,
|
||||
local: audiencia.local,
|
||||
});
|
||||
|
||||
return eventoId;
|
||||
}
|
||||
|
||||
private async criarEvento(dados: any): Promise<string> {
|
||||
// Implementar criação do evento
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { documentTemplates } from '../utils/documentTemplates';
|
||||
|
||||
interface DocumentoData {
|
||||
tipo: keyof typeof documentTemplates.templates;
|
||||
variaveis: Record<string, any>;
|
||||
config: {
|
||||
cabecalho: string;
|
||||
rodape: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface TemplateData {
|
||||
cliente?: any;
|
||||
atendimento?: any;
|
||||
processo?: any;
|
||||
advogado?: any;
|
||||
escritorio?: any;
|
||||
}
|
||||
|
||||
export class DocumentoService {
|
||||
static processarVariaveisData(): Record<string, string> {
|
||||
const hoje = new Date();
|
||||
const formatador = new Intl.DateTimeFormat('pt-BR', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
});
|
||||
|
||||
return {
|
||||
'data.atual': hoje.toLocaleDateString('pt-BR'),
|
||||
'data.atualExtenso': formatador.format(hoje), // Usar o formatador corretamente
|
||||
'data.dia': hoje.getDate().toString(),
|
||||
'data.mes': (hoje.getMonth() + 1).toString(),
|
||||
'data.mesExtenso': new Intl.DateTimeFormat('pt-BR', { month: 'long' }).format(hoje),
|
||||
'data.ano': hoje.getFullYear().toString()
|
||||
};
|
||||
}
|
||||
|
||||
static preencherTemplate(template: string, variaveis: Record<string, any>): string {
|
||||
return template.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
|
||||
const valor = key.split('.').reduce((obj: any, k: string) => obj?.[k], variaveis);
|
||||
return valor ?? match;
|
||||
});
|
||||
}
|
||||
|
||||
static processarVariaveis(template: string, data: TemplateData): string {
|
||||
const dataProcessada = {
|
||||
...data,
|
||||
data: this.processarVariaveisData()
|
||||
};
|
||||
|
||||
return template.replace(/\{\{([^}]+)\}\}/g, (match, path) => {
|
||||
try {
|
||||
const valor = path.split('.').reduce((obj, key) => obj?.[key], dataProcessada);
|
||||
if (valor === undefined || valor === null) {
|
||||
console.warn(`Variável não encontrada ou nula: ${path}`);
|
||||
return match; // Retornar o match original em vez de string vazia
|
||||
}
|
||||
console.log(`Substituindo ${path} por:`, valor); // Debug log
|
||||
return String(valor);
|
||||
} catch (error) {
|
||||
console.warn(`Erro ao processar variável ${path}:`, error);
|
||||
return match; // Retornar o match original em vez de string vazia
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static async gerarDocumentoFinal(documentoId: string): Promise<string> {
|
||||
// Buscar documento e dados relacionados
|
||||
const [documento, config] = await Promise.all([
|
||||
api.get(`/documentos/${documentoId}`),
|
||||
api.get('/documentos/configuracoes')
|
||||
]);
|
||||
|
||||
// Buscar dados relacionados
|
||||
const [cliente, atendimento, processo, advogado] = await Promise.all([
|
||||
documento.data.clienteId ? api.get(`/clientes/${documento.data.clienteId}`) : null,
|
||||
documento.data.atendimentoId ? api.get(`/atendimentos/${documento.data.atendimentoId}`) : null,
|
||||
documento.data.processoId ? api.get(`/processos/${documento.data.processoId}`) : null,
|
||||
api.get('/auth/me')
|
||||
]);
|
||||
|
||||
// Processar template com as variáveis
|
||||
const conteudoProcessado = this.processarVariaveis(documento.data.conteudo, {
|
||||
cliente: cliente?.data,
|
||||
atendimento: atendimento?.data,
|
||||
processo: processo?.data,
|
||||
advogado: advogado.data,
|
||||
escritorio: config.data
|
||||
});
|
||||
|
||||
return conteudoProcessado;
|
||||
}
|
||||
|
||||
static gerarDocumento(dados: DocumentoData): string {
|
||||
// 1. Pegar o template específico
|
||||
const templateConteudo = documentTemplates.templates[dados.tipo];
|
||||
|
||||
// 2. Preencher o conteúdo com as variaveis
|
||||
const conteudoPreenchido = this.preencherTemplate(templateConteudo, dados.variaveis);
|
||||
|
||||
// 3. Montar documento final com template base
|
||||
return this.preencherTemplate(documentTemplates.base, {
|
||||
cabecalho: dados.config.cabecalho,
|
||||
conteudo: conteudoPreenchido,
|
||||
rodape: dados.config.rodape
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import { createHash } from 'crypto';
|
||||
import { ExifTool } from 'exiftool-vendored';
|
||||
import imageSize from 'image-size';
|
||||
import PDFParser from 'pdf2json';
|
||||
import { detectFileType } from 'detect-file-type';
|
||||
|
||||
export class FileValidator {
|
||||
private exiftool: ExifTool;
|
||||
|
||||
constructor() {
|
||||
this.exiftool = new ExifTool();
|
||||
}
|
||||
|
||||
async validateFile(file: Buffer, mimeType: string) {
|
||||
// Validação básica de integridade
|
||||
const basicValidation = await this.performBasicValidation(file);
|
||||
if (!basicValidation.valid) {
|
||||
return basicValidation;
|
||||
}
|
||||
|
||||
// Validação específica por tipo
|
||||
switch (mimeType) {
|
||||
case 'application/pdf':
|
||||
return await this.validatePDF(file);
|
||||
case 'image/jpeg':
|
||||
case 'image/png':
|
||||
return await this.validateImage(file, mimeType);
|
||||
default:
|
||||
return { valid: false, error: 'Tipo de arquivo não suportado' };
|
||||
}
|
||||
}
|
||||
|
||||
private async performBasicValidation(file: Buffer) {
|
||||
// Verificar tamanho mínimo
|
||||
if (file.length < 100) {
|
||||
return { valid: false, error: 'Arquivo muito pequeno ou vazio' };
|
||||
}
|
||||
|
||||
// Verificar assinatura do arquivo
|
||||
const fileType = await detectFileType(file);
|
||||
if (!fileType) {
|
||||
return { valid: false, error: 'Tipo de arquivo não reconhecido' };
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
private async validatePDF(file: Buffer) {
|
||||
return new Promise((resolve) => {
|
||||
const pdfParser = new PDFParser();
|
||||
|
||||
pdfParser.on('pdfParser_dataReady', () => {
|
||||
resolve({ valid: true, metadata: pdfParser.getAllFieldsTypes() });
|
||||
});
|
||||
|
||||
pdfParser.on('pdfParser_dataError', (error) => {
|
||||
resolve({ valid: false, error: `PDF inválido: ${error}` });
|
||||
});
|
||||
|
||||
pdfParser.parseBuffer(file);
|
||||
});
|
||||
}
|
||||
|
||||
private async validateImage(file: Buffer, mimeType: string) {
|
||||
try {
|
||||
// Verificar dimensões
|
||||
const dimensions = imageSize(file);
|
||||
if (!dimensions.width || !dimensions.height) {
|
||||
return { valid: false, error: 'Dimensões da imagem inválidas' };
|
||||
}
|
||||
|
||||
// Extrair metadados EXIF
|
||||
const metadata = await this.exiftool.readBuffer(file);
|
||||
|
||||
// Verificações específicas para JPEG
|
||||
if (mimeType === 'image/jpeg') {
|
||||
if (!metadata.FileType || metadata.FileType !== 'JPEG') {
|
||||
return { valid: false, error: 'Assinatura JPEG inválida' };
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
metadata: {
|
||||
dimensions,
|
||||
exif: metadata
|
||||
}
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
return { valid: false, error: `Erro na validação da imagem: ${error.message}` };
|
||||
}
|
||||
}
|
||||
|
||||
async validateMetadata(metadata: any) {
|
||||
const requiredFields = ['userAgent', 'ipAddress', 'timestamp'];
|
||||
const missingFields = requiredFields.filter(field => !metadata[field]);
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `Campos obrigatórios ausentes: ${missingFields.join(', ')}`
|
||||
};
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
generateHashes(file: Buffer) {
|
||||
return {
|
||||
sha256: createHash('sha256').update(file).digest('hex'),
|
||||
sha512: createHash('sha512').update(file).digest('hex'),
|
||||
md5: createHash('md5').update(file).digest('hex'),
|
||||
blake2b: createHash('blake2b512').update(file).digest('hex'), // Hash adicional
|
||||
whirlpool: createHash('whirlpool').update(file).digest('hex'), // Hash adicional
|
||||
};
|
||||
}
|
||||
|
||||
async cleanup() {
|
||||
await this.exiftool.end();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Alerta } from "@prisma/client";
|
||||
|
||||
export class NotificacaoService {
|
||||
async enviarNotificacao(
|
||||
usuarioId: string,
|
||||
tipo: string,
|
||||
mensagem: string,
|
||||
): Promise<void> {
|
||||
// Implementar push notifications (ex: usando Firebase Cloud Messaging)
|
||||
await this.salvarAlerta({
|
||||
usuarioId,
|
||||
tipo,
|
||||
mensagem,
|
||||
dataAlerta: new Date(),
|
||||
});
|
||||
}
|
||||
|
||||
private async salvarAlerta(dados: Partial<Alerta>): Promise<Alerta> {
|
||||
// Implementar salvamento no banco
|
||||
return null as any;
|
||||
}
|
||||
|
||||
async verificarPrazos(): Promise<void> {
|
||||
// Verificar prazos próximos e enviar alertas
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
Processo,
|
||||
StatusProcesso,
|
||||
NaturezaProcesso,
|
||||
TipoParte,
|
||||
} from "../types/entities";
|
||||
import axios from "axios";
|
||||
|
||||
export class ProcessoService {
|
||||
constructor(private repository: any) {}
|
||||
|
||||
async criarProcesso(
|
||||
processo: Omit<Processo, "id" | "dataCriacao" | "dataAtualizacao">,
|
||||
): Promise<Processo> {
|
||||
const novoProcesso: Processo = {
|
||||
...processo,
|
||||
id: crypto.randomUUID(),
|
||||
dataCriacao: new Date(),
|
||||
dataAtualizacao: new Date(),
|
||||
prazos: [],
|
||||
documentos: [],
|
||||
};
|
||||
|
||||
return await this.repository.create(novoProcesso);
|
||||
}
|
||||
|
||||
async atualizarStatus(id: string, status: StatusProcesso): Promise<Processo> {
|
||||
return await this.repository.update(id, {
|
||||
status,
|
||||
dataAtualizacao: new Date(),
|
||||
});
|
||||
}
|
||||
|
||||
async adicionarPrazo(processoId: string, prazo: any): Promise<Processo> {
|
||||
const processo = await this.repository.findById(processoId);
|
||||
if (!processo) throw new Error("Processo não encontrado");
|
||||
|
||||
processo.prazos.push({
|
||||
...prazo,
|
||||
id: crypto.randomUUID(),
|
||||
processoId,
|
||||
});
|
||||
|
||||
return await this.repository.update(processoId, processo);
|
||||
}
|
||||
|
||||
async consultarProcessoTribunal(numero: string): Promise<any> {
|
||||
// Implementar integração com APIs dos tribunais
|
||||
// Ex: TJ, TRF, etc
|
||||
}
|
||||
|
||||
async agendarAudiencia(data: any): Promise<void> {
|
||||
// Integração com Google Calendar/Outlook
|
||||
}
|
||||
|
||||
async gerarPrazos(processo: Processo): Promise<void> {
|
||||
// Gerar prazos automaticamente baseado na natureza
|
||||
}
|
||||
|
||||
async monitorarMovimentacoes(processo: Processo): Promise<void> {
|
||||
// Webscraping ou integração com APIs dos tribunais
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { provaQueue } from '../workers/ProvaWorker';
|
||||
import { prisma } from '../config/database';
|
||||
import { createHash } from 'crypto';
|
||||
import { PDFDocument, StandardFonts } from 'pdf-lib';
|
||||
import * as OpenTimestamps from 'opentimestamps';
|
||||
import sharp from 'sharp';
|
||||
|
||||
export class ProvaService {
|
||||
private fileValidator: FileValidator;
|
||||
private reportGenerator: ReportGenerator;
|
||||
|
||||
constructor() {
|
||||
this.fileValidator = new FileValidator();
|
||||
this.reportGenerator = new ReportGenerator();
|
||||
}
|
||||
|
||||
async processFile(file: Express.Multer.File, metadata: any) {
|
||||
try {
|
||||
console.log('Iniciando processamento do arquivo:', file.originalname); // Debug log
|
||||
|
||||
// Validar arquivo e metadados
|
||||
if (!file.buffer || !file.originalname) {
|
||||
throw new Error('Arquivo inválido');
|
||||
}
|
||||
|
||||
// Criar registro inicial
|
||||
const prova = await prisma.prova.create({
|
||||
data: {
|
||||
fileName: file.originalname,
|
||||
mimeType: file.mimetype,
|
||||
status: 'PENDENTE',
|
||||
metadata: {
|
||||
...metadata,
|
||||
fileSize: file.size,
|
||||
originalName: file.originalname
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Registro criado com sucesso:', prova.id); // Debug log
|
||||
|
||||
// Adicionar à fila
|
||||
await provaQueue.add('processar-prova', {
|
||||
provaId: prova.id,
|
||||
fileData: file.buffer.toString('base64'),
|
||||
metadata
|
||||
});
|
||||
|
||||
return prova;
|
||||
} catch (error) {
|
||||
console.error('Erro detalhado no processamento:', error);
|
||||
throw new Error(`Erro ao processar arquivo ${file.originalname}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private generateChecksums(buffer: Buffer) {
|
||||
return {
|
||||
sha256: createHash('sha256').update(buffer).digest('hex'),
|
||||
sha512: createHash('sha512').update(buffer).digest('hex'),
|
||||
md5: createHash('md5').update(buffer).digest('hex')
|
||||
};
|
||||
}
|
||||
|
||||
private async registerTimestamp(buffer: Buffer) {
|
||||
const digest = createHash('sha256').update(buffer).digest();
|
||||
const timestamp = await OpenTimestamps.stamp(digest);
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
private async processFileByType(file: Express.Multer.File) {
|
||||
if (file.mimetype.startsWith('image/')) {
|
||||
return await this.processImage(file.buffer);
|
||||
}
|
||||
return file.buffer;
|
||||
}
|
||||
|
||||
private async processImage(buffer: Buffer) {
|
||||
return await sharp(buffer)
|
||||
.resize(1200, 900, {
|
||||
fit: 'inside',
|
||||
withoutEnlargement: true
|
||||
})
|
||||
.toBuffer();
|
||||
}
|
||||
|
||||
private async generateReport(data: any) {
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
const page = pdfDoc.addPage();
|
||||
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
||||
|
||||
// Adicionar conteúdo ao PDF
|
||||
// ... implementar a geração do relatório ...
|
||||
|
||||
return {
|
||||
path: '/path/to/report.pdf',
|
||||
buffer: await pdfDoc.save()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';
|
||||
import QRCode from 'qrcode';
|
||||
import { format } from 'date-fns';
|
||||
import ptBR from 'date-fns/locale/pt-BR';
|
||||
|
||||
export class ReportGenerator {
|
||||
async generateReport(data: {
|
||||
file: Express.Multer.File;
|
||||
checksums: { sha256: string; sha512: string; md5: string };
|
||||
timestamp: Buffer;
|
||||
metadata: any;
|
||||
processedFile: Buffer;
|
||||
verificationUrl: string;
|
||||
}) {
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
||||
const helveticaBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
||||
|
||||
// Página 1 - Resumo
|
||||
const page1 = pdfDoc.addPage();
|
||||
const { width, height } = page1.getSize();
|
||||
|
||||
// Gerar QR Code para verificação
|
||||
const qrCode = await QRCode.toDataURL(data.verificationUrl);
|
||||
const qrCodeImage = await pdfDoc.embedPng(qrCode);
|
||||
|
||||
// Cabeçalho
|
||||
page1.drawText('Relatório de Validação Digital', {
|
||||
x: 50,
|
||||
y: height - 50,
|
||||
size: 24,
|
||||
font: helveticaBold,
|
||||
});
|
||||
|
||||
// Informações do Arquivo
|
||||
const fileInfo = [
|
||||
`Nome do Arquivo: ${data.file.originalname}`,
|
||||
`Tipo: ${data.file.mimetype}`,
|
||||
`Tamanho: ${(data.file.size / 1024).toFixed(2)} KB`,
|
||||
`Data Upload: ${format(new Date(), "dd/MM/yyyy HH:mm:ss", { locale: ptBR })}`,
|
||||
`Data Validação: ${format(new Date(), "dd/MM/yyyy HH:mm:ss", { locale: ptBR })}`
|
||||
];
|
||||
|
||||
// Checksums
|
||||
const checksumInfo = [
|
||||
'Checksums:',
|
||||
`SHA-256: ${data.checksums.sha256}`,
|
||||
`SHA-512: ${data.checksums.sha512}`,
|
||||
`MD5: ${data.checksums.md5}`
|
||||
];
|
||||
|
||||
// Informações do Dispositivo
|
||||
const deviceInfo = [
|
||||
'Informações do Dispositivo:',
|
||||
`Navegador: ${data.metadata.userAgent}`,
|
||||
`IP: ${data.metadata.ipAddress}`,
|
||||
`Idioma: ${data.metadata.language}`,
|
||||
`Resolução: ${data.metadata.viewportWidth}x${data.metadata.viewportHeight}`
|
||||
];
|
||||
|
||||
// Desenhar seções
|
||||
this.drawSection(page1, fileInfo, height - 100, helveticaFont);
|
||||
this.drawSection(page1, checksumInfo, height - 250, helveticaFont);
|
||||
this.drawSection(page1, deviceInfo, height - 400, helveticaFont);
|
||||
|
||||
// QR Code e instruções de verificação
|
||||
page1.drawImage(qrCodeImage, {
|
||||
x: width - 150,
|
||||
y: height - 150,
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
|
||||
// Página 2 - Detalhes Técnicos
|
||||
const page2 = pdfDoc.addPage();
|
||||
|
||||
// Adicionar detalhes técnicos, como estrutura do arquivo, metadados extras, etc.
|
||||
// ...
|
||||
|
||||
// Salvar PDF
|
||||
const pdfBytes = await pdfDoc.save();
|
||||
return pdfBytes;
|
||||
}
|
||||
|
||||
private drawSection(page: any, lines: string[], startY: number, font: any) {
|
||||
lines.forEach((line, index) => {
|
||||
page.drawText(line, {
|
||||
x: 50,
|
||||
y: startY - (index * 20),
|
||||
size: 12,
|
||||
font,
|
||||
color: rgb(0, 0, 0),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import axios from "axios";
|
||||
import { MovimentacaoTribunal } from "@prisma/client";
|
||||
|
||||
export class TribunalService {
|
||||
private apiKey: string;
|
||||
|
||||
constructor(apiKey: string) {
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
async consultarProcesso(
|
||||
numero: string,
|
||||
tribunal: string,
|
||||
): Promise<MovimentacaoTribunal[]> {
|
||||
const endpoints = {
|
||||
TJ: "https://api.tribunal.jus.br/processo",
|
||||
TRT: "https://api.trt.jus.br/processo",
|
||||
TRF: "https://api.trf.jus.br/processo",
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${endpoints[tribunal]}/${numero}`, {
|
||||
headers: { Authorization: `Bearer ${this.apiKey}` },
|
||||
});
|
||||
|
||||
return this.formatarMovimentacoes(response.data);
|
||||
} catch (error) {
|
||||
console.error(`Erro ao consultar ${tribunal}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private formatarMovimentacoes(data: any): MovimentacaoTribunal[] {
|
||||
// Implementar formatação específica para cada tribunal
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export const api = axios.create({
|
||||
baseURL: '/api',
|
||||
timeout: 30000, // Aumentar para 30 segundos
|
||||
timeoutErrorMessage: 'Tempo limite da requisição excedido. Por favor, tente novamente.'
|
||||
});
|
||||
|
||||
// Interceptor para adicionar token em todas as requisições
|
||||
api.interceptors.request.use(config => {
|
||||
const token = localStorage.getItem('@AdvX:token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
api.interceptors.response.use(
|
||||
response => response,
|
||||
error => {
|
||||
if (axios.isAxiosError(error)) {
|
||||
if (error.code === 'ECONNABORTED') {
|
||||
// Erro específico de timeout
|
||||
console.error('Timeout da requisição:', {
|
||||
url: error.config?.url,
|
||||
method: error.config?.method,
|
||||
timeout: error.config?.timeout
|
||||
});
|
||||
return Promise.reject(new Error('A operação demorou muito para responder. Por favor, tente novamente.'));
|
||||
}
|
||||
|
||||
// Outros erros do Axios
|
||||
console.error('API Error:', {
|
||||
message: error.message,
|
||||
status: error.response?.status,
|
||||
data: error.response?.data,
|
||||
url: error.config?.url,
|
||||
method: error.config?.method
|
||||
});
|
||||
} else if (error.response) {
|
||||
console.error('API Error Response:', {
|
||||
status: error.response.status,
|
||||
statusText: error.response.statusText,
|
||||
data: error.response.data,
|
||||
url: error.config?.url,
|
||||
method: error.config?.method
|
||||
});
|
||||
} else if (error.request) {
|
||||
console.error('API Error Request:', error.request);
|
||||
} else {
|
||||
console.error('API Error:', error.message);
|
||||
}
|
||||
|
||||
if (error.response && error.response.status === 401) {
|
||||
// Remover token inválido
|
||||
localStorage.removeItem('@AdvX:token');
|
||||
|
||||
// Redirecionar para login se não estiver na página de login
|
||||
if (window.location.pathname !== '/login') {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default api;
|
||||
@@ -0,0 +1,26 @@
|
||||
import axios from "axios";
|
||||
import { DatajudSearchResponse } from "../types/datajud";
|
||||
|
||||
export const datajudApi = {
|
||||
async consultarProcesso(numeroProcesso: string, tribunal: string) {
|
||||
try {
|
||||
const response = await axios.post<DatajudSearchResponse>(
|
||||
"/api/datajud/consulta",
|
||||
{
|
||||
numeroProcesso,
|
||||
tribunal,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return response.data.hits.hits[0]?._source;
|
||||
} catch (error) {
|
||||
console.error("Erro ao consultar DataJud:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
import { createClient } from 'webdav';
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
const client = createClient('https://cloud.martinsevieira.adv.br/remote.php/dav/files/rodrigo/CLIENTES', {
|
||||
username: 'rodrigo',
|
||||
password: '34641747'
|
||||
});
|
||||
|
||||
export class NextCloudService {
|
||||
static async uploadFile(file: Buffer, clienteNome: string, tipo: string = '', atendimentoProtocolo?: string): Promise<string> {
|
||||
try {
|
||||
// Limpar nome do cliente para usar como pasta (remover acentos e caracteres especiais)
|
||||
const pastaCliente = clienteNome
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.toUpperCase()
|
||||
.replace(/[^a-zA-Z0-9]/g, '_');
|
||||
|
||||
// Se tiver protocolo, extrair apenas os números
|
||||
const pastaProtocolo = atendimentoProtocolo
|
||||
? atendimentoProtocolo.replace(/\D/g, '')
|
||||
: '';
|
||||
|
||||
// Construir caminho da pasta garantindo a estrutura correta
|
||||
let pasta = pastaCliente;
|
||||
if (tipo) pasta += `/${tipo.toLowerCase()}`; // Pasta atendimento em minúsculo
|
||||
if (pastaProtocolo) pasta += `/${pastaProtocolo}`; // Apenas números do protocolo
|
||||
|
||||
console.log('Criando pasta:', pasta); // Debug
|
||||
|
||||
// Criar pasta se não existir
|
||||
await client.createDirectory(pasta, { recursive: true });
|
||||
|
||||
// Gerar nome do arquivo com timestamp
|
||||
const fileName = `${Date.now()}_${file.name.replace(/[^a-zA-Z0-9.-]/g, '_')}`;
|
||||
const filePath = `${pasta}/${fileName}`;
|
||||
|
||||
console.log('Uploadando arquivo:', filePath); // Debug
|
||||
|
||||
// Upload do arquivo
|
||||
await client.putFileContents(filePath, file);
|
||||
|
||||
return filePath;
|
||||
} catch (error) {
|
||||
console.error('Erro no upload para NextCloud:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async deleteFile(filePath: string): Promise<void> {
|
||||
try {
|
||||
if (await client.exists(filePath)) {
|
||||
await client.deleteFile(filePath);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erro ao deletar arquivo:', error);
|
||||
throw new Error('Não foi possível deletar o arquivo do Nextcloud');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import api from './api';
|
||||
import { NextCloudService } from './nextcloud';
|
||||
|
||||
export async function uploadFile(
|
||||
file: File,
|
||||
clienteNome: string,
|
||||
tipo: string = '',
|
||||
protocolo?: string
|
||||
): Promise<any> {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('clienteNome', clienteNome);
|
||||
if (tipo) formData.append('tipo', tipo);
|
||||
if (protocolo) formData.append('protocolo', protocolo);
|
||||
|
||||
// Fazer upload para o NextCloud
|
||||
const uploadResponse = await api.post('/atendimentos/upload', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
});
|
||||
|
||||
// Criar anexo no banco
|
||||
const anexoData = {
|
||||
nome: file.name,
|
||||
tipo: tipo || 'GERAL',
|
||||
tamanho: file.size,
|
||||
url: uploadResponse.data.url
|
||||
};
|
||||
|
||||
const anexoResponse = await api.post('/anexos', anexoData);
|
||||
return anexoResponse.data;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erro no upload:', error);
|
||||
throw new Error('Falha no upload do arquivo');
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteFile = async (anexoId: string, filePath: string) => {
|
||||
try {
|
||||
await NextCloudService.deleteFile(filePath);
|
||||
await api.delete(`/anexos/${anexoId}`);
|
||||
} catch (error) {
|
||||
console.error('Erro ao deletar arquivo:', error);
|
||||
throw new Error('Falha ao deletar arquivo');
|
||||
}
|
||||
};
|
||||
Reference in new issue
Block a user