Skip to content

Commit

Permalink
Merge pull request #49 from Daniel-Alvarenga/main
Browse files Browse the repository at this point in the history
New tables and routes
  • Loading branch information
Daniel-Alvarenga authored Aug 5, 2024
2 parents 5b86eba + 76f7134 commit 42bc97a
Show file tree
Hide file tree
Showing 11 changed files with 306 additions and 13 deletions.
3 changes: 1 addition & 2 deletions client/src/services/api/aluno.js
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,6 @@ export const sendBoletim = async (file, token) => {
});
return response;
} catch (error) {
console.log("ARQUIVO: " + file);
// return error.response.data;
return error.response.data;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
-- CreateTable
CREATE TABLE `materias` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NOT NULL,
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updatedAt` DATETIME(3) NOT NULL,

PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- CreateTable
CREATE TABLE `cursos_materias` (
`cursoId` VARCHAR(191) NOT NULL,
`materiaId` VARCHAR(191) NOT NULL,

PRIMARY KEY (`cursoId`, `materiaId`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- CreateTable
CREATE TABLE `notas` (
`id` VARCHAR(191) NOT NULL,
`alunoId` VARCHAR(191) NOT NULL,
`materiaId` VARCHAR(191) NOT NULL,
`bimestre` INTEGER NOT NULL,
`ano` INTEGER NOT NULL,
`mencao` ENUM('I', 'R', 'B', 'MB') NOT NULL,
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updatedAt` DATETIME(3) NOT NULL,

PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- AddForeignKey
ALTER TABLE `cursos_materias` ADD CONSTRAINT `cursos_materias_cursoId_fkey` FOREIGN KEY (`cursoId`) REFERENCES `cursos`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE `cursos_materias` ADD CONSTRAINT `cursos_materias_materiaId_fkey` FOREIGN KEY (`materiaId`) REFERENCES `materias`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE `notas` ADD CONSTRAINT `notas_alunoId_fkey` FOREIGN KEY (`alunoId`) REFERENCES `alunos`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE `notas` ADD CONSTRAINT `notas_materiaId_fkey` FOREIGN KEY (`materiaId`) REFERENCES `materias`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
46 changes: 42 additions & 4 deletions server/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ model Aluno {
rm String? @unique
tentativasRestantes Int @default(5)
validated Boolean @default(false)
notas Nota[]
turmas AlunoTurma[]
boletins Boletim[]
atividades AlunoAtividade[]
Expand Down Expand Up @@ -91,16 +92,17 @@ model Admin {
}

model Curso {
id String @id @default(uuid())
id String @id @default(uuid())
name String
turno Turno
duracao String
coordenador Coordenador @relation(fields: [coordenadorId], references: [id])
coordenador Coordenador @relation(fields: [coordenadorId], references: [id])
coordenadorId String
materias CursoMateria[]
turma Turma[]
vaga Vaga[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("cursos")
}
Expand Down Expand Up @@ -305,3 +307,39 @@ model Mensagem {
@@map("mensagens")
}

model Materia {
id String @id @default(uuid())
name String
cursos CursoMateria[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Nota Nota[]
@@map("materias")
}

model CursoMateria {
cursoId String @default(uuid())
materiaId String @default(uuid())
curso Curso @relation(fields: [cursoId], references: [id])
materia Materia @relation(fields: [materiaId], references: [id])
@@id([cursoId, materiaId])
@@map("cursos_materias")
}

model Nota {
id String @id @default(uuid())
alunoId String
materiaId String
bimestre Int
ano Int
mencao Mencao
aluno Aluno @relation(fields: [alunoId], references: [id])
materia Materia @relation(fields: [materiaId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("notas")
}
19 changes: 18 additions & 1 deletion server/src/minioService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Client } from "minio";
import { AppError } from './errors/error';
import { promisify } from 'util';
import fs from 'fs';
import stream from 'stream';

export const minioClient = new Client({
endPoint: process.env.MINIO_END_POINT as string,
Expand All @@ -18,4 +21,18 @@ export const uploadToMinio = async (bucketName: string, objectName: string, file
} catch (error) {
throw new AppError(`Error uploading file: ${error}`);
}
};
};

export async function downloadFromMinio(bucketName: string, objectName: string): Promise<Buffer> {
const tempFilePath = `../uploads/tmp/${objectName}`;
const fileStream = fs.createWriteStream(tempFilePath);

const downloadStream = await minioClient.getObject(bucketName, objectName);
downloadStream.pipe(fileStream);

await promisify(stream.finished)(fileStream);

const fileBuffer = fs.readFileSync(tempFilePath);
fs.unlinkSync(tempFilePath);
return fileBuffer;
}
28 changes: 27 additions & 1 deletion server/src/modules/controllers/funcionarioControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import { ValidateRecoveryUseCase } from "../services/funcionario/ValidateRecover
import { RefreshTokenUseCase } from "../services/funcionario/RefreshTokenUseCase";
import { RegisterVagaUseCase } from "../services/funcionario/RegisterVagasUseCase";
import { SetEmpresaParceiraUseCase } from "../services/funcionario/SetAsParceiraUseCase";
import { SetEmpresaParceiraDTO } from "../interfaces/funcionarioDTOs";
import { CompareBoletimDTO, SetEmpresaParceiraDTO } from "../interfaces/funcionarioDTOs";
import { EntidadeEnum } from "../interfaces/sharedDTOs";
import { GetMessagesBetweenUseCase } from "../services/shared/GetChatUseCase";
import { CompareBoletimUseCase } from "../services/funcionario/CompareBoletinsUseCase";
import { GetBoletinsEmAnaliseUseCase } from "../services/funcionario/GetunapprovedUsecase";

export class InitFuncionarioController {
async handle(req: Request, res: Response) {
Expand Down Expand Up @@ -150,6 +152,30 @@ export class GetMessagesBetweenController {

const result = await getMessagesBetween.execute({ email1, identifier1, email2, identifier2 });

return res.status(201).json(result);
}
}

export class CompareBoletimController {
async handle(req: Request, res: Response): Promise<Response> {
const { boletimId } = req.body;
const file = req.file as Express.Multer.File;

const compareBoletimUseCase = new CompareBoletimUseCase();

const result = await compareBoletimUseCase.execute({boletimId, file});

return res.status(200).json(result);

}
}

export class GetBoletinsEmAnaliseController {
async handle(req: Request, res: Response) {
const getBoletinsEmAnaliseUseCase = new GetBoletinsEmAnaliseUseCase();

const result = await getBoletinsEmAnaliseUseCase.execute();

return res.status(201).json(result);
}
}
5 changes: 5 additions & 0 deletions server/src/modules/interfaces/funcionarioDTOs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,9 @@ export interface RegisterVagaDTO {
export interface SetEmpresaParceiraDTO {
funcionarioId: string;
emailEmpresa: string;
}

export interface CompareBoletimDTO {
boletimId: string;
file: Express.Multer.File;
}
68 changes: 64 additions & 4 deletions server/src/modules/services/aluno/SendBoletimUseCase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,22 @@ export class SendBoletimUseCase {
const boletimPath = path.resolve(boletim.path);
const boletimBuffer = fs.readFileSync(boletimPath);

const link = await this.extractAuthUrlFromPdf(boletimBuffer);
const { link, info } = await this.extractInfoFromPdf(boletimBuffer);

// console.log(`Ano Letivo / Semestre: ${info.anoLetivoSemestre}`);
// console.log(`RM: ${info.rm}`);
// console.log(`Nome do aluno: ${info.nomeAluno}`);
// console.log(`Curso: ${info.curso}`);
// console.log(`Modalidade: ${info.modalidade}`);
// console.log(`Série / Módulo: ${info.serieModulo}`);
// console.log(`Turno: ${info.turno}`);

if (info.rm != aluno.rm){
throw new AppError ("Boletim inválido: RM não corresponde");
}
if (info.nomeAluno != aluno.name){
throw new AppError ("Boletim inválido: Nome não corresponde");
}

const bucketName = 'boot';
const objectName = `aluno/${aluno.rm}/boletins/${path.basename(boletim.path)}`;
Expand All @@ -54,15 +69,60 @@ export class SendBoletimUseCase {
}
}

async extractAuthUrlFromPdf(buffer: Buffer): Promise<string> {
async extractInfoFromPdf(buffer: Buffer): Promise<{ link: string, info: any }> {
const data = await pdf(buffer);
const text = data.text;

const urlMatch = text.match(/https:\/\/nsa\.cps\.sp\.gov\.br\?a=[a-z0-9-]+/i);
if (!urlMatch) {
throw new AppError('URL de autenticação não encontrado no boletim.');
throw new AppError('Boletim inválido: URL de autenticação não encontrado no boletim.');
}

const anoLetivoSemestreMatch = text.match(/Ano Letivo \/ Semestre: (\d{4})/);
if (!anoLetivoSemestreMatch) {
throw new AppError('Boletim inválido: Ano Letivo / Semestre não encontrado.');
}

const rmMatch = text.match(/RM: (\d+)/);
if (!rmMatch) {
throw new AppError('Boletim inválido: RM não encontrado.');
}

const nomeAlunoMatch = text.match(/Nome do aluno: ([^\n]+)/);
if (!nomeAlunoMatch) {
throw new AppError('Boletim inválido: Nome do aluno não encontrado.');
}

return urlMatch[0];
const cursoMatch = text.match(/Curso: ([^\n]+)/);
if (!cursoMatch) {
throw new AppError('Boletim inválido: Curso não encontrado.');
}

const modalidadeMatch = text.match(/Modalidade: ([^\n]+)/);
if (!modalidadeMatch) {
throw new AppError('Boletim inválido: Modalidade não encontrada.');
}

const serieModuloMatch = text.match(/Série \/ Módulo: ([^\n]+)/);
if (!serieModuloMatch) {
throw new AppError('Boletim inválido: Série / Módulo não encontrado.');
}

const turnoMatch = text.match(/Turno: ([^\n]+)/);
if (!turnoMatch) {
throw new AppError('Boletim inválido: Turno não encontrado.');
}

const info = {
anoLetivoSemestre: anoLetivoSemestreMatch[1],
rm: rmMatch[1],
nomeAluno: nomeAlunoMatch[1],
curso: cursoMatch[1],
modalidade: modalidadeMatch[1],
serieModulo: serieModuloMatch[1],
turno: turnoMatch[1]
};

return { link: urlMatch[0], info: info };
}
}
47 changes: 47 additions & 0 deletions server/src/modules/services/funcionario/CompareBoletinsUseCase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import fs from 'fs';
import path from 'path';
import { prisma } from "../../../prisma/client";
import { AppError } from "../../../errors/error";
import { CompareBoletimDTO } from '../../interfaces/funcionarioDTOs';
import { downloadFromMinio } from "../../../minioService";
import { clearUploads } from '../shared/helpers/helpers';

export class CompareBoletimUseCase {
async execute({ boletimId, file }: CompareBoletimDTO) {
if (!boletimId) {
throw new AppError('ID do boletim não fornecido.');
}

if (!file || !file.path) {
throw new AppError('Arquivo do boletim não fornecido.');
}

const boletim = await prisma.boletim.findUnique({
where: {
id: boletimId,
},
});

if (!boletim) {
throw new AppError('Boletim não encontrado.');
}

const filePath = path.resolve(file.path);
const fileBuffer = fs.readFileSync(filePath);

const storedBoletimBuffer = await downloadFromMinio('boot', boletim.caminho);

const isEqual = fileBuffer.equals(storedBoletimBuffer);

const novoStatus = isEqual ? 'APROVADO' : 'RECUSADO';

await prisma.boletim.update({
where: { id: boletimId },
data: { status: novoStatus }
});

await clearUploads();

return { message: `Boletim ${novoStatus}.` };
}
}
Loading

0 comments on commit 42bc97a

Please sign in to comment.