diff --git a/src/app.module.ts b/src/app.module.ts index 4ce2296..8ea0ca7 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -14,7 +14,7 @@ import { ProvaModule } from './modules/prova/prova.module'; import { QuestaoModule } from './modules/questao/questao.module'; import { SimuladoModule } from './modules/simulado/simulado.module'; import { SubjectModule } from './modules/questao/subject/subject.module'; -import { TipoSimuladoModule } from './modules/tipo-simulado/tipo-simulado.module'; +import { CategoriaModule } from './modules/categoria/categoria.module'; import { EnvModule } from './shared/modules/env/env.module'; import { Env, envSchema } from './shared/modules/env/env'; @@ -37,7 +37,7 @@ import { Env, envSchema } from './shared/modules/env/env'; FrenteModule, MateriaModule, QuestaoModule, - TipoSimuladoModule, + CategoriaModule, SimuladoModule, ProvaModule, HistoricoModule, diff --git a/src/modules/tipo-simulado/tipo-simulado.controller.ts b/src/modules/categoria/categoria.controller.ts similarity index 61% rename from src/modules/tipo-simulado/tipo-simulado.controller.ts rename to src/modules/categoria/categoria.controller.ts index 277005e..828052c 100644 --- a/src/modules/tipo-simulado/tipo-simulado.controller.ts +++ b/src/modules/categoria/categoria.controller.ts @@ -10,25 +10,25 @@ import { import { ApiResponse, ApiTags } from '@nestjs/swagger'; import { GetAllDtoInput } from 'src/shared/dtos/get-all.dto.input'; import { GetAllDtoOutput } from 'src/shared/dtos/get-all.dto.output'; -import { CreateTipoSimuladoDTOInput } from './dtos/create.dto.input'; -import { TipoSimulado } from './schemas/tipo-simulado.schema'; -import { TipoSimuladoService } from './tipo-simulado.service'; +import { CreateCategoriaDTOInput } from './dtos/create.dto.input'; +import { Categoria } from './schemas/categoria.schema'; +import { CategoriaService } from './categoria.service'; -@ApiTags('Tipo Simulado') -@Controller('v1/tipo-simulado') -export class TipoSimuladoController { - constructor(private readonly service: TipoSimuladoService) {} +@ApiTags('Categoria') +@Controller('v1/categoria') +export class CategoriaController { + constructor(private readonly service: CategoriaService) {} @Get() @ApiResponse({ status: 200, description: 'materias cadastradas e validas', - type: TipoSimulado, + type: Categoria, isArray: true, }) public async getAll( @Query() query: GetAllDtoInput, - ): Promise> { + ): Promise> { return await this.service.getAll(query); } @@ -36,12 +36,12 @@ export class TipoSimuladoController { @ApiResponse({ status: 200, description: 'cadastrado com sucesso', - type: TipoSimulado, + type: Categoria, isArray: false, }) public async post( - @Body() model: CreateTipoSimuladoDTOInput, - ): Promise { + @Body() model: CreateCategoriaDTOInput, + ): Promise { return await this.service.add(model); } @@ -49,10 +49,10 @@ export class TipoSimuladoController { @ApiResponse({ status: 200, description: 'materias cadastradas e validas', - type: TipoSimulado, + type: Categoria, isArray: false, }) - public async getById(@Param('id') id: string): Promise { + public async getById(@Param('id') id: string): Promise { return await this.service.getById(id); } diff --git a/src/modules/categoria/categoria.module.ts b/src/modules/categoria/categoria.module.ts new file mode 100644 index 0000000..6ec66b2 --- /dev/null +++ b/src/modules/categoria/categoria.module.ts @@ -0,0 +1,35 @@ +import { Module } from '@nestjs/common'; +import { CategoriaService } from './categoria.service'; +import { CategoriaController } from './categoria.controller'; +import { CategoriaRepository } from './categoria.repository'; +import { MongooseModule } from '@nestjs/mongoose'; +import { Categoria, CategoriaSchema } from './schemas/categoria.schema'; +import { CategoriaUniqueValidator } from './validator/categoria-unique.validator'; +import { MateriaExistValidator } from '../materia/validator/materia-exist.validator'; +import { FrenteExistValidator } from '../frente/validator/frente-exist.validator'; +import { FrenteModule } from '../frente/frente.module'; +import { MateriaModule } from '../materia/materia.module'; +import { ExameModule } from '../exame/exame.module'; +import { ExameExistValidator } from '../exame/validator/exame-exist.validator'; + +@Module({ + imports: [ + MongooseModule.forFeature([ + { name: Categoria.name, schema: CategoriaSchema }, + ]), + FrenteModule, + MateriaModule, + ExameModule, + ], + controllers: [CategoriaController], + providers: [ + CategoriaService, + CategoriaRepository, + CategoriaUniqueValidator, + MateriaExistValidator, + FrenteExistValidator, + ExameExistValidator, + ], + exports: [CategoriaRepository], +}) +export class CategoriaModule {} diff --git a/src/modules/categoria/categoria.repository.ts b/src/modules/categoria/categoria.repository.ts new file mode 100644 index 0000000..6e62de6 --- /dev/null +++ b/src/modules/categoria/categoria.repository.ts @@ -0,0 +1,33 @@ +import { BaseRepository } from 'src/shared/base/base.repository'; +import { Categoria } from './schemas/categoria.schema'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { GetAllWhereInput } from 'src/shared/base/interfaces/get-all.input'; +import { GetAllOutput } from 'src/shared/base/interfaces/get-all.output'; + +export class CategoriaRepository extends BaseRepository { + constructor(@InjectModel(Categoria.name) model: Model) { + super(model); + } + + override async getById(id: string): Promise { + return await this.model + .findById(id) + .populate('exame'); + } + + override async getAll({ + page, + limit, + where, + }: GetAllWhereInput): Promise> { + const data = await this.model + .find() + .skip((page - 1) * limit) + .limit(limit ?? Infinity) + .where({ ...where }) + .populate('exame'); + const totalItems = await this.model.where({ ...where }).countDocuments(); + return { data, page, limit, totalItems }; + } +} diff --git a/src/modules/categoria/categoria.service.ts b/src/modules/categoria/categoria.service.ts new file mode 100644 index 0000000..ca2534b --- /dev/null +++ b/src/modules/categoria/categoria.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@nestjs/common'; +import { GetAllInput } from 'src/shared/base/interfaces/get-all.input'; +import { GetAllOutput } from 'src/shared/base/interfaces/get-all.output'; +import { CreateCategoriaDTOInput } from './dtos/create.dto.input'; +import { Categoria } from './schemas/categoria.schema'; +import { CategoriaRepository } from './categoria.repository'; + +@Injectable() +export class CategoriaService { + constructor(private readonly repository: CategoriaRepository) {} + + public async add(item: CreateCategoriaDTOInput): Promise { + const categoria = Object.assign(new Categoria(), item); + return await this.repository.create(categoria); + } + + public async getById(id: string): Promise { + return await this.repository.getById(id); + } + + public async getAll(param: GetAllInput): Promise> { + return await this.repository.getAll(param); + } + + public async delete(id: string): Promise { + await this.repository.delete(id); + } +} diff --git a/src/modules/categoria/dtos/create.dto.input.ts b/src/modules/categoria/dtos/create.dto.input.ts new file mode 100644 index 0000000..e70d987 --- /dev/null +++ b/src/modules/categoria/dtos/create.dto.input.ts @@ -0,0 +1,46 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + IsBoolean, + IsMongoId, + IsNumber, + IsOptional, + IsString, +} from 'class-validator'; +import { CategoriaUnique } from '../validator/categoria-unique.validator'; +import { ExameExist } from '../../exame/validator/exame-exist.validator'; + +export class CreateCategoriaDTOInput { + @ApiProperty() + @IsString() + @CategoriaUnique({ message: 'nome categoria já existe' }) + public nome: string; + + @ApiProperty() + @IsNumber() + public duracao: number; + + @ApiProperty({ required: false }) + @IsNumber() + @IsOptional() + public quantidadeTotalQuestao: number; + + @ApiProperty() + @IsMongoId() + @ExameExist({ message: 'exame não encontrado' }) + public exame: string; + + @ApiProperty({ required: false, default: false }) + @IsBoolean() + @IsOptional() + public custom: boolean; + + @ApiProperty({ required: false, default: true }) + @IsBoolean() + @IsOptional() + public selecionavel: boolean; + + @ApiProperty({ required: false, default: '' }) + @IsString() + @IsOptional() + public descricao: string; +} diff --git a/src/modules/categoria/schemas/categoria.schema.ts b/src/modules/categoria/schemas/categoria.schema.ts new file mode 100644 index 0000000..9c5a69b --- /dev/null +++ b/src/modules/categoria/schemas/categoria.schema.ts @@ -0,0 +1,38 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { BaseSchema } from 'src/shared/base/base.schema'; +import { ApiProperty } from '@nestjs/swagger'; +import { Types } from 'mongoose'; +import { Exame } from '../../exame/exame.schema'; + +@Schema({ timestamps: true, versionKey: false }) +export class Categoria extends BaseSchema { + @Prop({ unique: true }) + @ApiProperty() + public nome: string; + + @Prop() + @ApiProperty() + public duracao: number; + + @Prop({ required: false, default: null }) + @ApiProperty({ required: false, nullable: true }) + public quantidadeTotalQuestao: number | null; + + @Prop({ ref: Exame.name, type: Types.ObjectId, required: true }) + @ApiProperty() + public exame: Exame; + + @Prop({ required: true, default: false }) + @ApiProperty({ default: false }) + public custom: boolean; + + @Prop({ required: true, default: true }) + @ApiProperty({ default: true }) + public selecionavel: boolean; + + @Prop({ required: false, default: '' }) + @ApiProperty({ required: false, default: '' }) + public descricao: string; +} + +export const CategoriaSchema = SchemaFactory.createForClass(Categoria); diff --git a/src/modules/tipo-simulado/validator/tipo-simulado-exist.validator.ts b/src/modules/categoria/validator/categoria-exist.validator.ts similarity index 55% rename from src/modules/tipo-simulado/validator/tipo-simulado-exist.validator.ts rename to src/modules/categoria/validator/categoria-exist.validator.ts index ed48fd4..4f17cc3 100644 --- a/src/modules/tipo-simulado/validator/tipo-simulado-exist.validator.ts +++ b/src/modules/categoria/validator/categoria-exist.validator.ts @@ -5,35 +5,33 @@ import { ValidatorConstraintInterface, registerDecorator, } from 'class-validator'; -import { TipoSimuladoRepository } from '../tipo-simulado.repository'; +import { CategoriaRepository } from '../categoria.repository'; @Injectable() @ValidatorConstraint({ async: true }) -export class TipoSimuladoExistValidator - implements ValidatorConstraintInterface -{ +export class CategoriaExistValidator implements ValidatorConstraintInterface { constructor( - private readonly tipoSimuladoRepository: TipoSimuladoRepository, + private readonly categoriaRepository: CategoriaRepository, ) {} async validate(value: any): Promise { try { - const tipoSimulado = await this.tipoSimuladoRepository.getById(value); - return !!tipoSimulado; + const categoria = await this.categoriaRepository.getById(value); + return !!categoria; } catch { return false; } } } -export const TipoSimuladoExist = (validationOptions: ValidationOptions) => { +export const CategoriaExist = (validationOptions: ValidationOptions) => { return (obj: object, props: string) => { registerDecorator({ target: obj.constructor, propertyName: props, options: validationOptions, constraints: [], - validator: TipoSimuladoExistValidator, + validator: CategoriaExistValidator, }); }; }; diff --git a/src/modules/tipo-simulado/validator/tipo-simulado-unique.validator.ts b/src/modules/categoria/validator/categoria-unique.validator.ts similarity index 54% rename from src/modules/tipo-simulado/validator/tipo-simulado-unique.validator.ts rename to src/modules/categoria/validator/categoria-unique.validator.ts index a79c25c..79c8a05 100644 --- a/src/modules/tipo-simulado/validator/tipo-simulado-unique.validator.ts +++ b/src/modules/categoria/validator/categoria-unique.validator.ts @@ -5,33 +5,31 @@ import { ValidatorConstraintInterface, registerDecorator, } from 'class-validator'; -import { TipoSimuladoRepository } from '../tipo-simulado.repository'; +import { CategoriaRepository } from '../categoria.repository'; @Injectable() @ValidatorConstraint({ async: true }) -export class TipoSimuladoUniqueValidator - implements ValidatorConstraintInterface -{ +export class CategoriaUniqueValidator implements ValidatorConstraintInterface { constructor( - private readonly tipoSimuladoRepository: TipoSimuladoRepository, + private readonly categoriaRepository: CategoriaRepository, ) {} async validate(value: any): Promise { - const tipoSimulado = await this.tipoSimuladoRepository.getByFilter({ + const categoria = await this.categoriaRepository.getByFilter({ nome: value, }); - return !tipoSimulado; + return !categoria; } } -export const TipoSimuladoUnique = (validationOptions: ValidationOptions) => { +export const CategoriaUnique = (validationOptions: ValidationOptions) => { return (obj: object, props: string) => { registerDecorator({ target: obj.constructor, propertyName: props, options: validationOptions, constraints: [], - validator: TipoSimuladoUniqueValidator, + validator: CategoriaUniqueValidator, }); }; }; diff --git a/src/modules/exame/exame-seed.service.ts b/src/modules/exame/exame-seed.service.ts new file mode 100644 index 0000000..dec7c60 --- /dev/null +++ b/src/modules/exame/exame-seed.service.ts @@ -0,0 +1,20 @@ +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { ExameRepository } from './exame.repository'; +import { Exame } from './exame.schema'; +import { Localizacao } from './enum/localizacao.enum'; + +@Injectable() +export class ExameSeedService implements OnModuleInit { + constructor(private readonly repo: ExameRepository) {} + + async onModuleInit() { + const exists = await this.repo.getByFilter({ nome: 'Personalizado' }); + if (!exists) { + const exame = Object.assign(new Exame(), { + nome: 'Personalizado', + localizacao: Localizacao.BR, + }); + await this.repo.create(exame); + } + } +} diff --git a/src/modules/exame/exame.module.ts b/src/modules/exame/exame.module.ts index 520feb3..fba3dc5 100644 --- a/src/modules/exame/exame.module.ts +++ b/src/modules/exame/exame.module.ts @@ -6,6 +6,7 @@ import { MongooseModule } from '@nestjs/mongoose'; import { ExameSchema } from './exame.schema'; import { ExameUniqueValidator } from './validator/exame-unique.validator'; import { ExameExistValidator } from './validator/exame-exist.validator'; +import { ExameSeedService } from './exame-seed.service'; @Module({ imports: [ @@ -17,6 +18,7 @@ import { ExameExistValidator } from './validator/exame-exist.validator'; ExameRepository, ExameUniqueValidator, ExameExistValidator, + ExameSeedService, ], exports: [ExameService, ExameRepository], }) diff --git a/src/modules/historico/historico.repository.ts b/src/modules/historico/historico.repository.ts index df9d486..7fa33e0 100644 --- a/src/modules/historico/historico.repository.ts +++ b/src/modules/historico/historico.repository.ts @@ -157,11 +157,11 @@ export class HistoricoRepository extends BaseRepository { }, { $unwind: { path: '$simulado', preserveNullAndEmptyArrays: true } }, - // junta com TipoSimulado + // junta com Categoria { $lookup: { - from: 'tiposimulados', // nome real da collection - localField: 'simulado.tipo', + from: 'categorias', // nome real da collection + localField: 'simulado.categoria', foreignField: '_id', as: 'tipo', }, diff --git a/src/modules/prova/dtos/create.dto.input.ts b/src/modules/prova/dtos/create.dto.input.ts index 227fea9..8fc31f8 100644 --- a/src/modules/prova/dtos/create.dto.input.ts +++ b/src/modules/prova/dtos/create.dto.input.ts @@ -1,7 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { Edicao } from '../enums/edicao.enum'; import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; -import { ExameExist } from 'src/modules/exame/validator/exame-exist.validator'; +import { CategoriaExist } from 'src/modules/categoria/validator/categoria-exist.validator'; export class CreateProvaDTOInput { @ApiProperty({ enum: Edicao, required: false, default: Edicao.Regular }) @@ -18,12 +18,8 @@ export class CreateProvaDTOInput { @ApiProperty() @IsString() - @ExameExist({ message: 'exame não existe' }) - exame: string; - - @ApiProperty() - @IsString() //Verificar se o tipo existe - tipo: string; + @CategoriaExist({ message: 'categoria não existe' }) + categoria: string; @ApiProperty() @IsString() diff --git a/src/modules/prova/dtos/get-all.dto.output.ts b/src/modules/prova/dtos/get-all.dto.output.ts index 833fc9d..ece870f 100644 --- a/src/modules/prova/dtos/get-all.dto.output.ts +++ b/src/modules/prova/dtos/get-all.dto.output.ts @@ -14,6 +14,9 @@ export class GetProvaDTOOutout { @ApiProperty() ano: number; + @ApiProperty() + categoria: string; + @ApiProperty() exame: string; diff --git a/src/modules/prova/factory/enem_2010_2016_factory.ts b/src/modules/prova/factory/enem_2010_2016_factory.ts index 8868f8d..48a1c3a 100644 --- a/src/modules/prova/factory/enem_2010_2016_factory.ts +++ b/src/modules/prova/factory/enem_2010_2016_factory.ts @@ -1,5 +1,4 @@ import { BadRequestException, HttpException, HttpStatus } from '@nestjs/common'; -import { Exame } from 'src/modules/exame/exame.schema'; import { FrenteRepository } from 'src/modules/frente/frente.repository'; import { Frente } from 'src/modules/frente/frente.schema'; import { CreateQuestaoDTOInput } from 'src/modules/questao/dtos/create.dto.input'; @@ -10,7 +9,7 @@ import { Questao } from 'src/modules/questao/questao.schema'; import { Simulado } from 'src/modules/simulado/schemas/simulado.schema'; import { SimuladoRepository } from 'src/modules/simulado/simulado.repository'; import { SimuladoService } from 'src/modules/simulado/simulado.service'; -import { TipoSimuladoRepository } from 'src/modules/tipo-simulado/tipo-simulado.repository'; +import { CategoriaRepository } from 'src/modules/categoria/categoria.repository'; import { CreateProvaDTOInput } from '../dtos/create.dto.input'; import { ProvaRepository } from '../prova.repository'; import { Prova } from '../prova.schema'; @@ -19,29 +18,28 @@ import { ExameName, IProvaFactory } from './types'; export class Enem2010_2017Factory implements IProvaFactory { constructor( - private readonly tipoSimuladoRepository: TipoSimuladoRepository, + private readonly categoriaRepository: CategoriaRepository, private readonly questaoRepository: QuestaoRepository, private readonly provaRepository: ProvaRepository, private readonly frenteRepository: FrenteRepository, private readonly simuladoService: SimuladoService, private readonly simuladoRepository: SimuladoRepository, private readonly enemService: EnemService, - private exame: Exame, ) {} async createProva(item: CreateProvaDTOInput): Promise { - const tipo = await this.tipoSimuladoRepository.getById(item.tipo); - const prova = new Prova(item, this.exame, tipo); - prova.nome = `${tipo.nome} ${prova.ano} ${prova.edicao} ${prova.aplicacao}`; + const categoria = await this.categoriaRepository.getById(item.categoria); + const prova = new Prova(item, categoria); + prova.nome = `${categoria.nome} ${prova.ano} ${prova.edicao} ${prova.aplicacao}`; const hasProva = await this.enemService.getByName(prova.nome); if (!!hasProva) { throw new HttpException('Prova já esta cadastrada', HttpStatus.CONFLICT); } - if (prova.tipo.nome === EnemArea.Enem1) { + if (prova.categoria.nome === EnemArea.Enem1) { prova.enemAreas = [EnemArea.CienciasHumanas, EnemArea.BioExatas]; prova.totalQuestao = 90; - } else if (prova.tipo.nome === EnemArea.Enem2) { + } else if (prova.categoria.nome === EnemArea.Enem2) { prova.enemAreas = [EnemArea.Linguagens, EnemArea.Matematica]; prova.inicialNumero = 91; prova.totalQuestao = 95; @@ -50,39 +48,42 @@ export class Enem2010_2017Factory implements IProvaFactory { } public async createSimulados(prova: Prova) { - if (prova.tipo.nome === EnemArea.Enem1) { + if (prova.categoria.nome === EnemArea.Enem1) { await this.createSimuladoDia1(prova); - } else if (prova.tipo.nome === EnemArea.Enem2) { + } else if (prova.categoria.nome === EnemArea.Enem2) { await this.createSimuladoDia2(prova); } } public async createSimuladoDia1(prova: Prova) { - const mainName = `${prova.tipo.nome} ${prova.ano}`; + const mainName = `${prova.categoria.nome} ${prova.ano}`; + const exameId = prova.categoria.exame._id.toString(); prova.simulados.push( await this.enemService.createSimuladoArea( mainName, EnemArea.CienciasHumanas, + exameId, ), ); prova.simulados.push( - await this.enemService.createSimuladoArea(mainName, EnemArea.BioExatas), + await this.enemService.createSimuladoArea(mainName, EnemArea.BioExatas, exameId), ); prova.simulados.push( await this.simuladoRepository.create({ nome: `${mainName}`, - tipo: prova.tipo, + categoria: prova.categoria, questoes: [], - descricao: `${prova.exame.nome}`, + descricao: `${prova.categoria.exame.nome}`, }), ); } public async createSimuladoDia2(prova: Prova) { - const mainName = `${prova.tipo.nome} ${prova.ano}`; + const mainName = `${prova.categoria.nome} ${prova.ano}`; + const exameId = prova.categoria.exame._id.toString(); await this.enemService.createSimuladoIdiomatica(prova); prova.simulados.push( - await this.enemService.createSimuladoArea(mainName, EnemArea.Matematica), + await this.enemService.createSimuladoArea(mainName, EnemArea.Matematica, exameId), ); } @@ -342,8 +343,8 @@ export class Enem2010_2017Factory implements IProvaFactory { simulados = simulados.concat( prova.simulados.filter( (simulado) => - simulado.nome === `${prova.tipo.nome} ${prova.ano} Inglês` || - simulado.nome === `${prova.tipo.nome} ${prova.ano} Espanhol`, + simulado.nome === `${prova.categoria.nome} ${prova.ano} Inglês` || + simulado.nome === `${prova.categoria.nome} ${prova.ano} Espanhol`, ), ); } @@ -352,7 +353,7 @@ export class Enem2010_2017Factory implements IProvaFactory { simulado.nome.includes(`${question.enemArea}`), ); const simuladoPadrao = prova.simulados.find( - (simulado) => simulado.nome === `${prova.tipo.nome} ${prova.ano}`, + (simulado) => simulado.nome === `${prova.categoria.nome} ${prova.ano}`, ); if (simuladoPadrao) { simulados.push(simuladoPadrao); @@ -375,7 +376,7 @@ export class Enem2010_2017Factory implements IProvaFactory { if ( prova.ano < 2010 || prova.ano > 2016 || - prova.exame.nome !== ExameName.ENEM + prova.categoria.exame.nome !== ExameName.ENEM ) { simulados.push(...prova.simulados); } else { @@ -398,8 +399,8 @@ export class Enem2010_2017Factory implements IProvaFactory { simulados = simulados.concat( prova.simulados.filter( (simulado) => - simulado.nome === `${prova.tipo.nome} ${prova.ano} Inglês` || - simulado.nome === `${prova.tipo.nome} ${prova.ano} Espanhol`, + simulado.nome === `${prova.categoria.nome} ${prova.ano} Inglês` || + simulado.nome === `${prova.categoria.nome} ${prova.ano} Espanhol`, ), ); } @@ -408,7 +409,7 @@ export class Enem2010_2017Factory implements IProvaFactory { simulado.nome.includes(questao.enemArea), ); const simuladoPadrao = prova.simulados.find( - (simulado) => simulado.nome === `${prova.tipo.nome} ${prova.ano}`, + (simulado) => simulado.nome === `${prova.categoria.nome} ${prova.ano}`, ); if (simuladoPadrao) { simulados.push(simuladoPadrao); diff --git a/src/modules/prova/factory/enem_2017_plus_factory.ts b/src/modules/prova/factory/enem_2017_plus_factory.ts index 1054fc9..dfa2576 100644 --- a/src/modules/prova/factory/enem_2017_plus_factory.ts +++ b/src/modules/prova/factory/enem_2017_plus_factory.ts @@ -1,5 +1,4 @@ import { HttpException, HttpStatus } from '@nestjs/common'; -import { Exame } from 'src/modules/exame/exame.schema'; import { FrenteRepository } from 'src/modules/frente/frente.repository'; import { Frente } from 'src/modules/frente/frente.schema'; import { CreateQuestaoDTOInput } from 'src/modules/questao/dtos/create.dto.input'; @@ -10,7 +9,7 @@ import { Questao } from 'src/modules/questao/questao.schema'; import { Simulado } from 'src/modules/simulado/schemas/simulado.schema'; import { SimuladoRepository } from 'src/modules/simulado/simulado.repository'; import { SimuladoService } from 'src/modules/simulado/simulado.service'; -import { TipoSimuladoRepository } from 'src/modules/tipo-simulado/tipo-simulado.repository'; +import { CategoriaRepository } from 'src/modules/categoria/categoria.repository'; import { CreateProvaDTOInput } from '../dtos/create.dto.input'; import { ProvaRepository } from '../prova.repository'; import { Prova } from '../prova.schema'; @@ -19,29 +18,28 @@ import { ExameName, IProvaFactory } from './types'; export class Enem2017PlusFactory implements IProvaFactory { constructor( - private readonly tipoSimuladoRepository: TipoSimuladoRepository, + private readonly categoriaRepository: CategoriaRepository, private readonly questaoRepository: QuestaoRepository, private readonly provaRepository: ProvaRepository, private readonly frenteRepository: FrenteRepository, private readonly simuladoService: SimuladoService, private readonly simuladoRepository: SimuladoRepository, private readonly enemService: EnemService, - private exame: Exame, ) {} async createProva(item: CreateProvaDTOInput): Promise { - const tipo = await this.tipoSimuladoRepository.getById(item.tipo); - const prova = new Prova(item, this.exame, tipo); - prova.nome = `${tipo.nome} ${prova.ano} ${prova.edicao} ${prova.aplicacao}`; + const categoria = await this.categoriaRepository.getById(item.categoria); + const prova = new Prova(item, categoria); + prova.nome = `${categoria.nome} ${prova.ano} ${prova.edicao} ${prova.aplicacao}`; const hasProva = await this.enemService.getByName(prova.nome); if (!!hasProva) { throw new HttpException('Prova já esta cadastrada', HttpStatus.CONFLICT); } - if (prova.tipo.nome === EnemArea.Enem1) { + if (prova.categoria.nome === EnemArea.Enem1) { prova.enemAreas = [EnemArea.Linguagens, EnemArea.CienciasHumanas]; prova.totalQuestao = 95; - } else if (prova.tipo.nome === EnemArea.Enem2) { + } else if (prova.categoria.nome === EnemArea.Enem2) { prova.enemAreas = [EnemArea.BioExatas, EnemArea.Matematica]; prova.inicialNumero = 91; prova.totalQuestao = 90; @@ -50,38 +48,41 @@ export class Enem2017PlusFactory implements IProvaFactory { } public async createSimulados(prova: Prova) { - if (prova.tipo.nome === EnemArea.Enem1) { + if (prova.categoria.nome === EnemArea.Enem1) { await this.createSimuladoDia1(prova); - } else if (prova.tipo.nome === EnemArea.Enem2) { + } else if (prova.categoria.nome === EnemArea.Enem2) { await this.createSimuladoDia2(prova); } } public async createSimuladoDia1(prova: Prova) { - const mainName = `${prova.tipo.nome} ${prova.ano}`; + const mainName = `${prova.categoria.nome} ${prova.ano}`; + const exameId = prova.categoria.exame._id.toString(); await this.enemService.createSimuladoIdiomatica(prova); prova.simulados.push( await this.enemService.createSimuladoArea( mainName, EnemArea.CienciasHumanas, + exameId, ), ); } public async createSimuladoDia2(prova: Prova) { - const mainName = `${prova.tipo.nome} ${prova.ano}`; + const mainName = `${prova.categoria.nome} ${prova.ano}`; + const exameId = prova.categoria.exame._id.toString(); prova.simulados.push( - await this.enemService.createSimuladoArea(mainName, EnemArea.Matematica), + await this.enemService.createSimuladoArea(mainName, EnemArea.Matematica, exameId), ); prova.simulados.push( - await this.enemService.createSimuladoArea(mainName, EnemArea.BioExatas), + await this.enemService.createSimuladoArea(mainName, EnemArea.BioExatas, exameId), ); prova.simulados.push( await this.simuladoRepository.create({ nome: `${mainName}`, - tipo: prova.tipo, + categoria: prova.categoria, questoes: [], - descricao: `${prova.exame.nome}`, + descricao: `${prova.categoria.exame.nome}`, }), ); } @@ -320,8 +321,8 @@ export class Enem2017PlusFactory implements IProvaFactory { simulados = simulados.concat( prova.simulados.filter( (simulado) => - simulado.nome === `${prova.tipo.nome} ${prova.ano} Inglês` || - simulado.nome === `${prova.tipo.nome} ${prova.ano} Espanhol`, + simulado.nome === `${prova.categoria.nome} ${prova.ano} Inglês` || + simulado.nome === `${prova.categoria.nome} ${prova.ano} Espanhol`, ), ); } @@ -330,7 +331,7 @@ export class Enem2017PlusFactory implements IProvaFactory { simulado.nome.includes(question.enemArea), ); const simuladoPadrao = prova.simulados.find( - (simulado) => simulado.nome === `${prova.tipo.nome} ${prova.ano}`, + (simulado) => simulado.nome === `${prova.categoria.nome} ${prova.ano}`, ); if (simuladoPadrao) { simulados.push(simuladoPadrao); @@ -351,7 +352,7 @@ export class Enem2017PlusFactory implements IProvaFactory { diaMode: boolean, // dia1: Ciências Humanas e Linguagens ): Simulado[] { let simulados: Simulado[] = []; - if (prova.ano < 2017 || prova.exame.nome !== ExameName.ENEM) { + if (prova.ano < 2017 || prova.categoria.exame.nome !== ExameName.ENEM) { simulados = [...prova.simulados]; } else { const wasIngles = @@ -375,8 +376,8 @@ export class Enem2017PlusFactory implements IProvaFactory { simulados = simulados.concat( prova.simulados.filter( (simulado) => - simulado.nome === `${prova.tipo.nome} ${prova.ano} Inglês` || - simulado.nome === `${prova.tipo.nome} ${prova.ano} Espanhol`, + simulado.nome === `${prova.categoria.nome} ${prova.ano} Inglês` || + simulado.nome === `${prova.categoria.nome} ${prova.ano} Espanhol`, ), ); } @@ -385,7 +386,7 @@ export class Enem2017PlusFactory implements IProvaFactory { simulado.nome.includes(questao.enemArea), ); const simuladoPadrao = prova.simulados.find( - (simulado) => simulado.nome === `${prova.tipo.nome} ${prova.ano}`, + (simulado) => simulado.nome === `${prova.categoria.nome} ${prova.ano}`, ); if (simuladoPadrao) { simulados.push(simuladoPadrao); diff --git a/src/modules/prova/factory/prova_factory.ts b/src/modules/prova/factory/prova_factory.ts index 1023c73..cb75c0f 100644 --- a/src/modules/prova/factory/prova_factory.ts +++ b/src/modules/prova/factory/prova_factory.ts @@ -4,7 +4,7 @@ import { FrenteRepository } from 'src/modules/frente/frente.repository'; import { QuestaoRepository } from 'src/modules/questao/questao.repository'; import { SimuladoRepository } from 'src/modules/simulado/simulado.repository'; import { SimuladoService } from 'src/modules/simulado/simulado.service'; -import { TipoSimuladoRepository } from 'src/modules/tipo-simulado/tipo-simulado.repository'; +import { CategoriaRepository } from 'src/modules/categoria/categoria.repository'; import { ProvaRepository } from '../prova.repository'; import { EnemService } from '../services/enem_service'; import { Enem2010_2017Factory } from './enem_2010_2016_factory'; @@ -14,7 +14,7 @@ import { ExameName, IProvaFactory } from './types'; @Injectable() export class ProvaFactory { constructor( - private readonly tipoSimuladoRepository: TipoSimuladoRepository, + private readonly categoriaRepository: CategoriaRepository, private readonly questaoRepository: QuestaoRepository, private readonly provaRepository: ProvaRepository, private readonly frenteRepository: FrenteRepository, @@ -26,25 +26,23 @@ export class ProvaFactory { public getFactory(exame: Exame, ano: number): IProvaFactory { if (exame.nome === ExameName.ENEM && ano > 2016) { return new Enem2017PlusFactory( - this.tipoSimuladoRepository, + this.categoriaRepository, this.questaoRepository, this.provaRepository, this.frenteRepository, this.simuladoService, this.simuladoRepository, this.enemService, - exame, ); } else if (exame.nome === ExameName.ENEM && ano > 2009 && ano <= 2016) { return new Enem2010_2017Factory( - this.tipoSimuladoRepository, + this.categoriaRepository, this.questaoRepository, this.provaRepository, this.frenteRepository, this.simuladoService, this.simuladoRepository, this.enemService, - exame, ); } throw new Error('Factory não encontrada'); diff --git a/src/modules/prova/prova.module.ts b/src/modules/prova/prova.module.ts index c5d003c..ed3b137 100644 --- a/src/modules/prova/prova.module.ts +++ b/src/modules/prova/prova.module.ts @@ -6,10 +6,10 @@ import { MateriaModule } from '../materia/materia.module'; import { QuestaoModule } from '../questao/questao.module'; import { SimuladoModule } from '../simulado/simulado.module'; import { - TipoSimulado, - TipoSimuladoSchema, -} from '../tipo-simulado/schemas/tipo-simulado.schema'; -import { TipoSimuladoRepository } from '../tipo-simulado/tipo-simulado.repository'; + Categoria, + CategoriaSchema, +} from '../categoria/schemas/categoria.schema'; +import { CategoriaRepository } from '../categoria/categoria.repository'; import { ProvaFactory } from './factory/prova_factory'; import { ProvaController } from './prova.controller'; import { ProvaRepository } from './prova.repository'; @@ -22,7 +22,7 @@ import { EnemService } from './services/enem_service'; imports: [ MongooseModule.forFeature([ { name: Prova.name, schema: ProvaSchema }, - { name: TipoSimulado.name, schema: TipoSimuladoSchema }, + { name: Categoria.name, schema: CategoriaSchema }, ]), ExameModule, QuestaoModule, @@ -35,7 +35,7 @@ import { EnemService } from './services/enem_service'; ProvaService, ProvaRepository, ProvaExistValidator, - TipoSimuladoRepository, + CategoriaRepository, ProvaFactory, EnemService, ], diff --git a/src/modules/prova/prova.repository.ts b/src/modules/prova/prova.repository.ts index 6b60418..664499d 100644 --- a/src/modules/prova/prova.repository.ts +++ b/src/modules/prova/prova.repository.ts @@ -2,6 +2,8 @@ import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { BaseRepository } from 'src/shared/base/base.repository'; +import { GetAllOutput } from 'src/shared/base/interfaces/get-all.output'; +import { GetAllWhereInput } from 'src/shared/base/interfaces/get-all.input'; import { Status } from '../questao/enums/status.enum'; import { Questao } from '../questao/questao.schema'; import { Prova } from './prova.schema'; @@ -17,16 +19,21 @@ export class ProvaRepository extends BaseRepository { } async getProvaWithQuestion(id: string): Promise { - return await this.model.findById(id).populate(['questoes', 'exame']).exec(); + return await this.model + .findById(id) + .populate('questoes') + .populate({ path: 'categoria', populate: 'exame' }) + .exec(); } async getById(id: string): Promise { return await this.model .findById(id) - .populate(['exame', 'simulados', 'tipo', 'questoes']) + .populate(['simulados', 'questoes']) + .populate({ path: 'categoria', populate: 'exame' }) .populate({ path: 'simulados', - populate: ['tipo', 'questoes'], + populate: ['categoria', 'questoes'], }); } @@ -53,13 +60,29 @@ export class ProvaRepository extends BaseRepository { } } + async getAll({ + page, + limit, + where, + }: GetAllWhereInput): Promise> { + const data = await this.model + .find() + .populate({ path: 'categoria', populate: { path: 'exame' } }) + .populate('questoes') + .skip((page - 1) * limit) + .limit(limit ?? Infinity) + .where({ ...where }); + const totalItems = await this.model.where({ ...where }).countDocuments(); + return { data, page, limit, totalItems }; + } + async getAllPopulated(): Promise { return await this.model .find() - .populate(['exame', 'tipo']) + .populate({ path: 'categoria', populate: 'exame' }) .populate({ path: 'simulados', - populate: ['tipo', 'questoes'], + populate: ['categoria', 'questoes'], }) .populate('questoes') .exec(); diff --git a/src/modules/prova/prova.schema.ts b/src/modules/prova/prova.schema.ts index de12318..12442fe 100644 --- a/src/modules/prova/prova.schema.ts +++ b/src/modules/prova/prova.schema.ts @@ -1,20 +1,18 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import mongoose, { Types } from 'mongoose'; import { BaseSchema } from 'src/shared/base/base.schema'; -import { Exame } from '../exame/exame.schema'; import { Questao } from '../questao/questao.schema'; import { Simulado } from '../simulado/schemas/simulado.schema'; -import { TipoSimulado } from '../tipo-simulado/schemas/tipo-simulado.schema'; +import { Categoria } from '../categoria/schemas/categoria.schema'; import { CreateProvaDTOInput } from './dtos/create.dto.input'; import { Edicao } from './enums/edicao.enum'; @Schema({ timestamps: true, versionKey: false }) export class Prova extends BaseSchema { - constructor(item: CreateProvaDTOInput, exame: Exame, tipo: TipoSimulado) { + constructor(item: CreateProvaDTOInput, categoria: Categoria) { super(); this.edicao = item.edicao; - this.exame = exame; - this.tipo = tipo; + this.categoria = categoria; this.ano = item.ano; this.filename = item.filename; this.gabarito = item.gabarito; @@ -22,6 +20,7 @@ export class Prova extends BaseSchema { this.simulados = []; this.questoes = []; } + @Prop({ enum: Edicao }) public edicao: Edicao; @@ -31,11 +30,8 @@ export class Prova extends BaseSchema { @Prop() public ano: number; - @Prop({ ref: Exame.name, type: Types.ObjectId }) - public exame: Exame; - - @Prop({ ref: TipoSimulado.name, type: Types.ObjectId }) - public tipo: TipoSimulado; + @Prop({ ref: Categoria.name, type: Types.ObjectId }) + public categoria: Categoria; @Prop({ type: [{ ref: 'Simulado', type: mongoose.Schema.Types.ObjectId }], diff --git a/src/modules/prova/prova.service.ts b/src/modules/prova/prova.service.ts index 6692373..8a1c748 100644 --- a/src/modules/prova/prova.service.ts +++ b/src/modules/prova/prova.service.ts @@ -1,7 +1,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { GetAllInput } from 'src/shared/base/interfaces/get-all.input'; import { GetAllOutput } from 'src/shared/base/interfaces/get-all.output'; -import { ExameRepository } from '../exame/exame.repository'; +import { CategoriaRepository } from '../categoria/categoria.repository'; import { FrenteRepository } from '../frente/frente.repository'; import { Frente } from '../frente/frente.schema'; import { EnemArea } from '../questao/enums/enem-area.enum'; @@ -31,15 +31,18 @@ export class ProvaService { constructor( private readonly provaFactory: ProvaFactory, private readonly repository: ProvaRepository, - private readonly exameRepository: ExameRepository, + private readonly categoriaRepository: CategoriaRepository, private readonly simuladoRepository: SimuladoRepository, private readonly questaoRepository: QuestaoRepository, private readonly frenteRepository: FrenteRepository, ) {} public async create(item: CreateProvaDTOInput): Promise { - const exame = await this.exameRepository.getById(item.exame); - const factory = this.provaFactory.getFactory(exame, item.ano); + const categoria = await this.categoriaRepository.getById(item.categoria); + const factory = this.provaFactory.getFactory( + categoria.exame as any, + item.ano, + ); try { const prova = await factory.createProva(item); await factory.createSimulados(prova); @@ -50,7 +53,8 @@ export class ProvaService { edicao: result.edicao, aplicacao: result.aplicacao, ano: result.ano, - exame: result.exame.nome, + categoria: result.categoria.nome, + exame: result.categoria.exame.nome, nome: result.nome, totalQuestao: result.totalQuestao, totalQuestaoCadastradas: result.questoes.length, @@ -81,7 +85,8 @@ export class ProvaService { edicao: prova.edicao, aplicacao: prova.aplicacao, ano: prova.ano, - exame: prova.exame.nome, + categoria: prova.categoria.nome, + exame: prova.categoria.exame.nome, nome: prova.nome, totalQuestao: prova.totalQuestao, gabarito: prova.gabarito, @@ -111,7 +116,8 @@ export class ProvaService { if (!containsQuestion) return; const hasRequiredCount = - simulado.questoes.length === simulado.tipo.quantidadeTotalQuestao; + simulado.questoes.length === + simulado.categoria.quantidadeTotalQuestao; const allApproved = simulado.questoes.every( (q) => q.status === Status.Approved || q._id.toString() === questionId, @@ -147,7 +153,8 @@ export class ProvaService { // Recalcula bloqueado considerando a questão sendo rejeitada const hasRequiredCount = - simulado.questoes.length === simulado.tipo.quantidadeTotalQuestao; + simulado.questoes.length === + simulado.categoria.quantidadeTotalQuestao; const allApproved = hasRequiredCount && simulado.questoes.every((q) => { @@ -164,7 +171,10 @@ export class ProvaService { public async getMissingNumbers(id: string) { const prova = await this.repository.getProvaWithQuestion(id); - const factory = this.provaFactory.getFactory(prova.exame, prova.ano); + const factory = this.provaFactory.getFactory( + prova.categoria.exame as any, + prova.ano, + ); return factory.getMissingNumbers(prova); } @@ -331,8 +341,9 @@ export class ProvaService { } // C2: Recalcular bloqueado - const hasRequiredCount = simulado.tipo - ? newSimQuestoes.length === simulado.tipo.quantidadeTotalQuestao + const hasRequiredCount = simulado.categoria + ? newSimQuestoes.length === + simulado.categoria.quantidadeTotalQuestao : false; const allApproved = newSimQuestoes.length > 0 && @@ -345,9 +356,9 @@ export class ProvaService { issue: 'incorrect_value', oldValue: simulado.bloqueado, newValue: shouldBeBlocked, - detail: !simulado.tipo - ? 'tipo e nulo, definido bloqueado=true' - : `questoes: ${newSimQuestoes.length}/${simulado.tipo.quantidadeTotalQuestao}, todasAprovadas: ${allApproved}`, + detail: !simulado.categoria + ? 'categoria e nula, definido bloqueado=true' + : `questoes: ${newSimQuestoes.length}/${simulado.categoria.quantidadeTotalQuestao}, todasAprovadas: ${allApproved}`, }); } @@ -406,7 +417,9 @@ export class ProvaService { frenteEspanhol: Frente, ): Questao[] { const nome = simulado.nome; - const tipoNomeAno = prova.tipo ? `${prova.tipo.nome} ${prova.ano}` : ''; + const tipoNomeAno = prova.categoria + ? `${prova.categoria.nome} ${prova.ano}` + : ''; return questoesDaProva.filter((q) => { const frente1Id = q.frente1?._id?.toString() || ''; @@ -482,7 +495,8 @@ export class ProvaService { edicao: prova.edicao, aplicacao: prova.aplicacao, ano: prova.ano, - exame: prova.exame.nome, + categoria: prova.categoria.nome, + exame: prova.categoria.exame.nome, nome: prova.nome, totalQuestao: prova.totalQuestao, totalQuestaoCadastradas: prova.questoes.length, diff --git a/src/modules/prova/services/enem_service.ts b/src/modules/prova/services/enem_service.ts index 50aae7a..6171e08 100644 --- a/src/modules/prova/services/enem_service.ts +++ b/src/modules/prova/services/enem_service.ts @@ -5,7 +5,7 @@ import { UpdateDTOInput } from 'src/modules/questao/dtos/update.dto.input'; import { EnemArea } from 'src/modules/questao/enums/enem-area.enum'; import { SimuladoRepository } from 'src/modules/simulado/simulado.repository'; import { Simulado } from 'src/modules/simulado/schemas/simulado.schema'; -import { TipoSimuladoRepository } from 'src/modules/tipo-simulado/tipo-simulado.repository'; +import { CategoriaRepository } from 'src/modules/categoria/categoria.repository'; import { ProvaRepository } from '../prova.repository'; import { Prova } from '../prova.schema'; @@ -13,51 +13,55 @@ import { Prova } from '../prova.schema'; export class EnemService { constructor( private readonly simuladoRepository: SimuladoRepository, - private readonly tipoSimuladoRepository: TipoSimuladoRepository, + private readonly categoriaRepository: CategoriaRepository, private readonly provarepository: ProvaRepository, ) {} public async createSimuladoIdiomatica(prova: Prova) { - const mainName = `${prova.tipo.nome} ${prova.ano}`; + const mainName = `${prova.categoria.nome} ${prova.ano}`; + const exameId = prova.categoria.exame._id.toString(); await this.createSimuladoLinguagem(prova); prova.simulados.push( await this.simuladoRepository.create({ nome: `${mainName} Inglês`, - tipo: prova.tipo, + categoria: prova.categoria, questoes: [], - descricao: `${prova.exame.nome} Inglês`, + descricao: `${prova.categoria.exame.nome} Inglês`, }), ); prova.simulados.push( await this.simuladoRepository.create({ nome: `${mainName} Espanhol`, - tipo: prova.tipo, + categoria: prova.categoria, questoes: [], - descricao: `${prova.exame.nome} Espanhol`, + descricao: `${prova.categoria.exame.nome} Espanhol`, }), ); } public async createSimuladoLinguagem(prova: Prova) { - const mainName = `${prova.tipo.nome} ${prova.ano}`; + const mainName = `${prova.categoria.nome} ${prova.ano}`; + const exameId = prova.categoria.exame._id.toString(); prova.simulados.push( - await this.createSimuladoArea(mainName, EnemArea.Linguagens, 'Inglês'), + await this.createSimuladoArea(mainName, EnemArea.Linguagens, exameId, 'Inglês'), ); prova.simulados.push( - await this.createSimuladoArea(mainName, EnemArea.Linguagens, 'Espanhol'), + await this.createSimuladoArea(mainName, EnemArea.Linguagens, exameId, 'Espanhol'), ); } public async createSimuladoArea( defaultName: string, nomeTipo: string, + exameId: string, complemento?: string, ) { const simuladoArea = new Simulado(); - const tipo = await this.tipoSimuladoRepository.getByFilter({ + const categoria = await this.categoriaRepository.getByFilter({ nome: nomeTipo, + exame: exameId, }); - simuladoArea.tipo = tipo; + simuladoArea.categoria = categoria; simuladoArea.nome = `${defaultName} ${nomeTipo}`; if (complemento) { simuladoArea.nome = `${simuladoArea.nome} ${complemento}`; diff --git a/src/modules/questao/questao.module.ts b/src/modules/questao/questao.module.ts index 270888e..9269e71 100644 --- a/src/modules/questao/questao.module.ts +++ b/src/modules/questao/questao.module.ts @@ -15,10 +15,10 @@ import { Simulado, SimuladoSchema } from '../simulado/schemas/simulado.schema'; import { SimuladoRepository } from '../simulado/simulado.repository'; import { SimuladoService } from '../simulado/simulado.service'; import { - TipoSimulado, - TipoSimuladoSchema, -} from '../tipo-simulado/schemas/tipo-simulado.schema'; -import { TipoSimuladoRepository } from '../tipo-simulado/tipo-simulado.repository'; + Categoria, + CategoriaSchema, +} from '../categoria/schemas/categoria.schema'; +import { CategoriaRepository } from '../categoria/categoria.repository'; import { QuestaoController } from './questao.controller'; import { QuestaoRepository } from './questao.repository'; import { Questao, QuestaoSchema } from './questao.schema'; @@ -29,7 +29,7 @@ import { QuestaoService } from './questao.service'; MongooseModule.forFeature([ { name: Questao.name, schema: QuestaoSchema }, { name: Prova.name, schema: ProvaSchema }, - { name: TipoSimulado.name, schema: TipoSimuladoSchema }, + { name: Categoria.name, schema: CategoriaSchema }, { name: Simulado.name, schema: SimuladoSchema }, { name: Frente.name, schema: FrenteSchema }, ]), @@ -45,7 +45,7 @@ import { QuestaoService } from './questao.service'; QuestaoRepository, ProvaRepository, ProvaService, - TipoSimuladoRepository, + CategoriaRepository, SimuladoService, SimuladoRepository, ProvaFactory, diff --git a/src/modules/questao/questao.service.spec.ts b/src/modules/questao/questao.service.spec.ts index e785002..04c8460 100644 --- a/src/modules/questao/questao.service.spec.ts +++ b/src/modules/questao/questao.service.spec.ts @@ -14,7 +14,7 @@ const mockProvaFactory = { }; const mockProvaRepository = { - getById: jest.fn().mockResolvedValue({ _id: 'prova-id', exame: {}, ano: 2023 }), + getById: jest.fn().mockResolvedValue({ _id: 'prova-id', categoria: { exame: {} }, ano: 2023 }), }; function makeService(): QuestaoService { @@ -45,7 +45,7 @@ describe('QuestaoService.create', () => { jest.clearAllMocks(); mockProvaRepository.getById.mockResolvedValue({ _id: 'prova-id', - exame: {}, + categoria: { exame: {} }, ano: 2023, }); mockProvaFactory.getFactory.mockReturnValue(mockFactory); diff --git a/src/modules/questao/questao.service.ts b/src/modules/questao/questao.service.ts index 9a94c1f..cd55845 100644 --- a/src/modules/questao/questao.service.ts +++ b/src/modules/questao/questao.service.ts @@ -17,8 +17,6 @@ import { ProvaFactory } from '../prova/factory/prova_factory'; import { ProvaRepository } from '../prova/prova.repository'; import { ProvaService } from '../prova/prova.service'; import { SimuladoService } from '../simulado/simulado.service'; -import { Regra } from '../tipo-simulado/schemas/regra.schemas'; -import { TipoSimulado } from '../tipo-simulado/schemas/tipo-simulado.schema'; import { CreateQuestaoDTOInput } from './dtos/create.dto.input'; import { QuestaoAllDTO } from './dtos/questao.all.dto.output'; import { QuestaoDTOInput } from './dtos/questao.dto.input'; @@ -49,7 +47,7 @@ export class QuestaoService { public async create(item: CreateQuestaoDTOInput): Promise { const prova = await this.provaRepository.getById(item.prova); - const factory = this.provaFactory.getFactory(prova.exame, prova.ano); + const factory = this.provaFactory.getFactory(prova.categoria.exame as any, prova.ano); if (item.numero == null || await factory.verifyNumberProva(prova._id, item.numero)) { return await factory.createQuestion(item); } @@ -160,23 +158,6 @@ export class QuestaoService { } } - public async GeyManyQuestao(tipo: TipoSimulado): Promise { - let questoes: Questao[] = []; - await Promise.all( - tipo.regras.map(async (regra) => { - questoes = questoes.concat(await this.getQuestaoByRegras(regra)); - }), - ); - - if (tipo.quantidadeTotalQuestao > questoes.length) { - questoes = questoes.concat( - await this.getQuestoes(tipo.quantidadeTotalQuestao - questoes.length), - ); - } - await this.repository.IncrementaSimulado(questoes.map((q) => q._id)); - return questoes; - } - public async getInfos() { const param: GetAllInput = { page: 1, @@ -242,7 +223,7 @@ export class QuestaoService { throw new HttpException('Prova não informada', HttpStatus.BAD_REQUEST); } const prova = await this.provaRepository.getById(question.prova); - const factory = this.provaFactory.getFactory(prova.exame, prova.ano); + const factory = this.provaFactory.getFactory(prova.categoria.exame as any, prova.ano); try { await factory.updateQuestion(question); } catch (error: any) { @@ -363,11 +344,6 @@ export class QuestaoService { } } - private async getQuestaoByRegras(regra: Regra) { - const regras = this.MontaFiltro(regra); - return await this.getQuestoesByFiltro(regras, regra.quantidade as number); - } - private async getQuestoes(amount: number) { return await this.getQuestoesByFiltro({}, amount); } @@ -388,14 +364,6 @@ export class QuestaoService { return questoes; } - private MontaFiltro(regra: Regra) { - const regras: { [key: string]: any } = {}; - regras['materia'] = regra.materia._id; - if (regra.frente) regras['frente1'] = regra.frente._id; - if (regra.ano) regras['ano'] = regra.ano; - return regras; - } - private generateFrentesCombinations(text: string) { const combinations = []; if (text) { diff --git a/src/modules/simulado/dtos/create.dto.input.ts b/src/modules/simulado/dtos/create.dto.input.ts index 0205db2..cd8c3e0 100644 --- a/src/modules/simulado/dtos/create.dto.input.ts +++ b/src/modules/simulado/dtos/create.dto.input.ts @@ -1,10 +1,10 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsString } from 'class-validator'; -import { TipoSimuladoExist } from 'src/modules/tipo-simulado/validator/tipo-simulado-exist.validator'; +import { CategoriaExist } from 'src/modules/categoria/validator/categoria-exist.validator'; export class CreateSimuladoDTOInput { @ApiProperty() @IsString() - @TipoSimuladoExist({ message: 'exame não existe' }) + @CategoriaExist({ message: 'exame não existe' }) tipoId: string; } diff --git a/src/modules/simulado/dtos/simulado-answer.dto.output.ts b/src/modules/simulado/dtos/simulado-answer.dto.output.ts index c5f68a8..293cacf 100644 --- a/src/modules/simulado/dtos/simulado-answer.dto.output.ts +++ b/src/modules/simulado/dtos/simulado-answer.dto.output.ts @@ -6,7 +6,7 @@ export class SimuladoAnswerDTOOutput { _id: string; @ApiProperty() - tipo: string; + categoria: string; @ApiProperty({ type: QuestaoAnswerDTOOutput, isArray: true }) questoes: QuestaoAnswerDTOOutput[]; diff --git a/src/modules/simulado/schemas/simulado.schema.ts b/src/modules/simulado/schemas/simulado.schema.ts index 67660b5..c4ed65b 100644 --- a/src/modules/simulado/schemas/simulado.schema.ts +++ b/src/modules/simulado/schemas/simulado.schema.ts @@ -3,7 +3,7 @@ import { ApiProperty } from '@nestjs/swagger'; import mongoose, { Types } from 'mongoose'; import { BaseSchema } from 'src/shared/base/base.schema'; import { Questao } from '../../questao/questao.schema'; -import { TipoSimulado } from '../../tipo-simulado/schemas/tipo-simulado.schema'; +import { Categoria } from '../../categoria/schemas/categoria.schema'; @Schema({ timestamps: true, versionKey: false }) export class Simulado extends BaseSchema { @@ -15,9 +15,9 @@ export class Simulado extends BaseSchema { @ApiProperty() descricao: string; - @Prop({ ref: TipoSimulado.name, type: Types.ObjectId }) + @Prop({ ref: Categoria.name, type: Types.ObjectId }) @ApiProperty() - tipo: TipoSimulado; + categoria: Categoria; @Prop({ type: [{ ref: 'Questao', type: mongoose.Schema.Types.ObjectId }], diff --git a/src/modules/simulado/simulado.module.ts b/src/modules/simulado/simulado.module.ts index 2ee3eef..c5f91e8 100644 --- a/src/modules/simulado/simulado.module.ts +++ b/src/modules/simulado/simulado.module.ts @@ -8,7 +8,7 @@ import { MateriaModule } from '../materia/materia.module'; import { ProvaRepository } from '../prova/prova.repository'; import { Prova, ProvaSchema } from '../prova/prova.schema'; import { QuestaoModule } from '../questao/questao.module'; -import { TipoSimuladoModule } from '../tipo-simulado/tipo-simulado.module'; +import { CategoriaModule } from '../categoria/categoria.module'; import { Simulado, SimuladoSchema } from './schemas/simulado.schema'; import { AnswerProcessorService } from './answer-processor.service'; import { SimuladoController } from './simulado.controller'; @@ -23,7 +23,7 @@ import { SimuladoService } from './simulado.service'; ]), QueueModule, QuestaoModule, - TipoSimuladoModule, + CategoriaModule, ExameModule, FrenteModule, MateriaModule, diff --git a/src/modules/simulado/simulado.repository.ts b/src/modules/simulado/simulado.repository.ts index 6e1e60c..03336b5 100644 --- a/src/modules/simulado/simulado.repository.ts +++ b/src/modules/simulado/simulado.repository.ts @@ -15,7 +15,7 @@ export class SimuladoRepository extends BaseRepository { async getById(id: string): Promise { return await this.model .findById(id) - .populate('tipo') + .populate('categoria') .populate({ path: 'questoes', populate: ['frente1', 'materia'], @@ -50,7 +50,7 @@ export class SimuladoRepository extends BaseRepository { }: GetAllInput): Promise> { const data = await this.model .find() - .populate(['tipo']) + .populate(['categoria']) .skip((page - 1) * limit) .limit(limit); const totalItems = await this.model.countDocuments(); @@ -72,10 +72,10 @@ export class SimuladoRepository extends BaseRepository { }); } - async getAvailable(tipo: string) { + async getAvailable(categoriaId: string) { return await this.model .find() - .where({ tipo, bloqueado: false }) + .where({ categoria: categoriaId, bloqueado: false }) .select(['nome', '_id']); } diff --git a/src/modules/simulado/simulado.service.ts b/src/modules/simulado/simulado.service.ts index 69c2b58..7337ef0 100644 --- a/src/modules/simulado/simulado.service.ts +++ b/src/modules/simulado/simulado.service.ts @@ -15,7 +15,7 @@ import { MateriaRepository } from '../materia/materia.repository'; import { Status } from '../questao/enums/status.enum'; import { QuestaoRepository } from '../questao/questao.repository'; import { Questao } from '../questao/questao.schema'; -import { TipoSimuladoRepository } from '../tipo-simulado/tipo-simulado.repository'; +import { CategoriaRepository } from '../categoria/categoria.repository'; import { AnswerSimuladoDto } from './dtos/answer-simulado.dto.input'; import { AvailableSimuladoDTOoutput } from './dtos/available-simulado.dto.output'; import { SimuladoAnswerDTOOutput } from './dtos/simulado-answer.dto.output'; @@ -28,7 +28,7 @@ export class SimuladoService { constructor( private readonly simuladoRepository: SimuladoRepository, private readonly questoesRepository: QuestaoRepository, - private readonly tipoSimuladoRepository: TipoSimuladoRepository, + private readonly categoriaRepository: CategoriaRepository, private readonly historicoRepository: HistoricoRepository, private readonly materiaRepository: MateriaRepository, private readonly queueProducer: QueueProducer, @@ -68,7 +68,7 @@ export class SimuladoService { // Verifica se o simulador atingiu a quantidade total de questões const atingiuQuantidadeTotal = - sml.questoes.length === sml.tipo.quantidadeTotalQuestao; + sml.questoes.length === sml.categoria.quantidadeTotalQuestao; // Verifica se todas as questões estão aprovadas const todasAprovadas = sml.questoes.every( (q) => q.status === Status.Approved, @@ -191,7 +191,7 @@ export class SimuladoService { _id: simulado._id, nome: simulado.nome, descricao: simulado.descricao, - tipo: simulado.tipo._id, + categoria: simulado.categoria._id, questoes: simulado.questoes.map((q) => ({ _id: q._id, enemArea: q.enemArea, @@ -204,17 +204,17 @@ export class SimuladoService { prova: q.prova, })), inicio: inicio, - duracao: simulado.tipo.duracao, + duracao: simulado.categoria.duracao, }; } public async getAvailable( nomeTipo: string, ): Promise { - const tipo = await this.tipoSimuladoRepository.getByFilter({ + const categoria = await this.categoriaRepository.getByFilter({ nome: nomeTipo, }); - return await this.simuladoRepository.getAvailable(tipo._id); + return await this.simuladoRepository.getAvailable(categoria._id); } private async criaAproveitamento( diff --git a/src/modules/tipo-simulado/dtos/create.dto.input.ts b/src/modules/tipo-simulado/dtos/create.dto.input.ts deleted file mode 100644 index 0ede89c..0000000 --- a/src/modules/tipo-simulado/dtos/create.dto.input.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { RegraDTO } from './regra.dto'; -import { - IsArray, - IsNumber, - IsOptional, - IsString, - ValidateNested, -} from 'class-validator'; -import { TipoSimuladoUnique } from '../validator/tipo-simulado-unique.validator'; -import { Type } from 'class-transformer'; - -export class CreateTipoSimuladoDTOInput { - @ApiProperty() - @IsString() - @TipoSimuladoUnique({ message: 'nome tipo simulado já existe' }) - public nome: string; - - @ApiProperty() - @IsNumber() - public duracao: number; - - @ApiProperty() - @IsNumber() - public quantidadeTotalQuestao: number; - - @ApiProperty({ type: [RegraDTO], required: false }) - @IsArray() - @IsOptional() - @ValidateNested({ each: true }) - @Type(() => RegraDTO) - public regras: RegraDTO[]; -} diff --git a/src/modules/tipo-simulado/dtos/regra.dto.ts b/src/modules/tipo-simulado/dtos/regra.dto.ts deleted file mode 100644 index 0158e37..0000000 --- a/src/modules/tipo-simulado/dtos/regra.dto.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNumber, IsOptional, IsString } from 'class-validator'; -import { FrenteExist } from 'src/modules/frente/validator/frente-exist.validator'; -import { MateriaExist } from 'src/modules/materia/validator/materia-exist.validator'; - -export class RegraDTO { - @ApiProperty() - @IsString() - @MateriaExist({ message: 'materia não existe' }) - materia: string; - - @ApiProperty() - @IsNumber() - quantidade: number; - - @ApiProperty({ required: false }) - @IsString() - @IsOptional() - @FrenteExist({ message: 'frente não existe' }) - frente?: string; - - @ApiProperty({ required: false }) - @IsNumber() - @IsOptional() - ano?: number; -} diff --git a/src/modules/tipo-simulado/schemas/regra.schemas.ts b/src/modules/tipo-simulado/schemas/regra.schemas.ts deleted file mode 100644 index 8ca6765..0000000 --- a/src/modules/tipo-simulado/schemas/regra.schemas.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Prop } from '@nestjs/mongoose'; -import { ApiProperty } from '@nestjs/swagger'; -import { Types } from 'mongoose'; -import { Frente } from 'src/modules/frente/frente.schema'; -import { Materia } from 'src/modules/materia/materia.schema'; - -export class Regra { - @Prop({ - type: Types.ObjectId, - ref: Materia.name, - required: true, - }) - @ApiProperty() - materia: Materia; - - @Prop({ required: true }) - @ApiProperty() - quantidade: number; - - @Prop({ - type: Types.ObjectId, - ref: Frente.name, - required: false, - }) - @ApiProperty() - frente?: Frente; - - @Prop({ required: false }) - @ApiProperty() - ano?: number; -} diff --git a/src/modules/tipo-simulado/schemas/tipo-simulado.schema.ts b/src/modules/tipo-simulado/schemas/tipo-simulado.schema.ts deleted file mode 100644 index 8c8fbf2..0000000 --- a/src/modules/tipo-simulado/schemas/tipo-simulado.schema.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; -import { BaseSchema } from 'src/shared/base/base.schema'; -import { Regra } from './regra.schemas'; -import { ApiProperty } from '@nestjs/swagger'; - -@Schema({ timestamps: true, versionKey: false }) -export class TipoSimulado extends BaseSchema { - @Prop({ unique: true }) - @ApiProperty() - public nome: string; - - @Prop() - @ApiProperty() - public duracao: number; - - @Prop() - @ApiProperty() - public quantidadeTotalQuestao: number; - - @Prop([Regra]) - @ApiProperty({ isArray: true, type: Regra }) - public regras: Regra[]; -} - -export const TipoSimuladoSchema = SchemaFactory.createForClass(TipoSimulado); diff --git a/src/modules/tipo-simulado/tipo-simulado.module.ts b/src/modules/tipo-simulado/tipo-simulado.module.ts deleted file mode 100644 index 8bf6c11..0000000 --- a/src/modules/tipo-simulado/tipo-simulado.module.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TipoSimuladoService } from './tipo-simulado.service'; -import { TipoSimuladoController } from './tipo-simulado.controller'; -import { TipoSimuladoRepository } from './tipo-simulado.repository'; -import { MongooseModule } from '@nestjs/mongoose'; -import { TipoSimuladoSchema } from './schemas/tipo-simulado.schema'; -import { TipoSimuladoUniqueValidator } from './validator/tipo-simulado-unique.validator'; -import { MateriaExistValidator } from '../materia/validator/materia-exist.validator'; -import { FrenteExistValidator } from '../frente/validator/frente-exist.validator'; -import { FrenteModule } from '../frente/frente.module'; -import { MateriaModule } from '../materia/materia.module'; - -@Module({ - imports: [ - MongooseModule.forFeature([ - { name: 'TipoSimulado', schema: TipoSimuladoSchema }, - ]), - FrenteModule, - MateriaModule, - ], - controllers: [TipoSimuladoController], - providers: [ - TipoSimuladoService, - TipoSimuladoRepository, - TipoSimuladoUniqueValidator, - MateriaExistValidator, - FrenteExistValidator, - ], - exports: [TipoSimuladoRepository], -}) -export class TipoSimuladoModule {} diff --git a/src/modules/tipo-simulado/tipo-simulado.repository.ts b/src/modules/tipo-simulado/tipo-simulado.repository.ts deleted file mode 100644 index 87e3458..0000000 --- a/src/modules/tipo-simulado/tipo-simulado.repository.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { BaseRepository } from 'src/shared/base/base.repository'; -import { TipoSimulado } from './schemas/tipo-simulado.schema'; -import { InjectModel } from '@nestjs/mongoose'; -import { Model } from 'mongoose'; - -export class TipoSimuladoRepository extends BaseRepository { - constructor(@InjectModel(TipoSimulado.name) model: Model) { - super(model); - } - - override async getById(id: string): Promise { - return await this.model - .findById(id) - .populate({ - path: 'regras', - populate: { - path: 'materia', - model: 'Materia', - }, - }) - .populate({ - path: 'regras', - populate: { - path: 'frente', - model: 'Frente', - }, - }); - } -} diff --git a/src/modules/tipo-simulado/tipo-simulado.service.ts b/src/modules/tipo-simulado/tipo-simulado.service.ts deleted file mode 100644 index 01633c1..0000000 --- a/src/modules/tipo-simulado/tipo-simulado.service.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { GetAllInput } from 'src/shared/base/interfaces/get-all.input'; -import { GetAllOutput } from 'src/shared/base/interfaces/get-all.output'; -import { CreateTipoSimuladoDTOInput } from './dtos/create.dto.input'; -import { TipoSimulado } from './schemas/tipo-simulado.schema'; -import { TipoSimuladoRepository } from './tipo-simulado.repository'; - -@Injectable() -export class TipoSimuladoService { - constructor(private readonly repository: TipoSimuladoRepository) {} - - public async add(item: CreateTipoSimuladoDTOInput): Promise { - const tipo = Object.assign(new TipoSimulado(), item); - - return await this.repository.create(tipo); - } - - public async getById(id: string): Promise { - return await this.repository.getById(id); - } - - public async getAll(param: GetAllInput): Promise> { - return await this.repository.getAll(param); - } - - public async delete(id: string): Promise { - await this.repository.delete(id); - } -} diff --git a/test/categoria-schema-extension.e2e-spec.ts b/test/categoria-schema-extension.e2e-spec.ts new file mode 100644 index 0000000..955ae47 --- /dev/null +++ b/test/categoria-schema-extension.e2e-spec.ts @@ -0,0 +1,111 @@ +// ms-simulado/test/categoria-schema-extension.e2e-spec.ts + +// Requires a real MongoDB instance (process.env.MONGODB or mongodb://localhost:27017/ms-simulado-test). +import { INestApplication, ValidationPipe } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { getModelToken } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +// eslint-disable-next-line @typescript-eslint/no-require-imports +const request = require('supertest'); +import { useContainer } from 'class-validator'; +import { AppModule } from '../src/app.module'; +import { Categoria } from '../src/modules/categoria/schemas/categoria.schema'; + +describe('Card 02 — Categoria schema extension + ExameSeedService (e2e)', () => { + let app: INestApplication; + let categoriaModel: Model; + let exameId: string; + let createdCategoriaId: string; + + beforeAll(async () => { + process.env.NODE_ENV = 'test'; + process.env.MONGODB = + process.env.MONGODB ?? 'mongodb://localhost:27017/ms-simulado-test'; + process.env.QUEUE_DRIVER = 'memory'; + + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + app.useGlobalPipes( + new ValidationPipe({ + transform: true, + whitelist: true, + forbidNonWhitelisted: false, + }), + ); + await app.init(); + useContainer(app.select(AppModule), { fallbackOnErrors: true }); + + categoriaModel = moduleFixture.get>( + getModelToken(Categoria.name), + ); + + // ExameSeedService roda no OnModuleInit durante app.init() + const exameModel = moduleFixture.get>(getModelToken('Exame')); + const personalizado = await exameModel.findOne({ nome: 'Personalizado' }); + exameId = personalizado?._id.toString(); + }); + + afterAll(async () => { + if (createdCategoriaId) { + await categoriaModel.deleteOne({ _id: createdCategoriaId }); + } + await app.close(); + }); + + it('1. ExameSeedService cria Exame "Personalizado" no init do app', () => { + expect(exameId).toBeDefined(); + }); + + it('2. Exame "Personalizado" não é duplicado em reinicializações', async () => { + const exameModel = app.get>(getModelToken('Exame')); + const count = await exameModel.countDocuments({ nome: 'Personalizado' }); + expect(count).toBe(1); + }); + + it('3. POST /v1/categoria aceita os 4 campos novos e retorna 201', async () => { + const res = await request(app.getHttpServer()) + .post('/v1/categoria') + .send({ + nome: 'Categoria E2E Card02', + duracao: 120, + exame: exameId, + custom: false, + selecionavel: true, + descricao: 'descricao de teste', + }); + + expect(res.status).toBe(201); + expect(res.body.custom).toBe(false); + expect(res.body.selecionavel).toBe(true); + expect(res.body.descricao).toBe('descricao de teste'); + expect(res.body.quantidadeTotalQuestao).toBeNull(); + createdCategoriaId = res.body._id; + }); + + it('4. GET /v1/categoria retorna exame como objeto populado (não ObjectId)', async () => { + const res = await request(app.getHttpServer()) + .get('/v1/categoria') + .query({ page: 1, limit: 100 }); + + expect(res.status).toBe(200); + const created = res.body.data.find( + (c: any) => c._id === createdCategoriaId, + ); + expect(created).toBeDefined(); + expect(typeof created.exame).toBe('object'); + expect(created.exame.nome).toBe('Personalizado'); + }); + + it('5. GET /v1/categoria/:id retorna exame como objeto populado', async () => { + const res = await request(app.getHttpServer()).get( + `/v1/categoria/${createdCategoriaId}`, + ); + + expect(res.status).toBe(200); + expect(typeof res.body.exame).toBe('object'); + expect(res.body.exame.nome).toBe('Personalizado'); + }); +});