feat: add question transition constraints and command provider
This commit is contained in:
@@ -1,2 +1,10 @@
|
||||
export const UNIQUE_TEST_ORDER_IDX = 'UNQ_TEST_ORDER_IDX';
|
||||
export const UNIQUE_QUESTION_ORDER_IDX = 'UNQ_QUESTION_ORDER_IDX';
|
||||
|
||||
export const UNIQUE_QUESTION_NUMBER_RANGE = 'UNQ_QUESTION_NUMBER_RANGE';
|
||||
export const UNIQUE_QUESTION_CHOICE = 'UNQ_QUESTION_CHOICE';
|
||||
export const UNIQUE_QUESTION_DEFAULT = 'UNQ_QUESTION_DEFAULT';
|
||||
export const UNIQUE_QUESTION_END = 'UNQ_QUESTION_END';
|
||||
export const UNIQUE_TEST_START = 'UNQ_TEST_START';
|
||||
export const EX_QUESTION_NUMBER_RANGE_OVERLAP =
|
||||
'EX_QUESTION_NUMBER_RANGE_OVERLAP';
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import { ConflictException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { QueryFailedError, Repository } from 'typeorm';
|
||||
import { QuestionTransition } from '../../entities/question-transition.entity';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { QuestionTransitionValidatorProvider } from './validations/question-transition-validator.provider';
|
||||
import { CreateQuestionTransitionDTO } from '../../dtos/create-question-transition.dto';
|
||||
import {
|
||||
EX_QUESTION_NUMBER_RANGE_OVERLAP,
|
||||
UNIQUE_QUESTION_CHOICE,
|
||||
UNIQUE_QUESTION_DEFAULT,
|
||||
UNIQUE_QUESTION_END,
|
||||
UNIQUE_QUESTION_NUMBER_RANGE,
|
||||
UNIQUE_TEST_START,
|
||||
} from '../../constants';
|
||||
import { UpdateQuestionTransitionDTO } from '../../dtos/update-question-transition.dto';
|
||||
import { AppTimeoutException } from '@/common/exceptions/app-timeout.exception';
|
||||
import { AppException } from '@/common/exceptions/app.exception';
|
||||
|
||||
@Injectable()
|
||||
export class QuestionTransitionCommandProvider {
|
||||
constructor(
|
||||
/**
|
||||
* Inject QuestionTransitions Repository
|
||||
*/
|
||||
@InjectRepository(QuestionTransition)
|
||||
private readonly transitionsRepository: Repository<QuestionTransition>,
|
||||
|
||||
/**
|
||||
* Inject QuestionTransitionValidator Provider
|
||||
*/
|
||||
private readonly questionTransitionValidatorProvider: QuestionTransitionValidatorProvider,
|
||||
) {}
|
||||
|
||||
public async createOne(
|
||||
createQuestionTransitionDto: CreateQuestionTransitionDTO,
|
||||
) {
|
||||
const { test, fromQuestion, toQuestion, type, conditionValue } =
|
||||
await this.questionTransitionValidatorProvider.validate(
|
||||
createQuestionTransitionDto,
|
||||
);
|
||||
|
||||
const newTransition = this.transitionsRepository.create({
|
||||
test,
|
||||
fromQuestion,
|
||||
toQuestion,
|
||||
type,
|
||||
condition: conditionValue,
|
||||
});
|
||||
|
||||
try {
|
||||
await this.transitionsRepository.save(newTransition);
|
||||
} catch (err) {
|
||||
this.handleDBExceptions(err);
|
||||
}
|
||||
}
|
||||
|
||||
public async updateOne(
|
||||
id: number,
|
||||
updateQuestionTransitionDto: UpdateQuestionTransitionDTO,
|
||||
) {
|
||||
const { transition, test, fromQuestion, toQuestion, type, conditionValue } =
|
||||
await this.questionTransitionValidatorProvider.validate(
|
||||
updateQuestionTransitionDto,
|
||||
id,
|
||||
);
|
||||
|
||||
if (!transition) {
|
||||
throw new AppException(
|
||||
'common.errors.entityNotFound',
|
||||
HttpStatus.NOT_FOUND,
|
||||
{ entity: '$t(questions.fields.questionTransition)' },
|
||||
);
|
||||
}
|
||||
|
||||
transition.test = test;
|
||||
transition.fromQuestion = fromQuestion;
|
||||
transition.toQuestion = toQuestion;
|
||||
transition.type = type;
|
||||
transition.condition = conditionValue;
|
||||
|
||||
try {
|
||||
await this.transitionsRepository.save(transition);
|
||||
} catch (err) {
|
||||
this.handleDBExceptions(err);
|
||||
}
|
||||
}
|
||||
|
||||
private handleDBExceptions(err: unknown) {
|
||||
if (err instanceof QueryFailedError) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
||||
const constraint = (err as any).constraint;
|
||||
|
||||
switch (constraint) {
|
||||
case UNIQUE_TEST_START:
|
||||
throw new ConflictException(
|
||||
'questions.errors.questionTransition.startAlreadyExists',
|
||||
);
|
||||
|
||||
case UNIQUE_QUESTION_END:
|
||||
throw new ConflictException(
|
||||
'questions.errors.questionTransition.endAlreadyExists',
|
||||
);
|
||||
|
||||
case UNIQUE_QUESTION_DEFAULT:
|
||||
throw new ConflictException(
|
||||
'questions.errors.questionTransition.defaultAlreadyExists',
|
||||
);
|
||||
|
||||
case UNIQUE_QUESTION_CHOICE:
|
||||
case UNIQUE_QUESTION_NUMBER_RANGE:
|
||||
throw new ConflictException(
|
||||
'questions.errors.questionTransition.duplicateTransition',
|
||||
);
|
||||
|
||||
case EX_QUESTION_NUMBER_RANGE_OVERLAP:
|
||||
throw new ConflictException(
|
||||
'questions.errors.questionTransition.numberRangeOverlap',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
throw new AppTimeoutException(err);
|
||||
}
|
||||
|
||||
public async deleteOne(id: number) {
|
||||
let transition: QuestionTransition | null = null;
|
||||
|
||||
try {
|
||||
transition = await this.transitionsRepository.findOneBy({ id });
|
||||
} catch (err) {
|
||||
throw new AppTimeoutException(err);
|
||||
}
|
||||
|
||||
if (!transition) {
|
||||
throw new AppException(
|
||||
'common.errors.entityNotFound',
|
||||
HttpStatus.NOT_FOUND,
|
||||
{ entity: '$t(questions.fields.questionTransition)' },
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.transitionsRepository.delete(transition.id);
|
||||
} catch (err) {
|
||||
throw new AppTimeoutException(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
-11
@@ -14,6 +14,7 @@ import { QuestionsQueryProvider } from '../../questions-query.provider';
|
||||
import { TransitionValidateOptions } from '../../../interfaces/transition-validate-options.interface';
|
||||
import { QuestionTransitionTypeValidator } from '../../../validators/question-transition-type.validator';
|
||||
import { CheckTransitionDBProvider } from './check-transition-db.provider';
|
||||
import { AppTimeoutException } from '@/common/exceptions/app-timeout.exception';
|
||||
|
||||
@Injectable()
|
||||
export class QuestionTransitionValidatorProvider {
|
||||
@@ -50,15 +51,21 @@ export class QuestionTransitionValidatorProvider {
|
||||
let toQuestion: Question | null;
|
||||
let conditionValue: TransitionConditionType | undefined;
|
||||
|
||||
let transition: QuestionTransition | null = null;
|
||||
|
||||
if (transitionId !== undefined) {
|
||||
const transition = await this.transitionsRepository.findOne({
|
||||
where: { id: transitionId },
|
||||
relations: {
|
||||
test: true,
|
||||
fromQuestion: true,
|
||||
toQuestion: true,
|
||||
},
|
||||
});
|
||||
try {
|
||||
transition = await this.transitionsRepository.findOne({
|
||||
where: { id: transitionId },
|
||||
relations: {
|
||||
test: true,
|
||||
fromQuestion: true,
|
||||
toQuestion: true,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
throw new AppTimeoutException(err);
|
||||
}
|
||||
|
||||
if (!transition) {
|
||||
throw new AppException(
|
||||
@@ -75,7 +82,7 @@ export class QuestionTransitionValidatorProvider {
|
||||
|
||||
type = dto.type ?? transition.type;
|
||||
|
||||
if (dto.fromQuestion) {
|
||||
if (dto.fromQuestion !== undefined && dto.fromQuestion !== null) {
|
||||
fromQuestion = await this.questionsQueryProvider.findOneByIdForAdmin(
|
||||
dto.fromQuestion,
|
||||
);
|
||||
@@ -85,7 +92,7 @@ export class QuestionTransitionValidatorProvider {
|
||||
fromQuestion = transition.fromQuestion;
|
||||
}
|
||||
|
||||
if (dto.toQuestion) {
|
||||
if (dto.toQuestion !== undefined && dto.toQuestion !== null) {
|
||||
toQuestion = await this.questionsQueryProvider.findOneByIdForAdmin(
|
||||
dto.toQuestion,
|
||||
);
|
||||
@@ -138,7 +145,7 @@ export class QuestionTransitionValidatorProvider {
|
||||
|
||||
await this.checkTransitionDBProvider.validate(options, transitionId);
|
||||
|
||||
return options;
|
||||
return { ...options, transition };
|
||||
}
|
||||
|
||||
private validateQuestions(
|
||||
|
||||
Reference in New Issue
Block a user