diff --git a/prisma/migrations/20241009025017_refinamento_de_propriedades_das_entidades_do_sistema/migration.sql b/prisma/migrations/20241009025017_refinamento_de_propriedades_das_entidades_do_sistema/migration.sql new file mode 100644 index 0000000..466dfe3 --- /dev/null +++ b/prisma/migrations/20241009025017_refinamento_de_propriedades_das_entidades_do_sistema/migration.sql @@ -0,0 +1,140 @@ +/* + Warnings: + + - The values [DISCENTE] on the enum `Role` will be removed. If these variants are still used in the database, this will fail. + - You are about to drop the column `cursoId` on the `Disciplina` table. All the data in the column will be lost. + - You are about to drop the column `discenteId` on the `Disciplina` table. All the data in the column will be lost. + - You are about to drop the column `docenteId` on the `Disciplina` table. All the data in the column will be lost. + - You are about to drop the column `status` on the `Disciplina` table. All the data in the column will be lost. + - You are about to drop the `Curso` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `Discente` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `DisciplinaEmCurso` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `Docente` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `HistoricoAlunoDisciplina` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `LivroEmprestadoBiblioteca` table. If the table is not empty, all the data it contains will be lost. + - Added the required column `codigo` to the `Disciplina` table without a default value. This is not possible if the table is not empty. + +*/ +-- CreateEnum +CREATE TYPE "SituacaoDisciplina" AS ENUM ('APROVADO', 'EM_PROGRESSO', 'TRANCADA', 'REPROVADO', 'DISPENSA', 'REPROVADO_POR_FALTA'); + +-- AlterEnum +BEGIN; +CREATE TYPE "Role_new" AS ENUM ('ADMIN', 'ALUNO', 'DOCENTE'); +ALTER TABLE "User" ALTER COLUMN "role" DROP DEFAULT; +ALTER TABLE "User" ALTER COLUMN "role" TYPE "Role_new" USING ("role"::text::"Role_new"); +ALTER TYPE "Role" RENAME TO "Role_old"; +ALTER TYPE "Role_new" RENAME TO "Role"; +DROP TYPE "Role_old"; +ALTER TABLE "User" ALTER COLUMN "role" SET DEFAULT 'ALUNO'; +COMMIT; + +-- DropForeignKey +ALTER TABLE "Curso" DROP CONSTRAINT "Curso_universidadeId_fkey"; + +-- DropForeignKey +ALTER TABLE "Discente" DROP CONSTRAINT "Discente_cursoId_fkey"; + +-- DropForeignKey +ALTER TABLE "Discente" DROP CONSTRAINT "Discente_livroId_fkey"; + +-- DropForeignKey +ALTER TABLE "Discente" DROP CONSTRAINT "Discente_userId_fkey"; + +-- DropForeignKey +ALTER TABLE "Disciplina" DROP CONSTRAINT "Disciplina_cursoId_fkey"; + +-- DropForeignKey +ALTER TABLE "Disciplina" DROP CONSTRAINT "Disciplina_discenteId_fkey"; + +-- DropForeignKey +ALTER TABLE "Disciplina" DROP CONSTRAINT "Disciplina_docenteId_fkey"; + +-- DropForeignKey +ALTER TABLE "DisciplinaEmCurso" DROP CONSTRAINT "DisciplinaEmCurso_discenteId_fkey"; + +-- DropForeignKey +ALTER TABLE "DisciplinaEmCurso" DROP CONSTRAINT "DisciplinaEmCurso_disciplinaId_fkey"; + +-- DropForeignKey +ALTER TABLE "DisciplinaEmCurso" DROP CONSTRAINT "DisciplinaEmCurso_docenteId_fkey"; + +-- DropForeignKey +ALTER TABLE "Docente" DROP CONSTRAINT "Docente_cursoId_fkey"; + +-- DropForeignKey +ALTER TABLE "Docente" DROP CONSTRAINT "Docente_userId_fkey"; + +-- DropForeignKey +ALTER TABLE "HistoricoAlunoDisciplina" DROP CONSTRAINT "HistoricoAlunoDisciplina_discenteId_fkey"; + +-- DropForeignKey +ALTER TABLE "HistoricoAlunoDisciplina" DROP CONSTRAINT "HistoricoAlunoDisciplina_disciplinaId_fkey"; + +-- DropForeignKey +ALTER TABLE "HistoricoAlunoDisciplina" DROP CONSTRAINT "HistoricoAlunoDisciplina_docenteId_fkey"; + +-- AlterTable +ALTER TABLE "Disciplina" DROP COLUMN "cursoId", +DROP COLUMN "discenteId", +DROP COLUMN "docenteId", +DROP COLUMN "status", +ADD COLUMN "alunoId" INTEGER, +ADD COLUMN "cargaHoraria" INTEGER NOT NULL DEFAULT 0, +ADD COLUMN "docente" TEXT NOT NULL DEFAULT 'Desconhecido', +ADD COLUMN "situacao" "SituacaoDisciplina" DEFAULT 'EM_PROGRESSO', +DROP COLUMN "codigo", +ADD COLUMN "codigo" INTEGER NOT NULL; + +-- AlterTable +ALTER TABLE "User" ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ALTER COLUMN "role" SET DEFAULT 'ALUNO'; + +-- DropTable +DROP TABLE "Curso"; + +-- DropTable +DROP TABLE "Discente"; + +-- DropTable +DROP TABLE "DisciplinaEmCurso"; + +-- DropTable +DROP TABLE "Docente"; + +-- DropTable +DROP TABLE "HistoricoAlunoDisciplina"; + +-- DropTable +DROP TABLE "LivroEmprestadoBiblioteca"; + +-- DropEnum +DROP TYPE "DisciplinaEmCursoStatus"; + +-- DropEnum +DROP TYPE "DisciplinaStatus"; + +-- CreateTable +CREATE TABLE "Aluno" ( + "id" SERIAL NOT NULL, + "userId" INTEGER NOT NULL, + "nome" TEXT NOT NULL, + "matricula" VARCHAR(9) NOT NULL, + "curso" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Aluno_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Aluno_userId_key" ON "Aluno"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Aluno_matricula_key" ON "Aluno"("matricula"); + +-- AddForeignKey +ALTER TABLE "Aluno" ADD CONSTRAINT "Aluno_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Disciplina" ADD CONSTRAINT "Disciplina_alunoId_fkey" FOREIGN KEY ("alunoId") REFERENCES "Aluno"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/migrations/20241009025141_altera_nomenclatura_user_para_usuario/migration.sql b/prisma/migrations/20241009025141_altera_nomenclatura_user_para_usuario/migration.sql new file mode 100644 index 0000000..b28b338 --- /dev/null +++ b/prisma/migrations/20241009025141_altera_nomenclatura_user_para_usuario/migration.sql @@ -0,0 +1,39 @@ +/* + Warnings: + + - You are about to drop the column `userId` on the `Aluno` table. All the data in the column will be lost. + - You are about to drop the `User` table. If the table is not empty, all the data it contains will be lost. + - A unique constraint covering the columns `[usuarioId]` on the table `Aluno` will be added. If there are existing duplicate values, this will fail. + - Added the required column `usuarioId` to the `Aluno` table without a default value. This is not possible if the table is not empty. + +*/ +-- DropForeignKey +ALTER TABLE "Aluno" DROP CONSTRAINT "Aluno_userId_fkey"; + +-- DropIndex +DROP INDEX "Aluno_userId_key"; + +-- AlterTable +ALTER TABLE "Aluno" DROP COLUMN "userId", +ADD COLUMN "usuarioId" INTEGER NOT NULL; + +-- DropTable +DROP TABLE "User"; + +-- CreateTable +CREATE TABLE "Usuario" ( + "id" SERIAL NOT NULL, + "nome" TEXT NOT NULL, + "token" TEXT, + "role" "Role" NOT NULL DEFAULT 'ALUNO', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Usuario_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Aluno_usuarioId_key" ON "Aluno"("usuarioId"); + +-- AddForeignKey +ALTER TABLE "Aluno" ADD CONSTRAINT "Aluno_usuarioId_fkey" FOREIGN KEY ("usuarioId") REFERENCES "Usuario"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20241009025515_adiciona_propriedade_default_para_a_propriedade_curso_do_aluno/migration.sql b/prisma/migrations/20241009025515_adiciona_propriedade_default_para_a_propriedade_curso_do_aluno/migration.sql new file mode 100644 index 0000000..01e39a3 --- /dev/null +++ b/prisma/migrations/20241009025515_adiciona_propriedade_default_para_a_propriedade_curso_do_aluno/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Aluno" ALTER COLUMN "curso" SET DEFAULT 'Universidade Federal de Campina Grande'; diff --git a/prisma/migrations/20241009042431_muda_tipo_da_propriedade_codigo_de_disciplina/migration.sql b/prisma/migrations/20241009042431_muda_tipo_da_propriedade_codigo_de_disciplina/migration.sql new file mode 100644 index 0000000..5ec7e93 --- /dev/null +++ b/prisma/migrations/20241009042431_muda_tipo_da_propriedade_codigo_de_disciplina/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Disciplina" ALTER COLUMN "codigo" SET DATA TYPE TEXT; diff --git a/prisma/migrations/20241009054755_ajusta_propriedades_do_aluno/migration.sql b/prisma/migrations/20241009054755_ajusta_propriedades_do_aluno/migration.sql new file mode 100644 index 0000000..85dcfac --- /dev/null +++ b/prisma/migrations/20241009054755_ajusta_propriedades_do_aluno/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "Aluno" ADD COLUMN "universidade" TEXT NOT NULL DEFAULT 'Universidade Federal de Campina Grande', +ALTER COLUMN "curso" SET DEFAULT 'Ciência da Computação'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9092824..806c25b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -8,140 +8,66 @@ datasource db { } enum Role { - ADMIN // Administrador do sistema - DISCENTE // Aluno de alguma instituição que usa o sistema - DOCENTE // Professor de alguma instituição que usa o sistema + ADMIN + ALUNO + DOCENTE } -enum DisciplinaStatus { - APENAS_MEDIA_APROVADO // A disciplina foi registrada como paga com êxito e aprovado no sistema - APENAS_MEDIA_REPROVADO // A disciplina foi registrada como nota insuficiente e reprovado no sistema - REPROVADO_POR_FALTA // A disciplina foi registrada como aluno reprovado por falta - TRANCADA // A disciplina foi registrada como trancada pelo aluno +enum SituacaoDisciplina { + APROVADO EM_PROGRESSO -} - -enum DisciplinaEmCursoStatus { - EM_PROGRESSO // A disciplina está em curso - TRANCADA // O aluno trancou a disciplina + TRANCADA + REPROVADO + DISPENSA + REPROVADO_POR_FALTA } model Universidade { id Int @id @default(autoincrement()) - sigla String // A sigla da universidade. Ex: 'UFCG' + sigla String // 'UFCG', 'UFPB', 'UFRJ' nome String cidade String estado String createdAt DateTime @default(now()) - cursos Curso[] } -model User { +model Usuario { id Int @id @default(autoincrement()) nome String - token String? - role Role @default(DISCENTE) + token String? + role Role @default(ALUNO) + aluno Aluno? createdAt DateTime @default(now()) - discente Discente? - docente Docente? + updatedAt DateTime @default(now()) } -model Discente { - id Int @id @default(autoincrement()) - nome String - matricula String @unique @db.VarChar(9) - cursoId Int - userId Int @unique - livroId Int? @unique - createdAt DateTime @default(now()) - user User @relation(fields: [userId], references: [id]) - curso Curso @relation(fields: [cursoId], references: [id]) - livros LivroEmprestadoBiblioteca? @relation(fields: [livroId], references: [id]) - disciplinasEmCurso DisciplinaEmCurso[] - historicos HistoricoAlunoDisciplina[] - Disciplina Disciplina[] -} - -model LivroEmprestadoBiblioteca { - id Int @id @default(autoincrement()) - nomeDoLivro String - dataQueFoiPego String - dataQuePrecisaSerEntregue String - dataQueAlunoQuerSerLembrado String - anotacoesSobreOLivro String? - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) - discente Discente? -} - - -model Docente { - id Int @id @default(autoincrement()) - nome String - cursoId Int - userId Int @unique - createdAt DateTime @default(now()) - user User @relation(fields: [userId], references: [id]) - curso Curso @relation(fields: [cursoId], references: [id]) - disciplinas Disciplina[] - disciplinasEmCurso DisciplinaEmCurso[] - historicos HistoricoAlunoDisciplina[] -} - -model Curso { - id Int @id @default(autoincrement()) - universidadeId Int - nome String - createdAt DateTime @default(now()) - universidade Universidade @relation(fields: [universidadeId], references: [id]) - discentes Discente[] - docentes Docente[] - disciplinas Disciplina[] +model Aluno { + id Int @id @default(autoincrement()) + usuarioId Int @unique + nome String + matricula String @unique @db.VarChar(9) + curso String @default("Ciência da Computação") + universidade String @default("Universidade Federal de Campina Grande") + usuario Usuario @relation(fields: [usuarioId], references: [id]) + historicoAcademico Disciplina[] + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) } model Disciplina { - id Int @id @default(autoincrement()) - codigo String? - nome String - creditos Int // Créditos são definidos por horas. Ex: "Cáculo I possui 4 horas semanais, portanto 4 créditos." - semestre String // Semestre em que a disciplina foi cursada + id Int @id @default(autoincrement()) + codigo String + nome String + creditos Int + cargaHoraria Int @default(0) + semestre String + situacao SituacaoDisciplina? @default(EM_PROGRESSO) quantidadeProvas Int quantidadeFaltas Int - mediaFinal Float? - docenteId Int? - discenteId Int? - status DisciplinaStatus - cursoId Int - createdAt DateTime @default(now()) - docente Docente? @relation(fields: [docenteId], references: [id]) - discente Discente? @relation(fields: [discenteId], references: [id]) - curso Curso @relation(fields: [cursoId], references: [id]) - historicos HistoricoAlunoDisciplina[] - disciplinasEmCurso DisciplinaEmCurso[] - horario String? -} - -model HistoricoAlunoDisciplina { - id Int @id @default(autoincrement()) - discenteId Int - docenteId Int? - disciplinaId Int - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) - discente Discente @relation(fields: [discenteId], references: [id]) - docente Docente? @relation(fields: [docenteId], references: [id]) - disciplina Disciplina @relation(fields: [disciplinaId], references: [id]) -} - -model DisciplinaEmCurso { - id Int @id @default(autoincrement()) - discenteId Int - docenteId Int? - disciplinaId Int - notaDoDiscente Float? - status DisciplinaEmCursoStatus - discente Discente @relation(fields: [discenteId], references: [id]) - docente Docente? @relation(fields: [docenteId], references: [id]) - disciplina Disciplina @relation(fields: [disciplinaId], references: [id]) + mediaFinal Float? + docente String @default("Desconhecido") + alunoId Int? + aluno Aluno? @relation(fields: [alunoId], references: [id]) + horario String? + createdAt DateTime @default(now()) } - diff --git a/src/controllers/auth-controller.ts b/src/controllers/auth-controller.ts deleted file mode 100644 index 0033688..0000000 --- a/src/controllers/auth-controller.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Request, Response } from "express" -import { AuthService } from "../services/auth-service" -import { ReportarErrorAoSistema } from "../exceptions/ReportarErroAoSistema" - -export class AuthController { - private authService: AuthService - - constructor(authService: AuthService) { - this.authService = authService - } - - async gerarToken(req: Request, res: Response) { - const { id, role } = req.body - - try { - const token = await this.authService.lidaComGeracaoDoToken({ id, role }) - - return res.status(201).json({ - message: "Token gerado com sucesso.", - data: token - }) - } catch (error) { - if (error instanceof ReportarErrorAoSistema) { - return res.status(400).json({ - error: error.message, - details: process.env.NODE_ENV === 'development' ? error.stack : "" - }) - } else { - res.status(500).json({ error: 'Internal Server Error' }) - } - } - } -} diff --git a/src/controllers/sistema-controller.ts b/src/controllers/sistema-controller.ts deleted file mode 100644 index f0db8ef..0000000 --- a/src/controllers/sistema-controller.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Request, Response } from "express"; -import { PayloadUserAlreadyExists } from "../middlewares/autorizacao-middleware"; -import { UserService } from "../services/user-service"; -import { DiscenteService } from "../services/discente-service"; -import { DisciplinaService } from "../services/disciplina-service"; -import { SessionDTO } from "../dtos/session-dto"; -import { SistemaService } from "../services/sistema-service"; - -export class SistemaController { - private userService: UserService - private discenteService: DiscenteService - private disciplinaService: DisciplinaService - private sistemaService: SistemaService - - constructor(userService: UserService, discenteService: DiscenteService, disciplinaService: DisciplinaService, sistemaService: SistemaService) { - this.userService = userService, - this.discenteService = discenteService - this.disciplinaService = disciplinaService - this.sistemaService = sistemaService - } - - public async criaConexaoComSistema(req: Request, res: Response): Promise { - interface CustomRequest extends Request { - user: PayloadUserAlreadyExists; - } - - const sessionDTO: SessionDTO = req.body; - - // // customReq agora compartilha o mesmo acesso de memória que req - // const customReq: CustomRequest = req as CustomRequest; - - // const { userAlreadyExists } = customReq.user; - - // if (userAlreadyExists) { - // this.retornaDadosDoDiscente() - // } - - this.configuraSistemaDoUsuario(sessionDTO) - - - res.status(200).json({ message: 'Conexão com o sistema criada com sucesso!' }) - } - - public atualizaSistema(): void { - // Implementar a atualização do sistema - } - - private retornaDadosDoDiscente(): void { - // Implementar a consulta dos dados do discente - } - - // Implementar a configuração do sistema do usuário - private async configuraSistemaDoUsuario(sessionDTO: SessionDTO) { - // Importa dados do usuário - // Cria usuário - const usuario = await this.userService.lidaComCriacaoDoUsuario({ nome: '', role: 'DISCENTE' }) - - if (!usuario) { - throw new Error('Erro ao cadastrar o usuário') - } - - const discente = await this.discenteService.lidaComCriacaoDoDiscente({ - nome: '', - matricula: sessionDTO.matricula, - cursoId: 1, // Adicionar o ID do curso - userId: usuario!.id, // Adicionar o ID do usuário - }); - - if (!discente) { - throw new Error('Erro ao cadastrar o discente') - } - - // Identifica a instituição que o aluno possui vínculo - const importer = this.sistemaService.lidaComIdentificacaoDaInstituicaoDeEnsino(sessionDTO.vinculo) - - // Importa disciplinas do usuário - this.disciplinaService.lidaComImportacaoDasDisciplinasDoDiscente({ ...sessionDTO, discenteId: discente.id }, importer) - - // Configura as permissões do usuário - - // Cria as rotas do sistema - - // Cria as rotas do sistema do usuário - - // Inicia as rotas do sistema - - // Inicia as rotas do sistema do usuário - - // Inicia a API - } - -} diff --git a/src/database/database-interface.ts b/src/database/database-interface.ts deleted file mode 100644 index 2783944..0000000 --- a/src/database/database-interface.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Discente, Disciplina, User } from "@prisma/client" -import { UserDTO } from "../dtos/user-dto" -import { DiscenteDTO } from "../dtos/student-dto" - -export interface IDatabase { - criaUsuario(data: UserDTO): Promise - buscaUsuarioPorId(id: number): Promise - buscaUsuarioPorEmail(email: string): Promise - atualizaUsuario(id: number, data: Partial): Promise - deletaUsuario(id: number): Promise - buscaPorUsuarios(): Promise - - criaDiscente(data: DiscenteDTO): Promise - atualizaDiscente(id: number, data: Partial): Promise - deletaDiscente(id: number): Promise - buscaDiscentePorId(id: number): Promise - buscaDiscentePorMatricula(matricula: string): Promise - buscaDiscentePorEmail(email: string): Promise - - autenticaUsuario(email: string, senha: string): Promise - adicionaTokenDeAutenticacao(id: number, token: string): Promise - - salvaDisciplinas(data: Disciplina[]): Promise -} diff --git a/src/database/prisma-database.ts b/src/database/prisma-database.ts deleted file mode 100644 index 7bb9f16..0000000 --- a/src/database/prisma-database.ts +++ /dev/null @@ -1,106 +0,0 @@ -// src/database/PrismaDatabase.tsimport { PrismaClient } from'@prisma/client' -import { Discente, Disciplina, PrismaClient, User } from '@prisma/client' -import { UserDTO } from '../dtos/user-dto' -import { IDatabase } from'./database-interface' -import { DiscenteDTO } from '../dtos/discente-dto' -import { PrismaClientInitializationError } from '@prisma/client/runtime/library' - -const prisma = new PrismaClient() - -export class PrismaDatabase implements IDatabase { - async salvaDisciplinas(data: Disciplina[]): Promise { - try { - const disciplinas = await prisma.disciplina.createMany({ data }) - - return disciplinas.count - } catch (error) { - if (error instanceof PrismaClientInitializationError) { - console.error('Erro ao inicializar o Prisma Client:', error.message) - } else { - console.error('Erro desconhecido ao salvar disciplinas:', error) - } - throw error - } - } - - async buscaPorUsuarios(): Promise { - return await prisma.user.findMany() - } - - async adicionaTokenDeAutenticacao(id: number, token: string): Promise { - await prisma.user.update({ - where: { - id, - }, - data: { - token - } - }) - } - buscaUsuarioPorEmail(email: string): Promise { - throw new Error('Method not implemented.') - } - atualizaDiscente(id: number, data: Partial): Promise { - throw new Error('Method not implemented.') - } - deletaDiscente(id: number): Promise { - throw new Error('Method not implemented.') - } - buscaDiscentePorId(id: number): Promise { - throw new Error('Method not implemented.') - } - buscaDiscentePorMatricula(matricula: string): Promise { - throw new Error('Method not implemented.') - } - buscaDiscentePorEmail(email: string): Promise { - throw new Error('Method not implemented.') - } - criaDiscente(data: DiscenteDTO): Promise { - console.log(data) - const discente = prisma.discente.create({ - data: { - nome: data.nome, - matricula: data.matricula, - user: { - connect: { - id: data.userId, - } - }, - curso: { - connect: { - id: data.cursoId, - } - } - }, - }) - - return discente - } - - async criaUsuario(data: UserDTO): Promise { - return prisma.user.create({ data: { - nome: data.nome, - role: data.role, - }}) - } - - async buscaUsuarioPorId(id: number): Promise { - return prisma.user.findUnique({ where: { id } }) - } - - async atualizaUsuario(id: number, data: Partial): Promise { - return prisma.user.update({ where: { id }, data }) - } - - async deletaUsuario(id: number): Promise { - await prisma.user.delete({ where: { id } }) - } - - async autenticaUsuario(email: string, password: string): Promise { - return null - } - - async validaToken(token: string): Promise { - return true - } -} diff --git a/src/dtos/discente-dto.ts b/src/dtos/discente-dto.ts deleted file mode 100644 index a7b4dc1..0000000 --- a/src/dtos/discente-dto.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type DiscenteDTO = { - nome: string - matricula: string - userId: number - cursoId: number -} diff --git a/src/dtos/student-dto.ts b/src/dtos/student-dto.ts deleted file mode 100644 index 8c8dea6..0000000 --- a/src/dtos/student-dto.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type DiscenteDTO = { - nome: string - matricula: string -} diff --git a/src/features/administrador/administrador-controller.ts b/src/features/administrador/administrador-controller.ts new file mode 100644 index 0000000..b5e8a0f --- /dev/null +++ b/src/features/administrador/administrador-controller.ts @@ -0,0 +1,9 @@ +import { AdministradorService } from "./administrador-service" + +export class AdministradorController { + private administadorService: AdministradorService + + constructor(administradorService: AdministradorService) { + this.administadorService = administradorService + } +} diff --git a/src/repositories/docente-repository.ts b/src/features/administrador/administrador-dto.ts similarity index 100% rename from src/repositories/docente-repository.ts rename to src/features/administrador/administrador-dto.ts diff --git a/src/features/administrador/administrador-repository.ts b/src/features/administrador/administrador-repository.ts new file mode 100644 index 0000000..b49ebbc --- /dev/null +++ b/src/features/administrador/administrador-repository.ts @@ -0,0 +1,3 @@ +export class AdministradorRepository { + constructor() { } +} diff --git a/src/features/administrador/administrador-route.ts b/src/features/administrador/administrador-route.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/features/administrador/administrador-service.ts b/src/features/administrador/administrador-service.ts new file mode 100644 index 0000000..6c2fe84 --- /dev/null +++ b/src/features/administrador/administrador-service.ts @@ -0,0 +1,3 @@ +export class AdministradorService { + constructor() { } +} diff --git a/src/controllers/discente-controller.ts b/src/features/alunos/aluno-controller.ts similarity index 52% rename from src/controllers/discente-controller.ts rename to src/features/alunos/aluno-controller.ts index 811e15a..f29d057 100644 --- a/src/controllers/discente-controller.ts +++ b/src/features/alunos/aluno-controller.ts @@ -1,20 +1,30 @@ +import { AlunoDTO } from './aluno-dto' import { Request, Response } from 'express' -import { DiscenteDTO } from '../dtos/discente-dto' -import { IDiscenteService } from '../services/discente-service' -import { ReportarErrorAoSistema } from '../exceptions/ReportarErroAoSistema' +import { IAlunoService } from './aluno-service' +import { IUsuarioService } from '../usuarios/usuario-service' +import { ReportarErrorAoSistema } from '../../shared/exceptions/ReportarErroAoSistema' -export class DiscenteController { - private discenteService: IDiscenteService +export class AlunoController { + private usuarioService: IUsuarioService + private alunoService: IAlunoService - constructor(discenteService: IDiscenteService) { - this.discenteService = discenteService + constructor(usuarioService: IUsuarioService, alunoService: IAlunoService) { + this.alunoService = alunoService + this.usuarioService = usuarioService } - public async criaDiscente(req: Request, res: Response): Promise { - const discente: DiscenteDTO = req.body + public async criaAluno(req: Request, res: Response): Promise { + const aluno: AlunoDTO = req.body try { - await this.discenteService.lidaComCriacaoDoDiscente(discente) + const alunoJaExiste = await this.alunoService.lidaComBuscaDoAlunoPorMatricula(aluno.matricula) + if (alunoJaExiste) { + res.status(400).json({ error: 'Aluno já cadastrado' }) + return + } + + const usuarioId = await this.usuarioService.lidaComCriacaoDoUsuario(aluno) + await this.alunoService.lidaComCriacaoDoAluno({ ...aluno, usuarioId }) res.status(201).json({ message: "Aluno criado com sucesso!" }) } catch (error) { if (error instanceof ReportarErrorAoSistema) { @@ -26,11 +36,11 @@ export class DiscenteController { } } - public async buscaDiscentePorId(req: Request, res: Response): Promise { - const discenteId = parseInt(req.params.id, 10) + public async buscaAlunoPorId(req: Request, res: Response): Promise { + const alunoId = parseInt(req.params.id, 10) try { - const student = await this.discenteService.lidaComBuscaDoDiscentePorId(discenteId) + const student = await this.alunoService.lidaComBuscaDoAlunoPorId(alunoId) if (student) { res.status(200).json(student) } else { @@ -46,11 +56,11 @@ export class DiscenteController { } } - public async buscaDiscentePorMatricula(req: Request, res: Response): Promise { + public async buscaAlunoPorMatricula(req: Request, res: Response): Promise { const matricula = req.body.matricula try { - const student = await this.discenteService.lidaComBuscaDoDiscentePorMatricula(matricula) + const student = await this.alunoService.lidaComBuscaDoAlunoPorMatricula(matricula) if (student) { res.status(200).json(student) } else { @@ -66,12 +76,12 @@ export class DiscenteController { } } - public async atualizaDiscente(req: Request, res: Response): Promise { - const discenteId = parseInt(req.params.id, 10) - const discente: DiscenteDTO = req.body + public async atualizaAluno(req: Request, res: Response): Promise { + const alunoId = parseInt(req.params.id, 10) + const aluno: AlunoDTO = req.body try { - const updatedStudent = await this.discenteService.lidaComAtualizacaoDoDiscente(discenteId, discente) + const updatedStudent = await this.alunoService.lidaComAtualizacaoDoAluno(alunoId, aluno) if (updatedStudent) { res.status(200).json(updatedStudent) } else { @@ -87,11 +97,11 @@ export class DiscenteController { } } - public async deletaDiscente(req: Request, res: Response): Promise { - const discenteId = parseInt(req.params.id, 10) + public async deletaAluno(req: Request, res: Response): Promise { + const alunoId = parseInt(req.params.id, 10) try { - await this.discenteService.lidaComRemocaoDoDiscente(discenteId) + await this.alunoService.lidaComRemocaoDoAluno(alunoId) res.status(204).send() } catch (error) { if (error instanceof ReportarErrorAoSistema) { diff --git a/src/features/alunos/aluno-dto.ts b/src/features/alunos/aluno-dto.ts new file mode 100644 index 0000000..1533457 --- /dev/null +++ b/src/features/alunos/aluno-dto.ts @@ -0,0 +1,5 @@ +export type AlunoDTO = { + nome: string + matricula: string + usuarioId: number +} diff --git a/src/features/alunos/aluno-repository.ts b/src/features/alunos/aluno-repository.ts new file mode 100644 index 0000000..b28c30c --- /dev/null +++ b/src/features/alunos/aluno-repository.ts @@ -0,0 +1,51 @@ +import { Aluno } from "@prisma/client" +import { AlunoDTO } from "./aluno-dto" +import { IDatabase } from "../../shared/database/database-interface" + +export interface IAlunoRepository { + salvaAluno(data: AlunoDTO): Promise + atualizaAluno(id: number, data: Partial): Promise + removeAluno(id: number): Promise + buscaAlunoPorId(id: number): Promise + buscaAlunoPorMatricula(matricula: string): Promise + buscaInformacoesDoAlunoPorId(id: number): Promise | null> +} + +export type AtualizaAlunoDTO = { + email?: string + name?: string + password?: string + role?: string +} + +export class AlunoRepository implements IAlunoRepository { + private orm: IDatabase + + constructor(orm: IDatabase) { + this.orm = orm + } + + async salvaAluno(data: AlunoDTO): Promise { + return this.orm.salvaAluno(data) + } + + async atualizaAluno(id: number, data: AlunoDTO): Promise { + return this.orm.atualizaAluno(id, data) + } + + async removeAluno(id: number): Promise { + await this.orm.removeAluno(id) + } + + async buscaAlunoPorId(id: number): Promise { + return this.orm.buscaAlunoPorId(id) + } + + async buscaAlunoPorMatricula(matricula: string): Promise { + return this.orm.buscaAlunoPorMatricula(matricula) + } + + async buscaInformacoesDoAlunoPorId(id: number): Promise | null> { + return this.orm.buscaInformacoesDoAlunoPorId(id) + } +} diff --git a/src/features/alunos/aluno-route.ts b/src/features/alunos/aluno-route.ts new file mode 100644 index 0000000..b6eae41 --- /dev/null +++ b/src/features/alunos/aluno-route.ts @@ -0,0 +1,25 @@ +import { AlunoRepository } from './aluno-repository' +import { Router } from 'express' +import { AlunoController } from './aluno-controller' +import { AlunoService } from './aluno-service' +import { PrismaDatabase } from '../../shared/database/prisma-database' +import { AutorizacaoMiddleware } from '../../shared/middlewares/autorizacao-middleware' +import { UsuarioRepository } from '../usuarios/usuario-repository' +import { UsuarioService } from '../usuarios/usuario-service' + +const router = Router() +const database = new PrismaDatabase(); + +const alunoRepository = new AlunoRepository(database) +const usuarioRepository = new UsuarioRepository(database) +const alunoService = new AlunoService(alunoRepository) +const usuarioService = new UsuarioService(usuarioRepository) +const alunoController = new AlunoController(usuarioService, alunoService) +const middlewareAutorizacao = new AutorizacaoMiddleware() + +router.get('/:id', middlewareAutorizacao.autorizarApenas('ADMIN'), alunoController.buscaAlunoPorId.bind(alunoController)) +router.post('/create', middlewareAutorizacao.autorizarApenas('ADMIN'), alunoController.criaAluno.bind(alunoController)) +router.post('/update', middlewareAutorizacao.autorizarApenas('ADMIN'), alunoController.atualizaAluno.bind(alunoController)) +router.delete('/delete', middlewareAutorizacao.autorizarApenas('ADMIN'), alunoController.deletaAluno.bind(alunoController)); + +export { router as alunoRoutes } diff --git a/src/features/alunos/aluno-service.ts b/src/features/alunos/aluno-service.ts new file mode 100644 index 0000000..0d6480e --- /dev/null +++ b/src/features/alunos/aluno-service.ts @@ -0,0 +1,101 @@ +import { IAlunoRepository } from './aluno-repository' +import { AlunoDTO } from './aluno-dto' +import { Aluno } from '@prisma/client' +import { ReportarErrorAoSistema } from '../../shared/exceptions/ReportarErroAoSistema' + +export interface IAlunoService { + lidaComCriacaoDoAluno(data: AlunoDTO): Promise + lidaComAtualizacaoDoAluno(id: number, data: Partial): Promise + lidaComRemocaoDoAluno(id: number): Promise + lidaComBuscaDoAlunoPorId(id: number): Promise + lidaComBuscaDoAlunoPorMatricula(matricula: string): Promise +} + +export class AlunoService implements IAlunoService { + private alunoRepository: IAlunoRepository + + constructor(alunoRepository: IAlunoRepository) { + this.alunoRepository = alunoRepository + } + + async lidaComCriacaoDoAluno(data: AlunoDTO) { + if (!data.nome || !data.matricula || !data.usuarioId) { + throw new ReportarErrorAoSistema('Dados insuficientes para criar aluno. Veja a documentação: Nome e matrícula são obrigatórios. Além disso, um usuário precisa ser criado para que seu id seja vinculado a entidade aluno.') + } + + try { + data.usuarioId = Number(data.usuarioId) + + const aluno = await this.alunoRepository.salvaAluno(data) + return aluno + } catch (error) { + console.error(error) + throw new Error('Error creating aluno') + } + } + + async lidaComBuscaDoAlunoPorId(id: number) { + if (!id) { + throw new ReportarErrorAoSistema('ID do usuário não informado.') + } + try { + const aluno = await this.alunoRepository.buscaAlunoPorId(id) + return aluno + } catch (error) { + throw new Error('Error fetching aluno') + } + } + + async lidaComBuscaDeInformacoesDoAlunoPorId(id: number) { + if (!id) { + throw new ReportarErrorAoSistema('ID do usuário não informado.') + } + + try { + const aluno = await this.alunoRepository.buscaInformacoesDoAlunoPorId(id) + return aluno + } catch (error) { + throw new Error('Error fetching aluno') + } + } + + async lidaComBuscaDoAlunoPorMatricula(matricula: string): Promise { + if (!matricula) { + throw new ReportarErrorAoSistema('Matricula do usuário não informada.') + } + try { + const aluno = await this.alunoRepository.buscaAlunoPorMatricula(matricula) + return aluno + } catch (error) { + throw new Error('Error fetching aluno') + } + } + + async lidaComAtualizacaoDoAluno(id: number, data: Partial) { + if (!id) { + throw new ReportarErrorAoSistema('ID do usuário não informado.') + } + + if (!data.nome || !data.matricula || !data.usuarioId) { + throw new ReportarErrorAoSistema('Dados insuficientes para criar aluno. Veja a documentação: Nome e matrícula são obrigatórios. Além disso, um usuário precisa ser criado para que seu id seja vinculado a entidade aluno.') + } + + try { + const alunoAtualizado = await this.alunoRepository.atualizaAluno(id, data) + return alunoAtualizado + } catch (error) { + throw new Error('Error updating aluno') + } + } + + async lidaComRemocaoDoAluno(id: number) { + if (!id) { + throw new ReportarErrorAoSistema('ID do usuário não informado.') + } + try { + await this.alunoRepository.removeAluno(id) + } catch (error) { + throw new Error('Error deleting aluno') + } + } +} diff --git a/src/features/autenticacao/autenticacao-controller.ts b/src/features/autenticacao/autenticacao-controller.ts new file mode 100644 index 0000000..b2ddb50 --- /dev/null +++ b/src/features/autenticacao/autenticacao-controller.ts @@ -0,0 +1,33 @@ +import { Request, Response } from "express" +import { AutenticacaoService } from "./autenticacao-service" +import { ReportarErrorAoSistema } from "../../shared/exceptions/ReportarErroAoSistema" + +export class AutenticacaoController { + private autenticacaoService: AutenticacaoService + + constructor(autenticacaoService: AutenticacaoService) { + this.autenticacaoService = autenticacaoService + } + + async gerarToken(req: Request, res: Response) { + const { id, role } = req.body + + try { + const token = await this.autenticacaoService.lidaComGeracaoDoToken({ id, role }) + + return res.status(201).json({ + message: "Token gerado com sucesso.", + data: token + }) + } catch (error) { + if (error instanceof ReportarErrorAoSistema) { + return res.status(400).json({ + error: error.message, + details: process.env.NODE_ENV === 'development' ? error.stack : "" + }) + } else { + res.status(500).json({ error: 'Internal Server Error' }) + } + } + } +} diff --git a/src/repositories/auth-repository.ts b/src/features/autenticacao/autenticacao-repository.ts similarity index 68% rename from src/repositories/auth-repository.ts rename to src/features/autenticacao/autenticacao-repository.ts index 9bd5aeb..b5e588e 100644 --- a/src/repositories/auth-repository.ts +++ b/src/features/autenticacao/autenticacao-repository.ts @@ -1,10 +1,10 @@ -import { IDatabase } from "../database/database-interface" +import { IDatabase } from "../../shared/database/database-interface" -export interface IAuthRepository { +export interface IAutenticacaoRepository { adicionaTokenDeAutenticacao(id: number, token: string): Promise } -export class AuthRepository implements IAuthRepository { +export class AutenticacaoRepository implements IAutenticacaoRepository { private orm: IDatabase constructor(orm: IDatabase) { @@ -19,6 +19,6 @@ export class AuthRepository implements IAuthRepository { // } async adicionaTokenDeAutenticacao(userId: number, token: string): Promise { - await this.orm.adicionaTokenDeAutenticacao(userId, token) + await this.orm.adicionaTokenDeAutenticacaoAoUsuario(userId, token) } } diff --git a/src/features/autenticacao/autenticacao-route.ts b/src/features/autenticacao/autenticacao-route.ts new file mode 100644 index 0000000..1d27099 --- /dev/null +++ b/src/features/autenticacao/autenticacao-route.ts @@ -0,0 +1,16 @@ +import { Router } from 'express' +import { PrismaDatabase } from '../../shared/database/prisma-database' +import { AutenticacaoController } from './autenticacao-controller' +import { AutenticacaoService } from './autenticacao-service' +import { AutenticacaoRepository } from './autenticacao-repository' + +const router = Router() +const database = new PrismaDatabase(); + +const autenticacaoRepository = new AutenticacaoRepository(database) +const autenticacaoService = new AutenticacaoService(autenticacaoRepository) +const autenticacaoController = new AutenticacaoController(autenticacaoService) + +router.post('/gera-token', autenticacaoController.gerarToken.bind(autenticacaoController)) + +export { router as autenticacaoRoutes } diff --git a/src/services/auth-service.ts b/src/features/autenticacao/autenticacao-service.ts similarity index 61% rename from src/services/auth-service.ts rename to src/features/autenticacao/autenticacao-service.ts index c4f5186..47cf9d0 100644 --- a/src/services/auth-service.ts +++ b/src/features/autenticacao/autenticacao-service.ts @@ -1,21 +1,21 @@ import { Role } from "@prisma/client"; -import { ReportarErrorAoSistema } from "../exceptions/ReportarErroAoSistema"; -import { IAuthRepository } from "../repositories/auth-repository" +import { ReportarErrorAoSistema } from "../../shared/exceptions/ReportarErroAoSistema"; +import { IAutenticacaoRepository } from "./autenticacao-repository" import jwt from 'jsonwebtoken' -export interface IAuthService { +export interface IAutenticacaoService { lidaComGeracaoDoToken(usuario: { id: number, role: string }): Promise } -export class AuthService implements IAuthService { - private authRepository: IAuthRepository +export class AutenticacaoService implements IAutenticacaoService { + private autenticacaoRepository: IAutenticacaoRepository private segredo: string; - constructor(authRepository: IAuthRepository) { - this.authRepository = authRepository + constructor(autenticacaoRepository: IAutenticacaoRepository) { + this.autenticacaoRepository = autenticacaoRepository this.segredo = process.env.JWT_SECRET || 'segredo_padrao' } - + public async lidaComGeracaoDoToken(usuario: { id: number, role: string }): Promise { const payload = { id: usuario.id, @@ -27,7 +27,7 @@ export class AuthService implements IAuthService { }); try { - await this.authRepository.adicionaTokenDeAutenticacao(usuario.id, token) + // await this.autenticacaoRepository.adicionaTokenDeAutenticacao(usuario.id, token) return token; } catch (err) { throw new ReportarErrorAoSistema("Erro ao tentar salvar token no banco de dados") @@ -37,7 +37,7 @@ export class AuthService implements IAuthService { public async lidaComGeracaoDoTokenDoDiscente(usuario: { id: number }): Promise { const payload = { id: usuario.id, - role: Role.DISCENTE, + role: Role.ALUNO, }; const token = jwt.sign(payload, this.segredo, { @@ -45,7 +45,7 @@ export class AuthService implements IAuthService { }); try { - await this.authRepository.adicionaTokenDeAutenticacao(usuario.id, token) + await this.autenticacaoRepository.adicionaTokenDeAutenticacao(usuario.id, token) return token; } catch (err) { throw new ReportarErrorAoSistema("Erro ao tentar salvar token no banco de dados", err) diff --git a/src/controllers/disciplina-controller.ts b/src/features/disciplinas/disciplina-controller.ts similarity index 92% rename from src/controllers/disciplina-controller.ts rename to src/features/disciplinas/disciplina-controller.ts index cc00f4e..9eeb0ed 100644 --- a/src/controllers/disciplina-controller.ts +++ b/src/features/disciplinas/disciplina-controller.ts @@ -1,7 +1,7 @@ // constructor(private userService: IUserService) {} import { Request, Response } from "express"; -import { IDisciplinaService } from "../services/disciplina-service"; +import { IDisciplinaService } from "./disciplina-service"; // async importUserData(req: Request, res: Response) { // const { login, senha, vinculo } = req.body @@ -16,7 +16,7 @@ import { IDisciplinaService } from "../services/disciplina-service"; export class DisciplinaController { private disciplinaService: IDisciplinaService - + constructor(disciplinaService: IDisciplinaService) { this.disciplinaService = disciplinaService } @@ -33,7 +33,7 @@ export class DisciplinaController { } - public async importaDisciplinas(req: Request, res: Response): Promise {} + public async importaDisciplinas(req: Request, res: Response): Promise { } } // toString() { diff --git a/src/features/disciplinas/disciplina-repository.ts b/src/features/disciplinas/disciplina-repository.ts new file mode 100644 index 0000000..06d516d --- /dev/null +++ b/src/features/disciplinas/disciplina-repository.ts @@ -0,0 +1,18 @@ +import { IDatabase } from "../../shared/database/database-interface"; +import { DisciplinaHistoricoProps } from "../../shared/importers/implementation/ufcg-importer"; + +export interface IDisciplinaRepository { + saveDisciplinas(disciplinas: DisciplinaHistoricoProps[]): Promise +} + +export class DisciplinaRepository implements IDisciplinaRepository { + private orm: IDatabase + + constructor(orm: IDatabase) { + this.orm = orm + } + + saveDisciplinas(disciplinas: DisciplinaHistoricoProps[]): Promise { + return this.orm.salvaVariasDisciplinas(disciplinas) + } +} diff --git a/src/routes/disciplina-route.ts b/src/features/disciplinas/disciplina-route.ts similarity index 68% rename from src/routes/disciplina-route.ts rename to src/features/disciplinas/disciplina-route.ts index 4b7f8e5..a96ed0d 100644 --- a/src/routes/disciplina-route.ts +++ b/src/features/disciplinas/disciplina-route.ts @@ -1,17 +1,17 @@ -import { DisciplinaRepository } from './../repositories/disciplina-repository' import { Router } from 'express' -import { DisciplinaService } from '../services/disciplina-service' -import { PrismaDatabase } from '../database/prisma-database' -import { DisciplinaController } from '../controllers/disciplina-controller' -import { AutorizacaoMiddleware } from '../middlewares/autorizacao-middleware' +import { DisciplinaService } from './disciplina-service' +import { DisciplinaController } from './disciplina-controller' +import { PrismaDatabase } from '../../shared/database/prisma-database' +import { AutorizacaoMiddleware } from '../../shared/middlewares/autorizacao-middleware' +import { DisciplinaRepository } from './../../features/disciplinas/disciplina-repository' const router = Router() const database = new PrismaDatabase(); +const middlewareAutorizacao = new AutorizacaoMiddleware() const disciplinaRepository = new DisciplinaRepository(database) const disciplinaService = new DisciplinaService(disciplinaRepository) const disciplinaController = new DisciplinaController(disciplinaService) -const middlewareAutorizacao = new AutorizacaoMiddleware() router.post('/create', middlewareAutorizacao.autorizarApenas('ADMIN'), disciplinaController.criaDisciplina.bind(disciplinaController)) router.post('/update', middlewareAutorizacao.autorizarApenas('ADMIN'), disciplinaController.atualizaDisciplina.bind(disciplinaController)) diff --git a/src/features/disciplinas/disciplina-service.ts b/src/features/disciplinas/disciplina-service.ts new file mode 100644 index 0000000..380d597 --- /dev/null +++ b/src/features/disciplinas/disciplina-service.ts @@ -0,0 +1,41 @@ +import { Disciplina } from "@prisma/client" +import { IDisciplinaRepository } from "../../features/disciplinas/disciplina-repository" +import { UFCGImporter } from "../../shared/importers/implementation/ufcg-importer" +import { Importer } from "../../shared/importers/importer" +import { SessaoDTO } from "../../features/sessoes/sessao-dto" + +export interface IDisciplinaService { + lidaComImportacaoDasDisciplinasDoDiscente(sessaoDTO: SessaoDTO, importer: Importer): Promise +} + +export class DisciplinaService implements IDisciplinaService { + private disciplinaRepository: IDisciplinaRepository + + constructor(disciplinaRepository: IDisciplinaRepository) { + this.disciplinaRepository = disciplinaRepository + } + + async lidaComImportacaoDasDisciplinasDoDiscente(sessaoDTO: SessaoDTO, importer: Importer): Promise { + const { matricula, senha, alunoId } = sessaoDTO + + if (!sessaoDTO.matricula || !sessaoDTO.senha) { + throw new Error("Login, senha são obrigatórios") + } + + + const disciplinasImportadas = await importer.obterDisciplinasDoHistoricoAcademico(alunoId!) + await importer.obterDetalhesDaDisciplina() + const countDisciplinasCriadas = await this.disciplinaRepository.saveDisciplinas(disciplinasImportadas) + + return countDisciplinasCriadas + } + + switchImporter(vinculo: string) { + switch (vinculo) { + case "UFCG": + return new UFCGImporter() + default: + throw new Error("Importer error") + } + } +} diff --git a/src/features/sessoes/sessao-controller.ts b/src/features/sessoes/sessao-controller.ts new file mode 100644 index 0000000..262d8ed --- /dev/null +++ b/src/features/sessoes/sessao-controller.ts @@ -0,0 +1,74 @@ +import { Request, Response } from "express"; +import { PayloadUsuarioAlreadyExists } from "../../shared/middlewares/autorizacao-middleware"; +import { UsuarioService } from "../../features/usuarios/usuario-service"; +import { AlunoService } from "../../features/alunos/aluno-service"; +import { DisciplinaService } from "../../features/disciplinas/disciplina-service"; +import { SessaoDTO } from "../../features/sessoes/sessao-dto"; +import { SessaoService } from "./sessao-service"; +import { Importer } from "../../shared/importers/importer"; +import { ReportarErrorAoSistema } from "../../shared/exceptions/ReportarErroAoSistema"; + +export class SessaoController { + private usuarioService: UsuarioService + private alunoService: AlunoService + private disciplinaService: DisciplinaService + private sessaoService: SessaoService + + constructor(usuarioService: UsuarioService, alunoService: AlunoService, disciplinaService: DisciplinaService, sessaoService: SessaoService) { + this.usuarioService = usuarioService, + this.alunoService = alunoService + this.disciplinaService = disciplinaService + this.sessaoService = sessaoService + } + + public async criaConexaoComSessao(req: Request, res: Response): Promise { + const sessaoDTO: Omit = req.body; + + try { + const aluno = await this.configuraSessaoDoUsuario(sessaoDTO) + res.status(200).json({ message: 'Conexão com o sistema criada com sucesso!', data: aluno }) + } catch (error) { + if (error instanceof ReportarErrorAoSistema) { + res.status(400).json({ error: error.message }) + } else { + console.error(error) + res.status(500).json({ error: 'Internal Server Error' }) + } + } + } + + private async configuraSessaoDoUsuario(sessaoDTO: Omit) { + const { matricula, senha, vinculo } = sessaoDTO + + const importer: Importer = this.sessaoService.lidaComIdentificacaoDaInstituicaoDeEnsino(vinculo) + await importer.autenticaAluno(matricula, senha) // Autentica usuário e salva cookie retornado da response + + const dadosAluno = { + nome: 'Huandrey', + } + + const alunoJaExiste = await this.alunoService.lidaComBuscaDoAlunoPorMatricula(matricula) + if (alunoJaExiste) { + return await this.alunoService.lidaComBuscaDeInformacoesDoAlunoPorId(alunoJaExiste.id) + } + + const usuarioId: number = await this.usuarioService.lidaComCriacaoDoUsuario({ nome: dadosAluno.nome }) + + if (!usuarioId) { + throw new ReportarErrorAoSistema('Erro ao cadastrar o usuário') + } + + const aluno = await this.alunoService.lidaComCriacaoDoAluno({ + nome: dadosAluno.nome, + matricula: matricula, + usuarioId, + }); + + if (!aluno) { + throw new ReportarErrorAoSistema('Erro ao cadastrar o aluno') + } + + await this.disciplinaService.lidaComImportacaoDasDisciplinasDoDiscente({ ...sessaoDTO, alunoId: aluno.id }, importer) + return await this.alunoService.lidaComBuscaDeInformacoesDoAlunoPorId(aluno.id) + } +} diff --git a/src/dtos/session-dto.ts b/src/features/sessoes/sessao-dto.ts similarity index 53% rename from src/dtos/session-dto.ts rename to src/features/sessoes/sessao-dto.ts index 4666cc5..fa095da 100644 --- a/src/dtos/session-dto.ts +++ b/src/features/sessoes/sessao-dto.ts @@ -1,6 +1,6 @@ -export type SessionDTO = { +export type SessaoDTO = { matricula: string senha: string vinculo: string - discenteId?: number + alunoId?: number } diff --git a/src/features/sessoes/sessao-route.ts b/src/features/sessoes/sessao-route.ts new file mode 100644 index 0000000..cc5f20f --- /dev/null +++ b/src/features/sessoes/sessao-route.ts @@ -0,0 +1,34 @@ +import { Router } from 'express' +import { PrismaDatabase } from '../../shared/database/prisma-database' +import { SessaoController } from './sessao-controller'; +import { AlunoService } from '../../features/alunos/aluno-service'; +import { AlunoRepository } from '../../features/alunos/aluno-repository'; +import { DisciplinaRepository } from '../../features/disciplinas/disciplina-repository'; +import { UsuarioRepository } from '../../features/usuarios/usuario-repository'; +import { DisciplinaService } from '../../features/disciplinas/disciplina-service'; +import { UsuarioService } from '../../features/usuarios/usuario-service'; +import { AutorizacaoMiddleware } from '../../shared/middlewares/autorizacao-middleware'; +import { SessaoService } from './sessao-service'; + +const router = Router() +const database = new PrismaDatabase(); + +const alunoRepository = new AlunoRepository(database) +const disciplinaRepository = new DisciplinaRepository(database) +const usuarioRepository = new UsuarioRepository(database) + +const usuarioService = new UsuarioService(usuarioRepository) +const alunoService = new AlunoService(alunoRepository) +const disciplinaService = new DisciplinaService(disciplinaRepository) +const sessaoService = new SessaoService() + +const sessaoController = new SessaoController( + usuarioService, + alunoService, + disciplinaService, + sessaoService, +) + +router.post('/iniciar-sessao', sessaoController.criaConexaoComSessao.bind(sessaoController)) + +export { router as sessaoRoutes } diff --git a/src/services/sistema-service.ts b/src/features/sessoes/sessao-service.ts similarity index 61% rename from src/services/sistema-service.ts rename to src/features/sessoes/sessao-service.ts index 9b1c085..ed8aba4 100644 --- a/src/services/sistema-service.ts +++ b/src/features/sessoes/sessao-service.ts @@ -1,10 +1,10 @@ -import { UFCGImporter } from "../importers/implementation/ufcg-importer" -import { Importer } from "../importers/importer" +import { UFCGImporter } from "../../shared/importers/implementation/ufcg-importer" +import { Importer } from "../../shared/importers/importer" -export interface ISistemaService { +export interface ISessaoService { lidaComIdentificacaoDaInstituicaoDeEnsino(vinculo: string): Importer } -export class SistemaService implements ISistemaService { +export class SessaoService implements ISessaoService { lidaComIdentificacaoDaInstituicaoDeEnsino(vinculo: string): Importer { return this.switchImporter(vinculo) } diff --git a/src/controllers/user-controller.ts b/src/features/usuarios/usuario-controller.ts similarity index 50% rename from src/controllers/user-controller.ts rename to src/features/usuarios/usuario-controller.ts index 6b3840f..0415606 100644 --- a/src/controllers/user-controller.ts +++ b/src/features/usuarios/usuario-controller.ts @@ -1,28 +1,28 @@ +import { UsuarioDTO } from './usuario-dto' import { Request, Response } from 'express' -import { UserDTO } from "../dtos/user-dto" -import { IUserService } from '../services/user-service' -import { ReportarErrorAoSistema } from '../exceptions/ReportarErroAoSistema' +import { IUsuarioService } from './usuario-service' +import { ReportarErrorAoSistema } from '../../shared/exceptions/ReportarErroAoSistema' export interface CriaUsuarioRequest extends Request { - body: UserDTO + body: UsuarioDTO } export interface AtualizaUsuarioRequest extends Request { - body: UserDTO + body: UsuarioDTO } -export class UserController { - private userService: IUserService +export class UsuarioController { + private usuarioService: IUsuarioService - constructor(userService: IUserService) { - this.userService = userService + constructor(usuarioService: IUsuarioService) { + this.usuarioService = usuarioService } public async criaUsuario(req: CriaUsuarioRequest, res: Response): Promise { try { const { nome, role } = req.body - const user = await this.userService.lidaComCriacaoDoUsuario({ nome, role }) - res.status(201).json(user) + const usuarioId = await this.usuarioService.lidaComCriacaoDoUsuario({ nome, role }) + res.status(201).json({ message: `Usuário com id ${usuarioId} criado com sucesso` }) } catch (error) { if (error instanceof ReportarErrorAoSistema) { res.status(400).json({ error: error.message }) @@ -35,47 +35,46 @@ export class UserController { public async atualizaUsuario(req: AtualizaUsuarioRequest, res: Response): Promise { try { - const userId = parseInt(req.params.id, 10) - console.log(userId) - const updates = req.body + const usuarioId: number = parseInt(req.params.id, 10) + const usuarioDTO: UsuarioDTO = req.body - const updatedUser = await this.userService.lidaComAtualizacaoDoUsuario(userId, updates) - - if (updatedUser) { - res.status(200).json(updatedUser) + const updatedUsuario = await this.usuarioService.lidaComAtualizacaoDoUsuario(usuarioId, usuarioDTO) + + if (updatedUsuario) { + res.status(200).json(updatedUsuario) } else { - res.status(404).json({ error: 'User not found' }) + res.status(404).json({ message: 'Usuario not found' }) } } catch (error) { if (error instanceof ReportarErrorAoSistema) { - res.status(400).json({ error: error.message }) + res.status(400).json({ message: error.message }) } else { console.error(error) - res.status(500).json({ error: 'Internal Server Error' }) + res.status(500).json({ message: 'Internal Server Error' }) } } } public async deletaUsuario(req: Request, res: Response): Promise { try { - const userId = parseInt(req.params.id, 10) - await this.userService.lidaComRemocaoDoUsuario(userId) + const usuarioId = parseInt(req.params.id, 10) + await this.usuarioService.lidaComRemocaoDoUsuario(usuarioId) res.status(204).send() // No Content } catch (error) { if (error instanceof ReportarErrorAoSistema) { - res.status(400).json({ error: error.message }) + res.status(400).json({ message: error.message }) } else { console.error(error) - res.status(500).json({ error: 'Internal Server Error' }) + res.status(500).json({ message: 'Internal Server Error' }) } } } public async buscaUsuario(req: Request, res: Response): Promise { try { - const userId = parseInt(req.params.id, 10) - const usuarioEncontrado = await this.userService.lidaComBuscaDoUsuarioPorId(userId) - + const usuarioId = parseInt(req.params.id, 10) + const usuarioEncontrado = await this.usuarioService.lidaComBuscaDoUsuarioPorId(usuarioId) + if (!usuarioEncontrado) { res.status(200).json({ message: 'Usuário não encontrado', @@ -86,28 +85,28 @@ export class UserController { res.status(200).json(usuarioEncontrado) } catch (error) { if (error instanceof ReportarErrorAoSistema) { - res.status(400).json({ error: error.message }) + res.status(400).json({ message: error.message }) } else { console.error(error) - res.status(500).json({ error: 'Internal Server Error' }) + res.status(500).json({ message: 'Internal Server Error' }) } } } public async buscaUsuarios(req: Request, res: Response): Promise { try { - const usuariosEncontrados = await this.userService.lidaComBuscaDosUsuarios() - + const usuariosEncontrados = await this.usuarioService.lidaComBuscaDeTodosUsuariosDoSistema() + res.status(200).json({ message: "Usuários encontrados", data: usuariosEncontrados }) } catch (error) { if (error instanceof ReportarErrorAoSistema) { - res.status(400).json({ error: error.message }) + res.status(400).json({ message: error.message }) } else { console.error(error) - res.status(500).json({ error: 'Internal Server Error' }) + res.status(500).json({ message: 'Internal Server Error' }) } } } diff --git a/src/dtos/user-dto.ts b/src/features/usuarios/usuario-dto.ts similarity index 57% rename from src/dtos/user-dto.ts rename to src/features/usuarios/usuario-dto.ts index 61d8e75..2734a92 100644 --- a/src/dtos/user-dto.ts +++ b/src/features/usuarios/usuario-dto.ts @@ -1,6 +1,6 @@ import { Role } from "@prisma/client" -export type UserDTO = { +export type UsuarioDTO = { nome: string - role: Role + role?: Role } diff --git a/src/features/usuarios/usuario-repository.ts b/src/features/usuarios/usuario-repository.ts new file mode 100644 index 0000000..33f1b35 --- /dev/null +++ b/src/features/usuarios/usuario-repository.ts @@ -0,0 +1,40 @@ +import { Usuario } from "@prisma/client" +import { IDatabase } from "../../shared/database/database-interface" +import { UsuarioDTO } from "./usuario-dto" + +export interface IUsuarioRepository { + criaUsuario(usuarioDTO: UsuarioDTO): Promise + atualizaUsuario(id: number, data: Partial): Promise + removeUsuario(id: number): Promise + buscaUsuarioPorId(id: number): Promise + buscaPorTodosUsuarios(): Promise +} + +export class UsuarioRepository implements IUsuarioRepository { + private orm: IDatabase + + constructor(orm: IDatabase) { + this.orm = orm + } + + async criaUsuario(data: UsuarioDTO): Promise { + const usuario = await this.orm.salvaUsuario(data) + return usuario.id + } + + async atualizaUsuario(id: number, data: UsuarioDTO): Promise { + return this.orm.atualizaUsuario(id, data) + } + + buscaUsuarioPorId(id: number): Promise { + return this.orm.buscaUsuarioPorId(id) + } + + async buscaPorTodosUsuarios(): Promise { + return this.orm.buscaPorTodosUsuarios() + } + + async removeUsuario(id: number): Promise { + await this.orm.removeUsuario(id) + } +} diff --git a/src/features/usuarios/usuario-route.ts b/src/features/usuarios/usuario-route.ts new file mode 100644 index 0000000..5942bdc --- /dev/null +++ b/src/features/usuarios/usuario-route.ts @@ -0,0 +1,22 @@ +import { Router } from 'express' +import { UsuarioService } from './usuario-service' +import { UsuarioRepository } from './usuario-repository' +import { UsuarioController } from './usuario-controller' +import { PrismaDatabase } from '../../shared/database/prisma-database' +import { AutorizacaoMiddleware } from '../../shared/middlewares/autorizacao-middleware' + +const router = Router() +const database = new PrismaDatabase(); + +const usuarioRepository = new UsuarioRepository(database) +const middlewareAutorizacao = new AutorizacaoMiddleware() +const usuarioService = new UsuarioService(usuarioRepository) +const usuarioController = new UsuarioController(usuarioService) + +router.get('', middlewareAutorizacao.autorizarApenas('ADMIN'), usuarioController.buscaUsuarios.bind(usuarioController)) +router.get('/:id', middlewareAutorizacao.autorizarApenas('ADMIN'), usuarioController.buscaUsuario.bind(usuarioController)) +router.post('/create', usuarioController.criaUsuario.bind(usuarioController)) +router.put('/update/:id', middlewareAutorizacao.autorizarApenas('ADMIN'), usuarioController.atualizaUsuario.bind(usuarioController)) +router.delete('/delete/:id', middlewareAutorizacao.autorizarApenas('ADMIN'), usuarioController.deletaUsuario.bind(usuarioController)) + +export { router as usuarioRoutes } diff --git a/src/features/usuarios/usuario-service.ts b/src/features/usuarios/usuario-service.ts new file mode 100644 index 0000000..26dd71c --- /dev/null +++ b/src/features/usuarios/usuario-service.ts @@ -0,0 +1,76 @@ +import { IUsuarioRepository } from './usuario-repository' +import { UsuarioDTO } from './usuario-dto' +import { Role, Usuario } from '@prisma/client' +import { ReportarErrorAoSistema } from '../../shared/exceptions/ReportarErroAoSistema' + +export interface IUsuarioService { + lidaComCriacaoDoUsuario(usuarioDTO: UsuarioDTO): Promise + lidaComBuscaDoUsuarioPorId(id: number): Promise + lidaComAtualizacaoDoUsuario(id: number, data: Partial): Promise + lidaComRemocaoDoUsuario(id: number): Promise + lidaComBuscaDeTodosUsuariosDoSistema(): Promise +} +export class UsuarioService implements IUsuarioService { + private usuarioRepository: IUsuarioRepository + + constructor(usuarioRepository: IUsuarioRepository) { + this.usuarioRepository = usuarioRepository + } + + async lidaComCriacaoDoUsuario(usuarioDTO: UsuarioDTO): Promise { + const { nome, role = Role.ALUNO } = usuarioDTO + + try { + if (!nome || typeof nome !== 'string') { + throw new ReportarErrorAoSistema('Nome é obrigatório e precisa ser uma string.') + } + + const usuarioId = await this.usuarioRepository.criaUsuario({ nome, role }) + return usuarioId + } catch (error) { + throw new ReportarErrorAoSistema('Erro ao tentar salvar usuário ao banco de dados.') + } + } + + async lidaComBuscaDoUsuarioPorId(id: number) { + try { + const usuario = await this.usuarioRepository.buscaUsuarioPorId(id) + if (!usuario) { + throw new Error('Usuario not found') + } + return usuario + } catch (error) { + throw new Error('Erro ao buscar usuário') + } + } + async lidaComAtualizacaoDoUsuario(id: number, data: Partial) { + if (!data.nome) { + throw new ReportarErrorAoSistema('Não foi informado nenhum parâmetro. Veja a documentação.') + } + + try { + const updatedUsuario = await this.usuarioRepository.atualizaUsuario(id, data) + return updatedUsuario + } catch (error) { + throw new Error('Erro ao atualizar o usuário') + } + } + + async lidaComRemocaoDoUsuario(id: number) { + await this.usuarioRepository.removeUsuario(id) + } + + async lidaComBuscaDeTodosUsuariosDoSistema() { + try { + const usuarios = await this.usuarioRepository.buscaPorTodosUsuarios() + + if (!usuarios) { + return [] + } + + return usuarios + } catch (error) { + throw new Error('Error fetching usuario') + } + } +} diff --git a/src/importers/implementation/ufcg-importer.ts b/src/importers/implementation/ufcg-importer.ts deleted file mode 100644 index c9044ec..0000000 --- a/src/importers/implementation/ufcg-importer.ts +++ /dev/null @@ -1,222 +0,0 @@ -import axios from 'axios' -import * as cheerio from 'cheerio' // Importação correta -import he from 'he' -import { Importer } from '../importer' -import { Disciplina, DisciplinaStatus } from '@prisma/client' -import iconv from 'iconv-lite'; - -export class UFCGImporter implements Importer { - private URL_BASE = "https://pre.ufcg.edu.br:8443/ControleAcademicoOnline/Controlador" - private URL_LOGIN = this.URL_BASE - private LOGIN = "login" - private SENHA = "senha" - private COMMAND = "command" - private ALUNO_LOGIN = "AlunoLogin" - - private REP_FALTA = "Reprovado por Falta" - private TRANCADO = "Trancado" - private DISPENSA = "Dispensa" - private APROVADO = "Aprovado" - private REPROVADO = "Reprovado" - private ERRO_AUTENTICACAO = "ERRO NA AUTENTICAÇÃO" - public static readonly ERRO = "Matrícula inválida ou senha incorreta." - - reportProgress(message: string): void { - console.log(message) // Implement your progress reporting logic here - } - - private cleanText = (text: string): string => { - return he.decode(text) // Decodifica entidades HTML - .replace(/\s+/g, ' ') - .trim() // Remove espaços em branco no início e no final - } - - public async importaDisciplinas(discenteId: number, matricula: string, senha: string): Promise { - const disciplinas: any = [] - const codigos: string[] = [] - this.reportProgress("Tentando fazer login...") - - try { - // Se autentica e armazena o cookie - const response = await axios.post(this.URL_LOGIN, new URLSearchParams({ - [this.LOGIN]: matricula, - [this.SENHA]: senha, - [this.COMMAND]: this.ALUNO_LOGIN - }), { - timeout: 10000, - withCredentials: true, - validateStatus: () => true, - httpsAgent: new (require('https').Agent)({ - rejectUnauthorized: false - }), - responseType: 'arraybuffer', // Receber os dados como array buffer - responseEncoding: 'binary', - }) - - const cookies: string[] = response.headers['set-cookie']! - let html = iconv.decode(Buffer.from(response.data), 'ISO-8859-1'); - - // Substituir por - html = html.replace(/]*charset=["']?[^"'>]+["']?[^>]*>/i, ''); - - const $ = cheerio.load(html) // Carregando o HTML - - if ($('head').text().includes(this.ERRO_AUTENTICACAO) || $('body').text().includes(UFCGImporter.ERRO)) { - throw new Error("Authentication Failed") - } - - console.log(response.data) - - // Abre a página do Histórico e pega o texto das disciplinas - this.reportProgress("Obtendo histórico...") - const historicoResponse = await axios.get(this.URL_LOGIN, { - params: { [this.COMMAND]: "AlunoHistorico" }, - headers: { Cookie: cookies.join(' ') }, - timeout: 10000, - httpsAgent: new (require('https').Agent)({ - rejectUnauthorized: false - }), - responseType: 'arraybuffer', // Receber os dados como array buffer - responseEncoding: 'binary', - }) - - let htmlHistoricoResponse = iconv.decode(Buffer.from(historicoResponse.data), 'ISO-8859-1'); - htmlHistoricoResponse = htmlHistoricoResponse.replace(/]*charset=["']?[^"'>]+["']?[^>]*>/i, ''); - console.log(htmlHistoricoResponse) - const historico$ = cheerio.load(htmlHistoricoResponse) - - const trsDisciplinas: any = historico$("div[id=disciplinas] > table > tbody > tr").toArray() - - for (const el of trsDisciplinas) { - const tds = historico$(el).find("td") - const disciplina = { - codigo: tds.eq(0).text() || '', - nome: this.cleanText(tds.eq(1).text()) || '', - creditos: parseInt(tds.eq(3).text() || '0', 10), - quantidadeProvas: this.calcularQtdeNotas(parseInt(tds.eq(3).text() || '0', 10)), - quantidadeFaltas: this.calcularQtdeFaltas(parseInt(tds.eq(3).text() || '0', 10)), - semestre: tds.eq(7).text() || '', - mediaFinal: isNaN(parseFloat(tds.eq(5).text().replace(",", "."))) ? null : parseFloat(tds.eq(5).text().replace(",", ".")), - status: '', - cursoId: 1, - discenteId, - } - - switch (tds.eq(6).text()) { - case this.APROVADO: - case this.DISPENSA: - disciplina.status = DisciplinaStatus.APENAS_MEDIA_APROVADO - if (disciplina.mediaFinal === null) { - disciplina.mediaFinal = 7.0 - } - break - case this.REPROVADO: - disciplina.status = DisciplinaStatus.APENAS_MEDIA_REPROVADO - break - case this.REP_FALTA: - disciplina.status = DisciplinaStatus.REPROVADO_POR_FALTA - disciplina.mediaFinal = 0 - break - case this.TRANCADO: - disciplina.status = DisciplinaStatus.TRANCADA - default: - disciplina.status = DisciplinaStatus.EM_PROGRESSO - break - } - - disciplinas.push(disciplina) - codigos.push(tds.eq(0).text() || '') - } - - if (disciplinas.length === 0) { - throw new Error("Você não possui histórico acadêmico") - } - - this.reportProgress("Obtendo detalhes das disciplinas...") - const horarioResponse = await axios.get(this.URL_BASE, { - params: { [this.COMMAND]: "AlunoTurmasListar" }, - headers: { Cookie: cookies.join(' ') }, - httpsAgent: new (require('https').Agent)({ - rejectUnauthorized: false - }), - responseType: 'arraybuffer', // Receber os dados como array buffer - responseEncoding: 'binary', - }) - - let htmlHorarioResponse = iconv.decode(Buffer.from(horarioResponse.data), 'ISO-8859-1'); - htmlHorarioResponse = htmlHorarioResponse.replace(/]*charset=["']?[^"'>]+["']?[^>]*>/i, ''); - - const horario$ = cheerio.load(htmlHorarioResponse) - const table = horario$("table") - - if (table) { - const trs = table.find("tr") - trs.each((index, element) => { - if (index === 0) return // Skip header row - - const tds = horario$(element).children() - const disciplina = this.findDisciplina(disciplinas, codigos, tds.eq(1).text() || '') - - if (disciplina) { - disciplina.horario = `Turma: ${tds.eq(3).text()}\nHorário: ${tds.eq(4).text()}` - } - }) - } - - } catch (error: any) { - if (axios.isAxiosError(error)) { - if (error.code === 'ECONNABORTED') { - throw new Error("Não foi possível buscar os dados. Tente novamente.") - } else if (error.message.includes('ENOTFOUND')) { - throw new Error("Não foi possível acessar o site da Universidade. Você está conectado a internet?") - } else if (error.message.includes('UNABLE_TO_VERIFY_LEAF_SIGNATURE')) { - throw new Error("Não foi possível acessar o site. Tente novamente") - } - } - - throw new Error(error.message) - } - - return disciplinas - } - - private findDisciplina(disciplinas: Disciplina[], codigos: string[], codigo: string): Disciplina | undefined { - const disciplinasFind = disciplinas.filter((disciplina, index) => codigos[index] === codigo) - - return disciplinasFind.reduce((latest, disciplina) => { - return (!latest || disciplina.semestre > latest.semestre) ? disciplina : latest - }, undefined as Disciplina | undefined) - } - - private calcularQtdeNotas(creditos: number): number { - switch (creditos) { - case 1: - case 2: - case 3: - return 2 - case 4: - return 3 - case 5: - case 6: - return 4 - default: - return 1 - } - } - - private calcularQtdeFaltas(creditos: number): number { - switch (creditos) { - case 1: - case 2: - case 3: - return 4 - case 4: - return 5 - case 5: - case 6: - return 6 - default: - return 3 - } - } -} diff --git a/src/importers/importer.ts b/src/importers/importer.ts deleted file mode 100644 index ae7532a..0000000 --- a/src/importers/importer.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Disciplina } from "@prisma/client" - -export interface Importer { - importaDisciplinas(discenteId: number, matricula: string, senha: string): Promise - reportProgress(msg: string): void -} diff --git a/src/repositories/discente-repository.ts b/src/repositories/discente-repository.ts deleted file mode 100644 index 667d077..0000000 --- a/src/repositories/discente-repository.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Discente } from "@prisma/client" -import { DiscenteDTO } from "../dtos/discente-dto" -import { IDatabase } from "../database/database-interface" - -export interface IDiscenteRepository { - criaDiscente(data: DiscenteDTO): Promise - atualizaDiscente(id: number, data: Partial): Promise - deletaDiscente(id: number): Promise - buscaDiscentePorId(id: number): Promise - buscaDiscentePorEmail(email: string): Promise - buscaDiscentePorMatricula(matricula: string): Promise -} - -export type AtualizaDiscenteDTO = { - email?: string - name?: string - password?: string - role?: string -} - -export class DiscenteRepository implements IDiscenteRepository { - private orm: IDatabase - - constructor(orm: IDatabase) { - this.orm = orm - } - - async criaDiscente(data: Discente): Promise { - return this.orm.criaDiscente(data) - } - - async atualizaDiscente(id: number, data: DiscenteDTO): Promise { - return this.orm.atualizaDiscente(id, data) - } - - async deletaDiscente(id: number): Promise { - await this.orm.deletaDiscente(id) - } - - async buscaDiscentePorId(id: number): Promise { - return this.orm.buscaDiscentePorId(id) - } - - async buscaDiscentePorMatricula(matricula: string): Promise { - return this.orm.buscaDiscentePorMatricula(matricula) - } - - async buscaDiscentePorEmail(email: string): Promise { - return this.orm.buscaDiscentePorEmail(email) - } -} diff --git a/src/repositories/disciplina-repository.ts b/src/repositories/disciplina-repository.ts deleted file mode 100644 index 2f83d50..0000000 --- a/src/repositories/disciplina-repository.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Disciplina } from "@prisma/client"; -import { IDatabase } from "../database/database-interface"; - -export interface IDisciplinaRepository { - saveDisciplinas(disciplinas: Disciplina[]): Promise -} - -export class DisciplinaRepository implements IDisciplinaRepository { - private orm: IDatabase - - constructor(orm: IDatabase) { - this.orm = orm - } - - saveDisciplinas(disciplinas: Disciplina[]): Promise { - return this.orm.salvaDisciplinas(disciplinas) - } -} diff --git a/src/repositories/user-repository.ts b/src/repositories/user-repository.ts deleted file mode 100644 index 7fe3b5f..0000000 --- a/src/repositories/user-repository.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { User } from "@prisma/client" -import { IDatabase } from "../database/database-interface" -import { UserDTO } from "../dtos/user-dto" - -export interface IUserRepository { - criaUsuario(data: UserDTO): Promise - atualizaUsuario(id: number, data: Partial): Promise - deletaUsuario(id: number): Promise - buscaUsuarioPorId(id: number): Promise - buscaUsuarioPorEmail(email: string): Promise - buscaUsuarios(): Promise -} - -export type UpdateUserDTO = { - email?: string - name?: string - password?: string - role?: string -} - -export class UserRepository implements IUserRepository { - private orm: IDatabase - - constructor(orm: IDatabase) { - this.orm = orm - } - buscaUsuarioPorId(id: number): Promise { - throw new Error("Method not implemented.") - } - buscaUsuarioPorEmail(email: string): Promise { - throw new Error("Method not implemented.") - } - async criaUsuario(data: UserDTO): Promise { - const user = await this.orm.criaUsuario(data) - return { - id: user.id, - token: '', - nome: user.nome, - role: user.role, - createdAt: user.createdAt - } - } - - async atualizaUsuario(id: number, data: UserDTO): Promise { - return this.orm.atualizaUsuario(id, data) - } - - async deletaUsuario(id: number): Promise { - await this.orm.deletaUsuario(id) - } - - async findUserById(id: number): Promise { - return this.orm.buscaUsuarioPorId(id) - } - - async findUserByEmail(email: string): Promise { - return this.orm.buscaUsuarioPorEmail(email) - } - - async buscaUsuarios(): Promise { - return this.orm.buscaPorUsuarios() - } -} diff --git a/src/routes/auth-route.ts b/src/routes/auth-route.ts deleted file mode 100644 index b305c18..0000000 --- a/src/routes/auth-route.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Router } from 'express' -import { PrismaDatabase } from '../database/prisma-database' -import { AuthController } from '../controllers/auth-controller' -import { AuthService } from '../services/auth-service' -import { AuthRepository } from '../repositories/auth-repository' - -const router = Router() -const database = new PrismaDatabase(); - -const authRepository = new AuthRepository(database) -const authService = new AuthService(authRepository) -const authController = new AuthController(authService) - -router.post('/token/generate', authController.gerarToken.bind(authController)) - -export { router as authRoutes } diff --git a/src/routes/discente-route.ts b/src/routes/discente-route.ts deleted file mode 100644 index b5d96f4..0000000 --- a/src/routes/discente-route.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { DiscenteRepository } from './../repositories/discente-repository' -import { Router } from 'express' -import { DiscenteController } from '../controllers/discente-controller' -import { DiscenteService } from '../services/discente-service' -import { PrismaDatabase } from '../database/prisma-database' -import { AutorizacaoMiddleware } from '../middlewares/autorizacao-middleware' - -const router = Router() -const database = new PrismaDatabase(); - -const discenteRepository = new DiscenteRepository(database) -const discenteService = new DiscenteService(discenteRepository) -const discenteController = new DiscenteController(discenteService) -const middlewareAutorizacao = new AutorizacaoMiddleware() - -router.post('/create', middlewareAutorizacao.autorizarApenas('ADMIN'), discenteController.criaDiscente.bind(discenteController)) -router.post('/update', middlewareAutorizacao.autorizarApenas('ADMIN'), discenteController.atualizaDiscente.bind(discenteController)) -router.delete('/delete', middlewareAutorizacao.autorizarApenas('ADMIN'), discenteController.deletaDiscente.bind(discenteController)); - -export { router as discenteRoutes } diff --git a/src/routes/sistema-route.ts b/src/routes/sistema-route.ts deleted file mode 100644 index 6de0b29..0000000 --- a/src/routes/sistema-route.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Router } from 'express' -import { PrismaDatabase } from '../database/prisma-database' -import { SistemaController } from '../controllers/sistema-controller'; -import { DiscenteService } from '../services/discente-service'; -import { DiscenteRepository } from '../repositories/discente-repository'; -import { DisciplinaRepository } from '../repositories/disciplina-repository'; -import { UserRepository } from '../repositories/user-repository'; -import { DisciplinaService } from '../services/disciplina-service'; -import { UserService } from '../services/user-service'; -import { AutorizacaoMiddleware } from '../middlewares/autorizacao-middleware'; -import { SistemaService } from '../services/sistema-service'; - -const router = Router() -const database = new PrismaDatabase(); - -const discenteRepository = new DiscenteRepository(database) -const disciplinaRepository = new DisciplinaRepository(database) -const userRepository = new UserRepository(database) - -const userService = new UserService(userRepository) -const discenteService = new DiscenteService(discenteRepository) -const disciplinaService = new DisciplinaService(disciplinaRepository) -const sistemaService = new SistemaService() - -const sistemaController = new SistemaController( - userService, - discenteService, - disciplinaService, - sistemaService, -) - -router.post('/session', sistemaController.criaConexaoComSistema.bind(sistemaController)) - -export { router as sistemaRoutes } diff --git a/src/routes/user-route.ts b/src/routes/user-route.ts deleted file mode 100644 index 37eda73..0000000 --- a/src/routes/user-route.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { UserRepository } from './../repositories/user-repository' -import { Router } from 'express' -import { UserController } from '../controllers/user-controller' -import { UserService } from '../services/user-service' -import { PrismaDatabase } from '../database/prisma-database' -import { AutorizacaoMiddleware } from '../middlewares/autorizacao-middleware' - -const router = Router() -const database = new PrismaDatabase(); - -const userRepository = new UserRepository(database) -const userService = new UserService(userRepository) -const userController = new UserController(userService) -const middlewareAutorizacao = new AutorizacaoMiddleware() - -router.get('', userController.buscaUsuarios.bind(userController)) -router.post('/create', userController.criaUsuario.bind(userController)) -router.put('/update/:id', middlewareAutorizacao.autorizarApenas('ADMIN'), userController.atualizaUsuario.bind(userController)) -router.delete('/delete/:id', middlewareAutorizacao.autorizarApenas('ADMIN'), userController.deletaUsuario.bind(userController)); - -export { router as userRoutes } diff --git a/src/server.ts b/src/server.ts index fc1965c..3cf2062 100644 --- a/src/server.ts +++ b/src/server.ts @@ -2,22 +2,22 @@ import express, { Request, Response } from 'express' import path from 'path' import cors from 'cors' import { PrismaClient } from '@prisma/client' -import { userRoutes } from './routes/user-route' -import { disciplinaRoutes } from './routes/disciplina-route' -import { discenteRoutes } from './routes/discente-route' -import { authRoutes } from './routes/auth-route' -import { sistemaRoutes } from './routes/sistema-route' +import { autenticacaoRoutes } from './features/autenticacao/autenticacao-route' +import { usuarioRoutes } from './features/usuarios/usuario-route' +import { alunoRoutes } from './features/alunos/aluno-route' +import { disciplinaRoutes } from './features/disciplinas/disciplina-route' +import { sessaoRoutes } from './features/sessoes/sessao-route' const prisma = new PrismaClient() const app = express() -const port = 3000 +const port = 7777 app.use(cors({ origin: true })) app.use(express.json()) app.use(express.static(path.join(__dirname, 'public'))) -async function main() {} +async function main() { } main() .then(async () => { @@ -29,11 +29,11 @@ main() process.exit(1) }) -app.use('/api/auth', authRoutes) -app.use('/api/users', userRoutes) -app.use('/api/discentes', discenteRoutes) +app.use('/api/auth', autenticacaoRoutes) +app.use('/api/users', usuarioRoutes) +app.use('/api/discentes', alunoRoutes) app.use('/api/disciplinas', disciplinaRoutes) -app.use('/api', sistemaRoutes) +app.use('/api', sessaoRoutes) app.get('/', (req: Request, res: Response) => { res.send('Bem-vindo a API do Controle Acadêmico!') diff --git a/src/services/discente-service.ts b/src/services/discente-service.ts deleted file mode 100644 index 6740658..0000000 --- a/src/services/discente-service.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { IDiscenteRepository } from '../repositories/discente-repository' -import { DiscenteDTO } from '../dtos/discente-dto' -import { Discente } from '@prisma/client' -import { ReportarErrorAoSistema } from '../exceptions/ReportarErroAoSistema' - -export interface IDiscenteService { - lidaComCriacaoDoDiscente(data: DiscenteDTO): Promise - lidaComAtualizacaoDoDiscente(id: number, data: Partial): Promise - lidaComRemocaoDoDiscente(id: number): Promise - lidaComBuscaDoDiscentePorId(id: number): Promise - lidaComBuscaDoDiscentePorMatricula(matricula: string): Promise -} -export class DiscenteService implements IDiscenteService { - private discenteRepository: IDiscenteRepository - - constructor(discenteRepository: IDiscenteRepository) { - this.discenteRepository = discenteRepository - } - - async lidaComCriacaoDoDiscente(data: DiscenteDTO) { - if (!data.nome || !data.matricula || !data.cursoId || !data.userId) { - throw new ReportarErrorAoSistema('Dados insuficientes para criar o usuário.') - } - - try { - data.cursoId = Number(data.cursoId) - data.userId = Number(data.userId) - - const discente = await this.discenteRepository.criaDiscente(data) - return discente - } catch (error) { - console.log(error) - throw new Error('Error creating discente') - } - } - - async lidaComBuscaDoDiscentePorId(id: number) { - if (!id) { - throw new ReportarErrorAoSistema('ID do usuário não informado.') - } - try { - const discente = await this.discenteRepository.buscaDiscentePorId(id) - if (!discente) { - throw new Error('User not found') - } - return discente - } catch (error) { - throw new Error('Error fetching discente') - } - } - - async lidaComBuscaDoDiscentePorMatricula(matricula: string): Promise { - if (!matricula) { - throw new ReportarErrorAoSistema('Matricula do usuário não informada.') - } - try { - const discente = await this.discenteRepository.buscaDiscentePorMatricula(matricula) - - return discente - } catch (error) { - throw new Error('Error fetching discente') - } - } - - async lidaComAtualizacaoDoDiscente(id: number, data: Partial) { - if (!id) { - throw new ReportarErrorAoSistema('ID do usuário não informado.') - } - - if (!data.nome || !data.matricula || !data.cursoId || !data.userId) { - throw new ReportarErrorAoSistema('Dados insuficientes para atualizar o usuário.') - } - - try { - const discenteAtualizado = await this.discenteRepository.atualizaDiscente(id, data) - return discenteAtualizado - } catch (error) { - throw new Error('Error updating discente') - } - } - - async lidaComRemocaoDoDiscente(id: number) { - if (!id) { - throw new ReportarErrorAoSistema('ID do usuário não informado.') - } - try { - await this.discenteRepository.deletaDiscente(id) - } catch (error) { - throw new Error('Error deleting discente') - } - } -} diff --git a/src/services/disciplina-service.ts b/src/services/disciplina-service.ts deleted file mode 100644 index c339a8f..0000000 --- a/src/services/disciplina-service.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Disciplina } from "@prisma/client" -import { IDisciplinaRepository } from "../repositories/disciplina-repository" -import { UFCGImporter } from "../importers/implementation/ufcg-importer" -import { Importer } from "../importers/importer" -import { SessionDTO } from "../dtos/session-dto" - -export interface IDisciplinaService { - lidaComImportacaoDasDisciplinasDoDiscente(sessionDTO: SessionDTO, importer: Importer): Promise -} - -export class DisciplinaService implements IDisciplinaService { - private disciplinaRepository: IDisciplinaRepository - - constructor(disciplinaRepository: IDisciplinaRepository) { - this.disciplinaRepository = disciplinaRepository - } - - async lidaComImportacaoDasDisciplinasDoDiscente(sessionDTO: SessionDTO, importer: Importer): Promise { - if (!sessionDTO.matricula || !sessionDTO.senha) { - throw new Error("Login, senha são obrigatórios") - } - - const { matricula, senha, discenteId } = sessionDTO - - const disciplinasImportadas = await importer.importaDisciplinas(discenteId!, matricula, senha) - console.log(`importUserData: ${JSON.stringify(disciplinasImportadas, null, 2)}`) - - const countDisciplinasCriadas = await this.disciplinaRepository.saveDisciplinas(disciplinasImportadas) - - return countDisciplinasCriadas - } - - switchImporter(vinculo: string) { - switch (vinculo) { - case "UFCG": - return new UFCGImporter() - default: - throw new Error("Importer error") - } - } -} diff --git a/src/services/user-service.ts b/src/services/user-service.ts deleted file mode 100644 index 79a5474..0000000 --- a/src/services/user-service.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { IUserRepository } from'../repositories/user-repository' -import { UserDTO } from '../dtos/user-dto' -import { User } from '@prisma/client' -import { ReportarErrorAoSistema } from '../exceptions/ReportarErroAoSistema' - -export interface IUserService { - lidaComCriacaoDoUsuario(data: UserDTO): Promise - lidaComBuscaDoUsuarioPorId(id: number): Promise - lidaComAtualizacaoDoUsuario(id: number, data: Partial): Promise - lidaComRemocaoDoUsuario(id: number): Promise - lidaComBuscaDosUsuarios(): Promise -} -export class UserService implements IUserService { - private userRepository: IUserRepository - - constructor(userRepository: IUserRepository) { - this.userRepository = userRepository - } - - async lidaComCriacaoDoUsuario({ nome, role }: UserDTO): Promise { - try { - if (!nome || typeof nome !== 'string') { - throw new ReportarErrorAoSistema('Nome é obrigatório e precisa ser uma string.') - } - if (!role || !['ADMIN', 'DISCENTE', 'DOCENTE'].includes(role)) { - throw new ReportarErrorAoSistema('Uma role é obrigatória. Veja a documentação') - } - - const user = await this.userRepository.criaUsuario({ nome, role }) - return user - } catch (error) { - throw new ReportarErrorAoSistema('Erro ao tentar salvar usuário ao banco de dados.') - } - } - - async lidaComBuscaDoUsuarioPorId(id: number) { - try { - const user = await this.userRepository.buscaUsuarioPorId(id) - if (!user) { - throw new Error('User not found') - } - return user - } catch (error) { - throw new Error('Erro ao buscar usuário') - } - } - async lidaComAtualizacaoDoUsuario(id: number, data: Partial) { - if (!data.nome || !data.role) { - throw new ReportarErrorAoSistema('Nome e role são obrigatórios') - } - try { - const updatedUser = await this.userRepository.atualizaUsuario(id, data) - return updatedUser - } catch (error) { - throw new Error('Erro ao atualizar o usuário') - } - } - - async lidaComRemocaoDoUsuario(id: number) { - try { - await this.userRepository.deletaUsuario(id) - } catch (error) { - throw new Error('Error deleting user') - } - } - - async lidaComBuscaDosUsuarios() { - try { - const users = await this.userRepository.buscaUsuarios() - - if (!users) { - return [] - } - - return users - } catch (error) { - throw new Error('Error fetching user') - } - } -} diff --git a/src/shared/database/database-interface.ts b/src/shared/database/database-interface.ts new file mode 100644 index 0000000..531cc76 --- /dev/null +++ b/src/shared/database/database-interface.ts @@ -0,0 +1,23 @@ +import { Aluno, Disciplina, Usuario } from "@prisma/client" +import { AlunoDTO } from "../../features/alunos/aluno-dto" +import { UsuarioDTO } from "../../features/usuarios/usuario-dto" +import { DisciplinaHistoricoProps } from "../importers/implementation/ufcg-importer" + +export interface IDatabase { + salvaUsuario(usuarioDTO: UsuarioDTO): Promise + atualizaUsuario(id: number, data: Partial): Promise + adicionaTokenDeAutenticacaoAoUsuario(id: number, token: string): Promise + buscaUsuarioPorId(id: number): Promise + buscaPorTodosUsuarios(): Promise + removeUsuario(id: number): Promise + + salvaAluno(data: AlunoDTO): Promise + atualizaAluno(id: number, data: Partial): Promise + buscaAlunoPorId(id: number): Promise + buscaAlunoPorMatricula(matricula: string): Promise + buscaInformacoesDoAlunoPorId(id: number): Promise | null> + removeAluno(id: number): Promise + + + salvaVariasDisciplinas(data: DisciplinaHistoricoProps[]): Promise +} diff --git a/src/shared/database/prisma-database.ts b/src/shared/database/prisma-database.ts new file mode 100644 index 0000000..71575f4 --- /dev/null +++ b/src/shared/database/prisma-database.ts @@ -0,0 +1,105 @@ +// src/database/PrismaDatabase.tsimport { PrismaClient } from'@prisma/client' +import { Aluno, Disciplina, PrismaClient, Usuario } from '@prisma/client' +import { UsuarioDTO } from '../../features/usuarios/usuario-dto' +import { IDatabase } from './database-interface' +import { AlunoDTO } from '../../features/alunos/aluno-dto' +import { PrismaClientInitializationError } from '@prisma/client/runtime/library' +import { DisciplinaHistoricoProps } from '../importers/implementation/ufcg-importer' + +const prisma = new PrismaClient() +export class PrismaDatabase implements IDatabase { + // Métodos de consulta e escrita do banco de dados para Usuário + async salvaUsuario(usuarioDTO: UsuarioDTO): Promise { + return await prisma.usuario.create({ data: { ...usuarioDTO } }) + } + + async atualizaUsuario(id: number, data: Partial): Promise { + return await prisma.usuario.update({ where: { id }, data }) + } + + async adicionaTokenDeAutenticacaoAoUsuario(id: number, token: string): Promise { + await prisma.usuario.update({ where: { id }, data: { token } }) + } + + async buscaUsuarioPorId(id: number): Promise { + return await prisma.usuario.findUnique({ where: { id } }) + } + + async buscaPorTodosUsuarios(): Promise { + return await prisma.usuario.findMany() + } + + async removeUsuario(id: number): Promise { + await prisma.usuario.delete({ where: { id } }) + } + + // Métodos de consulta e escrita do banco de dados para Aluno + async salvaAluno(data: AlunoDTO): Promise { + const aluno = await prisma.aluno.create({ + data: { + nome: data.nome, + matricula: data.matricula, + usuario: { + connect: { + id: data.usuarioId, + } + }, + }, + }) + + return aluno + } + + async atualizaAluno(id: number, data: Partial): Promise { + return await prisma.aluno.update({ where: { id }, data }) + } + + async buscaAlunoPorId(id: number): Promise { + return await prisma.aluno.findUnique({ where: { id } }) + } + + async buscaInformacoesDoAlunoPorId(id: number): Promise | null> { + return await prisma.aluno.findUnique({ + where: { + id, + }, + select: { + id: true, + nome: true, + matricula: true, + universidade: true, + curso: true, + historicoAcademico: true, + createdAt: true, + updatedAt: true, + } + }) + } + + + async buscaAlunoPorMatricula(matricula: string): Promise { + return await prisma.aluno.findUnique({ where: { matricula } }) + } + + async removeAluno(id: number) { + await prisma.aluno.delete({ where: { id } }) + } + + // Métodos de consulta e escrita do banco de dados para Disciplina + async salvaVariasDisciplinas(data: DisciplinaHistoricoProps[]): Promise { + try { + const disciplinas = await prisma.disciplina.createMany({ + data: [...data] + }) + + return disciplinas.count + } catch (error) { + if (error instanceof PrismaClientInitializationError) { + console.error('Erro ao inicializar o Prisma Client:', error.message) + } else { + console.error('Erro desconhecido ao salvar disciplinas:', error) + } + throw error + } + } +} diff --git a/src/exceptions/ReportarErroAoSistema.ts b/src/shared/exceptions/ReportarErroAoSistema.ts similarity index 100% rename from src/exceptions/ReportarErroAoSistema.ts rename to src/shared/exceptions/ReportarErroAoSistema.ts diff --git a/src/shared/importers/implementation/ufcg-importer.ts b/src/shared/importers/implementation/ufcg-importer.ts new file mode 100644 index 0000000..1c431ec --- /dev/null +++ b/src/shared/importers/implementation/ufcg-importer.ts @@ -0,0 +1,277 @@ +import axios, { AxiosRequestConfig, AxiosResponse } from 'axios' +import * as cheerio from 'cheerio' +import he from 'he' +import { Importer } from '../importer' +import { Disciplina, SituacaoDisciplina } from '@prisma/client' +import iconv from 'iconv-lite'; + +const urlSearchParams: AxiosRequestConfig = { + timeout: 10000, + withCredentials: true, + validateStatus: () => true, + httpsAgent: new (require('https').Agent)({ + rejectUnauthorized: false + }), + responseType: 'arraybuffer', + responseEncoding: 'binary', +} + +export interface DisciplinaHistoricoProps { + alunoId: number; + codigo: string; + nome: string; + creditos: number; + cargaHoraria: number; + quantidadeProvas: number; + quantidadeFaltas: number; + semestre: string; + mediaFinal: number | null; + situacao: SituacaoDisciplina; + docente: string; + horario?: string; +} + +export class UFCGImporter implements Importer { + private disciplinas: DisciplinaHistoricoProps[] = [] + private codigos: string[] = [] + private cookies: string[] = [] + private URL_BASE = "https://pre.ufcg.edu.br:8443/ControleAcademicoOnline/Controlador" + private URL_LOGIN = this.URL_BASE + private LOGIN = "login" + private SENHA = "senha" + private COMMAND = "command" + private ALUNO_LOGIN = "AlunoLogin" + + private REPROVADO_POR_FALTA = "Reprovado por Falta" + private TRANCADO = "Trancado" + private DISPENSA = "Dispensa" + private APROVADO = "Aprovado" + private REPROVADO = "Reprovado" + private ERRO_AUTENTICACAO = "ERRO NA AUTENTICAÇÃO" + public static readonly ERRO = "Matrícula inválida ou senha incorreta." + + public async autenticaAluno(matricula: string, senha: string): Promise { + this.reportProgress("Tentando fazer login...") + + try { + const response = await axios.post(this.URL_LOGIN, new URLSearchParams({ + [this.LOGIN]: matricula, + [this.SENHA]: senha, + [this.COMMAND]: this.ALUNO_LOGIN + }), { + timeout: 10000, + withCredentials: true, + validateStatus: () => true, + httpsAgent: new (require('https').Agent)({ + rejectUnauthorized: false + }), + responseEncoding: 'binary', + }) + + this.cookies = response.headers['set-cookie']! + const loadedDocument = this.carregaHtml(response) + + if (loadedDocument('head').text().includes(this.ERRO_AUTENTICACAO) || loadedDocument('body').text().includes(UFCGImporter.ERRO)) { + throw new Error("Authentication Failed") + } + } catch (err) { + console.error(err) + } + } + + public async obterDisciplinasDoHistoricoAcademico(alunoId: number): Promise { + this.reportProgress("Obtendo histórico...") + try { + const historicoResponse = await axios.get(this.URL_LOGIN, { + params: { [this.COMMAND]: "AlunoHistorico" }, + headers: { Cookie: this.cookies.join(' ') }, + timeout: 10000, + httpsAgent: new (require('https').Agent)({ + rejectUnauthorized: false + }), + responseType: 'arraybuffer', // Receber os dados como array buffer + responseEncoding: 'binary', + }) + + + + const loadedHistoricoDocument = this.carregaHtml(historicoResponse) + + + const trsDisciplinas = loadedHistoricoDocument("div[id=disciplinas] > table > tbody > tr").toArray() + for (const el of trsDisciplinas) { + const tds = loadedHistoricoDocument(el).find("td") + const disciplina: DisciplinaHistoricoProps = this.montaDisciplina(tds, alunoId) + this.disciplinas.push(disciplina) + this.codigos.push(tds.eq(0).text() || '') + } + + if (this.disciplinas.length === 0) { + throw new Error("Você não possui histórico acadêmico") + } + + return this.disciplinas + } catch (error: any) { + if (axios.isAxiosError(error)) { + if (error.code === 'ECONNABORTED') { + throw new Error("Não foi possível buscar os dados. Tente novamente.") + } else if (error.message.includes('ENOTFOUND')) { + throw new Error("Não foi possível acessar o site da Universidade. Você está conectado a internet?") + } else if (error.message.includes('UNABLE_TO_VERIFY_LEAF_SIGNATURE')) { + throw new Error("Não foi possível acessar o site. Tente novamente") + } + } + throw new Error(error.message) + } + } + + public async obterDetalhesDaDisciplina(): Promise { + this.reportProgress("Obtendo detalhes das disciplinas...") + + try { + const horarioResponse = await axios.get(this.URL_BASE, { + params: { [this.COMMAND]: "AlunoTurmasListar" }, + headers: { Cookie: this.cookies.join(' ') }, + httpsAgent: new (require('https').Agent)({ + rejectUnauthorized: false + }), + responseType: 'arraybuffer', // Receber os dados como array buffer + responseEncoding: 'binary', + }) + + let htmlHorarioResponse = iconv.decode(Buffer.from(horarioResponse.data), 'ISO-8859-1'); + htmlHorarioResponse = htmlHorarioResponse.replace(/]*charset=["']?[^"'>]+["']?[^>]*>/i, ''); + + const loadedHorarioDocument = this.carregaHtml(horarioResponse) + const table = loadedHorarioDocument("table") + + if (table) { + const trs = table.find("tr") + trs.each((index, element) => { + if (index === 0) return // Skip header row + + const tds = loadedHorarioDocument(element).children() + const disciplina = this.deParaDisciplinaPorCodigo(tds.eq(1).text() || '') + + if (disciplina) { + disciplina.horario = `Turma: ${tds.eq(3).text()}\nHorário: ${tds.eq(4).text()}` + } + }) + } + + return this.disciplinas + } catch (err) { + console.error(err) + } + } + + private carregaHtml(response: AxiosResponse): cheerio.CheerioAPI { + let htmlFormatado = iconv.decode(Buffer.from(response.data), 'ISO-8859-1'); + htmlFormatado = htmlFormatado.replace(/]*charset=["']?[^"'>]+["']?[^>]*>/i, ''); + const loadedDocument = cheerio.load(htmlFormatado) // Carregando o HTML + return loadedDocument + } + + private deParaDisciplinaPorCodigo(codigo: string): DisciplinaHistoricoProps | undefined { + const disciplinasFind = this.disciplinas.filter((disciplina, index) => this.codigos[index] === codigo) + + return disciplinasFind.reduce((latest, disciplina) => { + return (!latest || disciplina.semestre > latest.semestre) ? disciplina : latest + }, undefined as DisciplinaHistoricoProps | undefined) + } + + private montaDisciplina(tds: any, alunoId: number): DisciplinaHistoricoProps { + const disciplina: DisciplinaHistoricoProps = { + alunoId, + codigo: tds.eq(0).text() || '', + nome: this.cleanText(tds.eq(1).text()) || '', + creditos: parseInt(tds.eq(3).text() || '0', 10), + cargaHoraria: parseInt(tds.eq(4).text() || '0', 10), + quantidadeProvas: this.calcularQuantidadeNotasPorCreditosDaDisciplina(tds.eq(3).text()), + quantidadeFaltas: this.calcularQuantidadeFaltasPorCreditosDaDisciplina(tds.eq(3).text()), + semestre: tds.eq(7).text() || '', + mediaFinal: isNaN(parseFloat(tds.eq(5).text().replace(",", "."))) ? null : parseFloat(tds.eq(5).text().replace(",", ".")), + situacao: this.definirSituacaoDisciplina(tds.eq(6).text(), parseFloat(tds.eq(5).text().replace(",", "."))), + docente: this.cleanText(tds.eq(1).find('span.small').text() || 'Ausente') + } + + return disciplina + } + + private definirSituacaoDisciplina(situacao: string, mediaFinal: number | null): SituacaoDisciplina { + let status: SituacaoDisciplina; + + switch (situacao) { + case this.APROVADO: + status = SituacaoDisciplina.APROVADO; + break; + case this.DISPENSA: + status = SituacaoDisciplina.DISPENSA; + if (mediaFinal === null) { + mediaFinal = 7.0; + } + break; + case this.REPROVADO: + status = SituacaoDisciplina.REPROVADO; + break; + case this.REPROVADO_POR_FALTA: + status = SituacaoDisciplina.REPROVADO_POR_FALTA; + mediaFinal = 0; + break; + case this.TRANCADO: + status = SituacaoDisciplina.TRANCADA; + break; + default: + status = SituacaoDisciplina.EM_PROGRESSO; + break; + } + + return status; + } + + private calcularQuantidadeNotasPorCreditosDaDisciplina(creditos: string): number { + const creditosDisciplina = parseInt(creditos || '0', 10) + + switch (creditosDisciplina) { + case 1: + case 2: + case 3: + return 2 + case 4: + return 3 + case 5: + case 6: + return 4 + default: + return 1 + } + } + + private calcularQuantidadeFaltasPorCreditosDaDisciplina(creditos: string): number { + const creditosDisciplina = parseInt(creditos || '0', 10) + + switch (creditosDisciplina) { + case 1: + case 2: + case 3: + return 4 + case 4: + return 5 + case 5: + case 6: + return 6 + default: + return 3 + } + } + + reportProgress(message: string): void { + console.log(message) // Implement your progress reporting logic here + } + + private cleanText = (text: string): string => { + return he.decode(text) // Decodifica entidades HTML + .replace(/\s+/g, ' ') + .trim() // Remove espaços em branco no início e no final + } +} diff --git a/src/shared/importers/importer.ts b/src/shared/importers/importer.ts new file mode 100644 index 0000000..eef4476 --- /dev/null +++ b/src/shared/importers/importer.ts @@ -0,0 +1,9 @@ +import { Disciplina } from "@prisma/client" +import * as cheerio from 'cheerio' +import { DisciplinaHistoricoProps } from "./implementation/ufcg-importer" + +export interface Importer { + autenticaAluno(matricula: string, senha: string): Promise + obterDisciplinasDoHistoricoAcademico(alunoId: number): Promise + obterDetalhesDaDisciplina(): Promise +} diff --git a/src/middlewares/autorizacao-middleware.ts b/src/shared/middlewares/autorizacao-middleware.ts similarity index 55% rename from src/middlewares/autorizacao-middleware.ts rename to src/shared/middlewares/autorizacao-middleware.ts index f5bb98c..1b13491 100644 --- a/src/middlewares/autorizacao-middleware.ts +++ b/src/shared/middlewares/autorizacao-middleware.ts @@ -1,7 +1,7 @@ -import { NextFunction, Request, Response } from "express"; -import { DiscenteService, IDiscenteService } from "../services/discente-service"; import jwt from 'jsonwebtoken' -import { ReportarErrorAoSistema } from "../exceptions/ReportarErroAoSistema"; +import { NextFunction, Request, Response } from "express"; +import { ReportarErrorAoSistema } from "../exceptions/ReportarErroAoSistema" +import { AlunoService, IAlunoService } from "../../features/alunos/aluno-service" interface PayloadToken { id: number; @@ -10,23 +10,23 @@ interface PayloadToken { exp: number; } -export interface PayloadUserAlreadyExists extends Partial { - userAlreadyExists: boolean; +export interface PayloadUsuarioAlreadyExists extends Partial { + usuarioAlreadyExists: boolean; } export class AutorizacaoMiddleware { private segredo: string; - private discenteService?: DiscenteService + private alunoService?: AlunoService - constructor(discenteService?: DiscenteService) { + constructor(alunoService?: AlunoService) { this.segredo = process.env.JWT_SECRET || 'segredo_padrao' - this.discenteService = discenteService + this.alunoService = alunoService - console.log(discenteService) + console.log(alunoService) } public autorizarApenas(roleNecessaria: string) { - return(req: Request, res: Response, next: NextFunction) => { + return (req: Request, res: Response, next: NextFunction) => { const cabecalhoAutenticacao = req.headers.authorization; if (!cabecalhoAutenticacao) { @@ -43,10 +43,10 @@ export class AutorizacaoMiddleware { } interface CustomRequest extends Request { - user: PayloadToken; + usuario: PayloadToken; } const customReq: CustomRequest = req as CustomRequest; - customReq.user = decodificado; + customReq.usuario = decodificado; next() } catch (erro) { return res.status(401).json({ mensagem: 'Token inválido ou expirado.' }); @@ -54,35 +54,35 @@ export class AutorizacaoMiddleware { }; } - /** - * Esse método checa se um usuário já existe no sistema, ou seja, se já foi adicionado a base - * Se a classe `discenteService` não for inicializa por algum motivo e esse método for chamada você receberá um erro. - * @param {Request} req - The Express request object. - * @param {Response} res - The Express response object. - * @param {NextFunction} next - The Express next middleware function. - * @throws {Error} If the `discenteService` is not properly initialized, a warning message will be returned. - */ + /** + * Esse método checa se um usuário já existe no sistema, ou seja, se já foi adicionado a base + * Se a classe `alunoService` não for inicializa por algum motivo e esse método for chamada você receberá um erro. + * @param {Request} req - The Express request object. + * @param {Response} res - The Express response object. + * @param {NextFunction} next - The Express next middleware function. + * @throws {Error} If the `alunoService` is not properly initialized, a warning message will be returned. + */ public async verificaSeAlunoJaExisteNoSistema(req: Request, res: Response, next: NextFunction) { const { matricula } = req.body; - - if (!this.discenteService) { - throw new ReportarErrorAoSistema("DiscenteService is undefined. Por favor, verifique se a propriedade foi inicializada corretamente.") + + if (!this.alunoService) { + throw new ReportarErrorAoSistema("AlunoService is undefined. Por favor, verifique se a propriedade foi inicializada corretamente.") } try { - const aluno = await this.discenteService.lidaComBuscaDoDiscentePorMatricula(matricula); + const aluno = await this.alunoService.lidaComBuscaDoAlunoPorMatricula(matricula); interface CustomRequest extends Request { - user: PayloadUserAlreadyExists; + usuario: PayloadUsuarioAlreadyExists; } const customReq: CustomRequest = req as CustomRequest; - + if (aluno) { - customReq.user = { - userAlreadyExists: true, + customReq.usuario = { + usuarioAlreadyExists: true, } } - + next(); } catch (error) { if (error instanceof ReportarErrorAoSistema) {