import { AppException } from '@/common/exceptions/app.exception'; import { QuestionTransition } from '@/modules/questions/entities/question-transition.entity'; import { QuestionTransitionType } from '@/modules/questions/enums/question-transition-type.enum'; import { NumericCondition } from '@/modules/questions/interfaces/numeric-condition.interface'; import { SingleChoiceCondition } from '@/modules/questions/interfaces/single-choice-condition.interface'; import { TransitionValidateOptions } from '@/modules/questions/interfaces/transition-validate-options.interface'; import { HttpStatus, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import type { Repository } from 'typeorm'; @Injectable() export class CheckTransitionDBProvider { constructor( /** * Inject Transitions Repository */ @InjectRepository(QuestionTransition) private readonly transitionsRepository: Repository, ) {} public async validate( options: TransitionValidateOptions, transitionId?: number, ) { await this.validateSingleStart(options, transitionId); await this.validateSingleEnd(options, transitionId); await this.validateDefaultTransition(options, transitionId); await this.validateDuplicateCondition(options, transitionId); } private async validateSingleStart( options: TransitionValidateOptions, transitionId?: number, ) { if (options.type !== QuestionTransitionType.START) return; const qb = this.transitionsRepository .createQueryBuilder('t') .where('t.testId = :testId', { testId: options.test.id, }) .andWhere('t.type = :type', { type: QuestionTransitionType.START, }); if (transitionId) { qb.andWhere('t.id != :transitionId', { transitionId, }); } if (await qb.getExists()) { throw new AppException( 'questions.errors.questionTransition.startAlreadyExists', HttpStatus.CONFLICT, ); } } private async validateSingleEnd( options: TransitionValidateOptions, transitionId?: number, ) { if (options.type !== QuestionTransitionType.END) return; const qb = this.transitionsRepository .createQueryBuilder('t') .where('t.fromQuestionId = :fromQuestionId', { fromQuestionId: options.fromQuestion?.id, }) .andWhere('t.type = :type', { type: QuestionTransitionType.END, }); if (transitionId) { qb.andWhere('t.id != :transitionId', { transitionId, }); } if (await qb.getExists()) { throw new AppException( 'questions.errors.questionTransition.endAlreadyExists', HttpStatus.CONFLICT, ); } } private async validateDefaultTransition( options: TransitionValidateOptions, transitionId?: number, ) { if (options.type !== QuestionTransitionType.DEFAULT) return; const qb = this.transitionsRepository .createQueryBuilder('t') .where('t.fromQuestionId = :fromQuestionId', { fromQuestionId: options.fromQuestion?.id, }) .andWhere('t.type = :type', { type: QuestionTransitionType.DEFAULT, }); if (transitionId) { qb.andWhere('t.id != :transitionId', { transitionId, }); } if (await qb.getExists()) { throw new AppException( 'questions.errors.questionTransition.defaultAlreadyExists', HttpStatus.CONFLICT, ); } } private async validateDuplicateCondition( options: TransitionValidateOptions, transitionId?: number, ) { if ( options.type === QuestionTransitionType.DEFAULT || options.type === QuestionTransitionType.START || options.type === QuestionTransitionType.END ) { return; } const qb = this.transitionsRepository .createQueryBuilder('t') .where('t.fromQuestionId = :fromQuestionId', { fromQuestionId: options.fromQuestion?.id, }) .andWhere('t.type = :type', { type: options.type, }); if (options.type === QuestionTransitionType.CHOICE) { qb.andWhere(`t.conditionValue ->> 'choiceId' = :choiceId`, { choiceId: String( (options.conditionValue as SingleChoiceCondition).choiceId, ), }); } if (options.type === QuestionTransitionType.NUMBER_RANGE) { qb.andWhere(`t.conditionValue ->> 'min' = :min`, { min: String((options.conditionValue as NumericCondition).min), }).andWhere(`t.conditionValue ->> 'max' = :max`, { max: String((options.conditionValue as NumericCondition).max), }); } if (transitionId) { qb.andWhere('t.id != :transitionId', { transitionId, }); } if (await qb.getExists()) { throw new AppException( 'questions.errors.questionTransition.duplicateTransition', HttpStatus.CONFLICT, ); } } }