feat: add choices entity admin crud
This commit is contained in:
@@ -2,6 +2,12 @@
|
|||||||
"errors": {
|
"errors": {
|
||||||
"internalServerError": "Internal Server Error",
|
"internalServerError": "Internal Server Error",
|
||||||
"tooManyRequests": "Too many requests! Try again after {minutes} min.",
|
"tooManyRequests": "Too many requests! Try again after {minutes} min.",
|
||||||
"requestTimeout": "Unable to process your request at the moment."
|
"requestTimeout": "Unable to process your request at the moment.",
|
||||||
|
"entityNotFound": "{entity} not found."
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"createSuccess": "{entity} created successfully.",
|
||||||
|
"updateSuccess": "Changes were made successfully.",
|
||||||
|
"deleteSuccess": "{entity} deleted successfully."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,17 @@
|
|||||||
"content": "Content",
|
"content": "Content",
|
||||||
"order": "Order",
|
"order": "Order",
|
||||||
"required": "Required",
|
"required": "Required",
|
||||||
"metadata": "Metadata"
|
"metadata": "Metadata",
|
||||||
|
"question": "Question",
|
||||||
|
"text": "Text",
|
||||||
|
"value": "Value",
|
||||||
|
"choice": "Choice"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"uniqueOrderConstraint": "Order value should be unique.",
|
"uniqueOrderConstraint": "Order value should be unique."
|
||||||
"questionNotFound": "Question not found."
|
|
||||||
},
|
},
|
||||||
"messages": {
|
"messages": {
|
||||||
"createSuccess": "Question Created Successfully."
|
"createSuccess": "Question Created Successfully.",
|
||||||
|
"choiceCreateSuccess": "Choice Created Successfully."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
"description": "Description",
|
"description": "Description",
|
||||||
"accessType": "Access Type",
|
"accessType": "Access Type",
|
||||||
"price": "Price",
|
"price": "Price",
|
||||||
"isActive": "Is Active"
|
"isActive": "Is Active",
|
||||||
|
"test": "Test"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"intPrice": "Price should be an integer.",
|
"intPrice": "Price should be an integer.",
|
||||||
"testNotFound": "Test not found.",
|
|
||||||
"priceAccessConstraint": "Test's price depends on access type of 'Paid'."
|
"priceAccessConstraint": "Test's price depends on access type of 'Paid'."
|
||||||
},
|
},
|
||||||
"messages": {
|
"messages": {
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
"errors": {
|
"errors": {
|
||||||
"internalServerError": "خطایی رخ داد",
|
"internalServerError": "خطایی رخ داد",
|
||||||
"tooManyRequests": "تعداد درخواست ها بیشتر از حد مجاز است! لطفا بعد از {minutes} دقیقه دوباره امتحان کنید.",
|
"tooManyRequests": "تعداد درخواست ها بیشتر از حد مجاز است! لطفا بعد از {minutes} دقیقه دوباره امتحان کنید.",
|
||||||
"requestTimeout": "امکان پردازش درخواست شما در این لحظه وجود ندارد."
|
"requestTimeout": "امکان پردازش درخواست شما در این لحظه وجود ندارد.",
|
||||||
|
"entityNotFound": "{entity} مورد نظر یافت نشد."
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"createSuccess": "{entity} با موفقیت ایجاد شد.",
|
||||||
|
"updateSuccess": "تغییرات با موفقیت اعمال شد.",
|
||||||
|
"deleteSuccess": "{entity} با موفقیت حذف شد."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,17 @@
|
|||||||
"content": "محتوا",
|
"content": "محتوا",
|
||||||
"order": "اولویت",
|
"order": "اولویت",
|
||||||
"required": "اجباری",
|
"required": "اجباری",
|
||||||
"metadata": "تنظیمات"
|
"metadata": "تنظیمات",
|
||||||
|
"question": "سوال",
|
||||||
|
"text": "متن",
|
||||||
|
"value": "مقدار",
|
||||||
|
"choice": "گزینه"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"uniqueOrderConstraint": "مقدار اولویت تکراری است.",
|
"uniqueOrderConstraint": "مقدار اولویت تکراری است."
|
||||||
"questionNotFound": "Question not found."
|
|
||||||
},
|
},
|
||||||
"messages": {
|
"messages": {
|
||||||
"createSuccess": "سوال با موفقیت ایجاد شد."
|
"createSuccess": "سوال با موفقیت ایجاد شد.",
|
||||||
|
"choiceCreateSuccess": "گزینه با موفقیت ایجاد شد."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
"description": "توضیحات",
|
"description": "توضیحات",
|
||||||
"accessType": "نوع دسترسی",
|
"accessType": "نوع دسترسی",
|
||||||
"price": "قیمت",
|
"price": "قیمت",
|
||||||
"isActive": "فعال بودن"
|
"isActive": "فعال بودن",
|
||||||
|
"test": "تست"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"intPrice": "قیمت باید عدد صحیح باشد.",
|
"intPrice": "قیمت باید عدد صحیح باشد.",
|
||||||
"testNotFound": "تست مورد نظر یافت نشد.",
|
|
||||||
"priceAccessConstraint": "قیمت تست به نوع دسترسی 'پرداخت' وابسته است."
|
"priceAccessConstraint": "قیمت تست به نوع دسترسی 'پرداخت' وابسته است."
|
||||||
},
|
},
|
||||||
"messages": {
|
"messages": {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { AdminAuthController } from './auth/admin-auth.controller';
|
|||||||
import { AdminAuthService } from './auth/providers/admin-auth.service';
|
import { AdminAuthService } from './auth/providers/admin-auth.service';
|
||||||
import { QuestionsModule } from '../questions/questions.module';
|
import { QuestionsModule } from '../questions/questions.module';
|
||||||
import { AdminQuestionsController } from './questions/admin-questions.controller';
|
import { AdminQuestionsController } from './questions/admin-questions.controller';
|
||||||
|
import { AdminChoicesController } from './choices/admin-choices.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -21,6 +22,7 @@ import { AdminQuestionsController } from './questions/admin-questions.controller
|
|||||||
AdminTestsController,
|
AdminTestsController,
|
||||||
AdminAuthController,
|
AdminAuthController,
|
||||||
AdminQuestionsController,
|
AdminQuestionsController,
|
||||||
|
AdminChoicesController,
|
||||||
],
|
],
|
||||||
providers: [AdminAuthService],
|
providers: [AdminAuthService],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { Role } from '@/common/enums/roles.enum';
|
||||||
|
import { Roles } from '@/modules/auth/decorators/roles.decorator';
|
||||||
|
import { CreateChoiceDTO } from '@/modules/questions/dtos/create-choice.dto';
|
||||||
|
import { FindChoicesDTO } from '@/modules/questions/dtos/find-choices.dto';
|
||||||
|
import { UpdateChoiceDTO } from '@/modules/questions/dtos/update-choice.dto';
|
||||||
|
import { ChoicesService } from '@/modules/questions/providers/choices.service';
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
HttpCode,
|
||||||
|
HttpStatus,
|
||||||
|
Param,
|
||||||
|
ParseIntPipe,
|
||||||
|
Patch,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
|
||||||
|
@Controller('admin/choices')
|
||||||
|
@Roles(Role.SUPERUSER, Role.ADMIN)
|
||||||
|
export class AdminChoicesController {
|
||||||
|
constructor(
|
||||||
|
/**
|
||||||
|
* Inject Choices Service
|
||||||
|
*/
|
||||||
|
private readonly choicesService: ChoicesService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
public async createChoice(@Body() createChoiceDto: CreateChoiceDTO) {
|
||||||
|
return await this.choicesService.createOne(createChoiceDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
public async updateChoice(
|
||||||
|
@Param('id', ParseIntPipe) id: number,
|
||||||
|
@Body() updateChoiceDto: UpdateChoiceDTO,
|
||||||
|
) {
|
||||||
|
return await this.choicesService.updateOne(id, updateChoiceDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
|
public async deleteChoice(@Param('id', ParseIntPipe) id: number) {
|
||||||
|
return await this.choicesService.deleteOne(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
public async getAllChoices(@Query() query: FindChoicesDTO) {
|
||||||
|
return await this.choicesService.findAllForAdmin(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
public async getChoiceById(@Param('id', ParseIntPipe) id: number) {
|
||||||
|
return await this.choicesService.findOneForAdmin(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
GET http://localhost:3000/admin/choices/
|
||||||
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJwaG9uZSI6IjA5MzMzMDI2MzYzIiwiaWF0IjoxNzgxNjgxNzAzLCJleHAiOjE3ODE2ODUzMDMsImF1ZCI6ImxvY2FsaG9zdCIsImlzcyI6ImxvY2FsaG9zdCJ9.WD14d4CqEiM1EkNFf9ky1B-i32RmoVGbYYXaYJs4UC0
|
||||||
|
|
||||||
|
GET http://localhost:3000/admin/choices/1
|
||||||
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJwaG9uZSI6IjA5MzMzMDI2MzYzIiwiaWF0IjoxNzgxNjgxNzAzLCJleHAiOjE3ODE2ODUzMDMsImF1ZCI6ImxvY2FsaG9zdCIsImlzcyI6ImxvY2FsaG9zdCJ9.WD14d4CqEiM1EkNFf9ky1B-i32RmoVGbYYXaYJs4UC0
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
DELETE http://localhost:3000/admin/choices/6
|
||||||
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJwaG9uZSI6IjA5MzMzMDI2MzYzIiwiaWF0IjoxNzgxNTg0MjY1LCJleHAiOjE3ODE1ODc4NjUsImF1ZCI6ImxvY2FsaG9zdCIsImlzcyI6ImxvY2FsaG9zdCJ9.lV1U9uXQTAJmCamZfCDcDpITfIZs4QlVd9Fcjsr0BrA
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
PATCH http://localhost:3000/admin/choices/1
|
||||||
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJwaG9uZSI6IjA5MzMzMDI2MzYzIiwiaWF0IjoxNzgxNTMwNTI1LCJleHAiOjE3ODE1MzQxMjUsImF1ZCI6ImxvY2FsaG9zdCIsImlzcyI6ImxvY2FsaG9zdCJ9.C_Ol-dkX7mouofKqbc-VxtP4qF6WiCPmlpKXuq85DiQ
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"question": 4
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
POST http://localhost:3000/admin/choices
|
||||||
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJwaG9uZSI6IjA5MzMzMDI2MzYzIiwiaWF0IjoxNzgxNTg0MjY1LCJleHAiOjE3ODE1ODc4NjUsImF1ZCI6ImxvY2FsaG9zdCIsImlzcyI6ImxvY2FsaG9zdCJ9.lV1U9uXQTAJmCamZfCDcDpITfIZs4QlVd9Fcjsr0BrA
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"question": 4,
|
||||||
|
"text": "Option 2",
|
||||||
|
"value": 2,
|
||||||
|
"order": 2
|
||||||
|
}
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
PATCH http://localhost:3000/admin/questions/1
|
PATCH http://localhost:3000/admin/questions/4
|
||||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJwaG9uZSI6IjA5MzMzMDI2MzYzIiwiaWF0IjoxNzgxNDIyMjkxLCJleHAiOjE3ODE0MjU4OTEsImF1ZCI6ImxvY2FsaG9zdCIsImlzcyI6ImxvY2FsaG9zdCJ9.djDjd0v7UwfITA4Bh-PC5G830o9wu2CgRKRenwuJJ4E
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJwaG9uZSI6IjA5MzMzMDI2MzYzIiwiaWF0IjoxNzgxNTg0MjY1LCJleHAiOjE3ODE1ODc4NjUsImF1ZCI6ImxvY2FsaG9zdCIsImlzcyI6ImxvY2FsaG9zdCJ9.lV1U9uXQTAJmCamZfCDcDpITfIZs4QlVd9Fcjsr0BrA
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"metadata": {
|
"test": 4
|
||||||
"allowOther": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
DELETE http://localhost:3000/admin/tests/1
|
DELETE http://localhost:3000/admin/tests/100
|
||||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJwaG9uZSI6IjA5MzMzMDI2MzYzIiwiaWF0IjoxNzgwNzQ5MTcwLCJleHAiOjE3ODA3NTI3NzAsImF1ZCI6ImxvY2FsaG9zdCIsImlzcyI6ImxvY2FsaG9zdCJ9.apRhtVZgFMFcyutZGO7uzckscch9Jw42s0RCSZZv4K0
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJwaG9uZSI6IjA5MzMzMDI2MzYzIiwiaWF0IjoxNzgxNTI2OTMyLCJleHAiOjE3ODE1MzA1MzIsImF1ZCI6ImxvY2FsaG9zdCIsImlzcyI6ImxvY2FsaG9zdCJ9.RVtpegax_yXI1PL91WvNWiIDDLTaJcPUUWU6Be9Hg1k
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
export const UNIQUE_TEST_ORDER_IDX = 'UNQ_TEST_ORDER_IDX';
|
export const UNIQUE_TEST_ORDER_IDX = 'UNQ_TEST_ORDER_IDX';
|
||||||
|
export const UNIQUE_QUESTION_ORDER_IDX = 'UNQ_QUESTION_ORDER_IDX';
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { IsNotEmpty, IsNumber, IsPositive, MaxLength } from 'class-validator';
|
||||||
|
import { i18nValidationMessage as t } from 'nestjs-i18n';
|
||||||
|
|
||||||
|
export class CreateChoiceDTO {
|
||||||
|
@IsPositive({
|
||||||
|
message: t('validation.wrongField', {
|
||||||
|
field: '$t(questions.fields.question)',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
@IsNotEmpty({
|
||||||
|
message: t('validation.requiredField', {
|
||||||
|
field: '$t(questions.fields.question)',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
question!: number;
|
||||||
|
|
||||||
|
@IsNotEmpty({
|
||||||
|
message: t('validation.requiredField', {
|
||||||
|
field: '$t(questions.fields.text)',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
@MaxLength(255, {
|
||||||
|
message: t('validation.maxLengthField', {
|
||||||
|
field: '$t(users.fields.text)',
|
||||||
|
max: 255,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
text!: string;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@IsNotEmpty({
|
||||||
|
message: t('validation.requiredField', {
|
||||||
|
field: '$t(questions.fields.value)',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
value!: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@IsNotEmpty({
|
||||||
|
message: t('validation.requiredField', {
|
||||||
|
field: '$t(questions.fields.order)',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
order!: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { PaginationQueryDTO } from '@/common/dtos/pagination-query.dto';
|
||||||
|
import { PartialType } from '@nestjs/swagger';
|
||||||
|
import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator';
|
||||||
|
import { ChoiceSortField } from '../enums/choice-sort-field.enum';
|
||||||
|
|
||||||
|
export class FindChoicesDTO extends PartialType(PaginationQueryDTO) {
|
||||||
|
@IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
id?: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
question?: number;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
text?: string;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
value?: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
choiceOrder?: number;
|
||||||
|
|
||||||
|
@IsEnum(ChoiceSortField)
|
||||||
|
@IsOptional()
|
||||||
|
sortBy?: ChoiceSortField;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { PartialType } from '@nestjs/swagger';
|
||||||
|
import { CreateChoiceDTO } from './create-choice.dto';
|
||||||
|
|
||||||
|
export class UpdateChoiceDTO extends PartialType(CreateChoiceDTO) {}
|
||||||
@@ -1,7 +1,15 @@
|
|||||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
import {
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
Index,
|
||||||
|
ManyToOne,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
} from 'typeorm';
|
||||||
import { Question } from './question.entity';
|
import { Question } from './question.entity';
|
||||||
|
import { UNIQUE_QUESTION_ORDER_IDX } from '../constants';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
|
@Index(UNIQUE_QUESTION_ORDER_IDX, ['question', 'order'], { unique: true })
|
||||||
export class Choice {
|
export class Choice {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id!: number;
|
id!: number;
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export enum ChoiceSortField {
|
||||||
|
ID = 'id',
|
||||||
|
QUESTION = 'question',
|
||||||
|
TEXT = 'text',
|
||||||
|
ORDER = 'order',
|
||||||
|
VALUE = 'value',
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { FindOptionsWhere, ILike } from 'typeorm';
|
||||||
|
import { FindChoicesDTO } from '../dtos/find-choices.dto';
|
||||||
|
import { Choice } from '../entities/choice.entity';
|
||||||
|
|
||||||
|
export const filterChoices = (query: FindChoicesDTO) => {
|
||||||
|
const where: FindOptionsWhere<Choice> = {};
|
||||||
|
|
||||||
|
if (query.id) {
|
||||||
|
where.id = query.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.text) {
|
||||||
|
where.text = ILike(`%${query.text}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.choiceOrder !== undefined) {
|
||||||
|
where.order = query.choiceOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.value !== undefined) {
|
||||||
|
where.value = query.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return where;
|
||||||
|
};
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
import { ConflictException, HttpStatus, Injectable } from '@nestjs/common';
|
||||||
|
import { CreateChoiceDTO } from '../dtos/create-choice.dto';
|
||||||
|
import { QueryFailedError, Repository } from 'typeorm';
|
||||||
|
import { Choice } from '../entities/choice.entity';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Question } from '../entities/question.entity';
|
||||||
|
import { UNIQUE_QUESTION_ORDER_IDX } from '../constants';
|
||||||
|
import { AppTimeoutException } from '@/common/exceptions/app-timeout.exception';
|
||||||
|
import { AppResponse } from '@/common/responses';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
import { AppException } from '@/common/exceptions/app.exception';
|
||||||
|
import { UpdateChoiceDTO } from '../dtos/update-choice.dto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ChoicesCommandProvider {
|
||||||
|
constructor(
|
||||||
|
/**
|
||||||
|
* Inject Choices Repository
|
||||||
|
*/
|
||||||
|
@InjectRepository(Choice)
|
||||||
|
private readonly choicesRepository: Repository<Choice>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject Questions Repository
|
||||||
|
*/
|
||||||
|
@InjectRepository(Question)
|
||||||
|
private readonly questionsRepository: Repository<Question>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject I18n Service
|
||||||
|
*/
|
||||||
|
private readonly i18nService: I18nService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async createOne(createChoiceDto: CreateChoiceDTO) {
|
||||||
|
const question = await this.questionsRepository.findOneBy({
|
||||||
|
id: createChoiceDto.question,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!question) {
|
||||||
|
throw new AppException(
|
||||||
|
'common.errors.entityNotFound',
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
{ entity: '$t(questions.fields.question)' },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newChoice = this.choicesRepository.create({
|
||||||
|
...createChoiceDto,
|
||||||
|
question,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.choicesRepository.save(newChoice);
|
||||||
|
return new AppResponse(
|
||||||
|
newChoice,
|
||||||
|
this.i18nService.translate('common.messages.createSuccess', {
|
||||||
|
args: { entity: '$t(questions.fields.choice)' },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (
|
||||||
|
err instanceof QueryFailedError &&
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
|
(err as any).constraint === UNIQUE_QUESTION_ORDER_IDX
|
||||||
|
) {
|
||||||
|
throw new ConflictException('questions.errors.uniqueOrderConstraint');
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateOne(id: number, updateChoiceDto: UpdateChoiceDTO) {
|
||||||
|
const choice = await this.choicesRepository.findOneBy({ id });
|
||||||
|
|
||||||
|
if (!choice) {
|
||||||
|
throw new AppException(
|
||||||
|
'common.errors.entityNotFound',
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
{
|
||||||
|
entity: '$t(questions.fields.choice)',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateChoiceDto.question) {
|
||||||
|
const question = await this.questionsRepository.findOneBy({
|
||||||
|
id: updateChoiceDto.question,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!question) {
|
||||||
|
throw new AppException(
|
||||||
|
'common.errors.entityNotFound',
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
{
|
||||||
|
entity: '$t(questions.fields.question)',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(choice, updateChoiceDto);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updatedChoice = await this.choicesRepository.save(choice);
|
||||||
|
return new AppResponse(
|
||||||
|
updatedChoice,
|
||||||
|
this.i18nService.translate('common.messages.updateSuccess'),
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (
|
||||||
|
err instanceof QueryFailedError &&
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
|
(err as any).constraint === UNIQUE_QUESTION_ORDER_IDX
|
||||||
|
) {
|
||||||
|
throw new ConflictException('questions.errors.uniqueOrderConstraint');
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteOne(id: number) {
|
||||||
|
let choice: Choice | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
choice = await this.choicesRepository.findOneBy({ id });
|
||||||
|
} catch (err) {
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!choice) {
|
||||||
|
throw new AppException(
|
||||||
|
'common.errors.entityNotFound',
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
{
|
||||||
|
entity: '$t(questions.fields.choice)',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.choicesRepository.delete(choice.id);
|
||||||
|
} catch (err) {
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import Utils from '@/common/utils';
|
||||||
|
import { AppTimeoutException } from '@/common/exceptions/app-timeout.exception';
|
||||||
|
import { PaginatedResponse } from '@/common/responses';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Question } from '../entities/question.entity';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { Choice } from '../entities/choice.entity';
|
||||||
|
import { FindChoicesDTO } from '../dtos/find-choices.dto';
|
||||||
|
import { filterChoices } from '../helpers/filter-choices.helper';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ChoicesQueryProvider {
|
||||||
|
constructor(
|
||||||
|
/**
|
||||||
|
* Inject Choices Repository
|
||||||
|
*/
|
||||||
|
@InjectRepository(Choice)
|
||||||
|
private readonly choicesRepository: Repository<Choice>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject Questions Repository
|
||||||
|
*/
|
||||||
|
@InjectRepository(Question)
|
||||||
|
private readonly questionsRepository: Repository<Question>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async findAllForAdmin(query: FindChoicesDTO) {
|
||||||
|
const filters = filterChoices(query);
|
||||||
|
const { limit, page, skip, take } =
|
||||||
|
Utils.PaginationUtils.getPaginationOptions(query);
|
||||||
|
|
||||||
|
let selectedQuestion: Question | null = null;
|
||||||
|
|
||||||
|
if (query.question) {
|
||||||
|
try {
|
||||||
|
selectedQuestion = await this.questionsRepository.findOneBy({
|
||||||
|
id: query.question,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [choices, total] = await this.choicesRepository.findAndCount({
|
||||||
|
where: {
|
||||||
|
...filters,
|
||||||
|
...(selectedQuestion && {
|
||||||
|
question: {
|
||||||
|
id: selectedQuestion.id,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
loadRelationIds: true,
|
||||||
|
skip,
|
||||||
|
take,
|
||||||
|
order: query.sortBy
|
||||||
|
? {
|
||||||
|
[query.sortBy]: query.order ?? 'ASC',
|
||||||
|
}
|
||||||
|
: { id: 'DESC' },
|
||||||
|
});
|
||||||
|
|
||||||
|
return new PaginatedResponse(choices, {
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
total,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findOneForAdmin(id: number) {
|
||||||
|
try {
|
||||||
|
return await this.choicesRepository.findOne({
|
||||||
|
where: { id },
|
||||||
|
relations: { question: true },
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ChoicesCommandProvider } from './choices-command.provider';
|
||||||
|
import { CreateChoiceDTO } from '../dtos/create-choice.dto';
|
||||||
|
import { UpdateChoiceDTO } from '../dtos/update-choice.dto';
|
||||||
|
import { FindChoicesDTO } from '../dtos/find-choices.dto';
|
||||||
|
import { ChoicesQueryProvider } from './choices-query.provider';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ChoicesService {
|
||||||
|
constructor(
|
||||||
|
/**
|
||||||
|
* Inject Choices Command Provider
|
||||||
|
*/
|
||||||
|
private readonly choicesCommnadProvider: ChoicesCommandProvider,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject Choices Query Provider
|
||||||
|
*/
|
||||||
|
private readonly choicesQueryProvider: ChoicesQueryProvider,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async createOne(createChoiceDto: CreateChoiceDTO) {
|
||||||
|
return await this.choicesCommnadProvider.createOne(createChoiceDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateOne(id: number, updateChoiceDto: UpdateChoiceDTO) {
|
||||||
|
return await this.choicesCommnadProvider.updateOne(id, updateChoiceDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteOne(id: number) {
|
||||||
|
return await this.choicesCommnadProvider.deleteOne(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findAllForAdmin(query: FindChoicesDTO) {
|
||||||
|
return await this.choicesQueryProvider.findAllForAdmin(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findOneForAdmin(id: number) {
|
||||||
|
return await this.choicesQueryProvider.findOneForAdmin(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import { ConflictException, HttpStatus, Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Question } from '../entities/question.entity';
|
||||||
|
import { QueryFailedError, Repository } from 'typeorm';
|
||||||
|
import { TestsService } from '@/modules/tests/providers/tests.service';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
import { CreateQuestionDTO } from '../dtos/create-question.dto';
|
||||||
|
import { AppResponse } from '@/common/responses';
|
||||||
|
import { UNIQUE_TEST_ORDER_IDX } from '../constants';
|
||||||
|
import { AppTimeoutException } from '@/common/exceptions/app-timeout.exception';
|
||||||
|
import { UpdateQuestionDTO } from '../dtos/update-question.dto';
|
||||||
|
import { validateMetadata } from '../helpers/validate-metadata.helper';
|
||||||
|
import { AppException } from '@/common/exceptions/app.exception';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class QuestionsCommandProvider {
|
||||||
|
constructor(
|
||||||
|
/**
|
||||||
|
* Inject Questions Repository
|
||||||
|
*/
|
||||||
|
@InjectRepository(Question)
|
||||||
|
private readonly questionsRepository: Repository<Question>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject Tests Service
|
||||||
|
*/
|
||||||
|
private readonly testsService: TestsService,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject I18n Service
|
||||||
|
*/
|
||||||
|
private readonly i18nService: I18nService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async createOne(createQuestionDto: CreateQuestionDTO) {
|
||||||
|
const test = await this.testsService.adminGetOne(createQuestionDto.test);
|
||||||
|
|
||||||
|
if (!test) {
|
||||||
|
throw new AppException(
|
||||||
|
'common.errors.entityNotFound',
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
{ entity: '$t(tests.fields.test)' },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newQuestion = this.questionsRepository.create({
|
||||||
|
...createQuestionDto,
|
||||||
|
test,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.questionsRepository.save(newQuestion);
|
||||||
|
return new AppResponse(
|
||||||
|
newQuestion,
|
||||||
|
this.i18nService.translate('common.messages.createSuccess', {
|
||||||
|
args: { entity: '$t(questions.fields.question)' },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (
|
||||||
|
err instanceof QueryFailedError &&
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
|
(err as any).constraint === UNIQUE_TEST_ORDER_IDX
|
||||||
|
) {
|
||||||
|
throw new ConflictException('questions.errors.uniqueOrderConstraint');
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateOne(id: number, updateQuestionDto: UpdateQuestionDTO) {
|
||||||
|
const question = await this.questionsRepository.findOneBy({ id });
|
||||||
|
|
||||||
|
if (!question) {
|
||||||
|
throw new AppException(
|
||||||
|
'common.errors.entityNotFound',
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
{ entity: '$t(questions.fields.question)' },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateQuestionDto.test) {
|
||||||
|
const test = await this.testsService.adminGetOne(updateQuestionDto.test);
|
||||||
|
|
||||||
|
if (!test) {
|
||||||
|
throw new AppException(
|
||||||
|
'common.errors.entityNotFound',
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
{ entity: '$t(tests.fields.test)' },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const type =
|
||||||
|
updateQuestionDto.type !== undefined
|
||||||
|
? updateQuestionDto.type
|
||||||
|
: question.type;
|
||||||
|
|
||||||
|
const metadata =
|
||||||
|
updateQuestionDto.metadata !== undefined
|
||||||
|
? updateQuestionDto.metadata
|
||||||
|
: question.metadata;
|
||||||
|
|
||||||
|
const errors = await validateMetadata(type, metadata);
|
||||||
|
|
||||||
|
if (errors && errors.length > 0) {
|
||||||
|
throw new AppException(
|
||||||
|
'validation.wrongFieldFormat',
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
{ field: '$t(questions.fields.metadata)' },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(question, updateQuestionDto);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await this.questionsRepository.save(question);
|
||||||
|
} catch (err) {
|
||||||
|
if (
|
||||||
|
err instanceof QueryFailedError &&
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
|
(err as any).constraint === UNIQUE_TEST_ORDER_IDX
|
||||||
|
) {
|
||||||
|
throw new ConflictException('questions.errors.uniqueOrderConstraint');
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteOne(id: number) {
|
||||||
|
let question: Question | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
question = await this.questionsRepository.findOneBy({ id });
|
||||||
|
} catch (err) {
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!question)
|
||||||
|
throw new AppException(
|
||||||
|
'common.errors.entityNotFound',
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
{ entity: '$t(questions.fields.question)' },
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.questionsRepository.delete(question.id);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { filterQuestions } from '../helpers/filter-questions.helper';
|
||||||
|
import Utils from '@/common/utils';
|
||||||
|
import { FindQuestionsDTO } from '../dtos/find-questions.dto';
|
||||||
|
import { Test } from '@nestjs/testing';
|
||||||
|
import { AppTimeoutException } from '@/common/exceptions/app-timeout.exception';
|
||||||
|
import { PaginatedResponse } from '@/common/responses';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Question } from '../entities/question.entity';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { TestsService } from '@/modules/tests/providers/tests.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class QuestionsQueryProvider {
|
||||||
|
constructor(
|
||||||
|
/**
|
||||||
|
* Inject Questions Repository
|
||||||
|
*/
|
||||||
|
@InjectRepository(Question)
|
||||||
|
private readonly questionsRepository: Repository<Question>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject Tests Service
|
||||||
|
*/
|
||||||
|
private readonly testsService: TestsService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async findAllForAdmin(query: FindQuestionsDTO) {
|
||||||
|
const filters = filterQuestions(query);
|
||||||
|
const { limit, page, skip, take } =
|
||||||
|
Utils.PaginationUtils.getPaginationOptions(query);
|
||||||
|
|
||||||
|
let selectedTest: Test | null = null;
|
||||||
|
|
||||||
|
if (query.test) {
|
||||||
|
try {
|
||||||
|
selectedTest = await this.testsService.adminGetOne(query.test);
|
||||||
|
} catch (err) {
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [questions, total] = await this.questionsRepository.findAndCount({
|
||||||
|
where: {
|
||||||
|
...filters,
|
||||||
|
...(selectedTest && {
|
||||||
|
test: selectedTest,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
loadRelationIds: true,
|
||||||
|
skip,
|
||||||
|
take,
|
||||||
|
order: query.sortBy
|
||||||
|
? {
|
||||||
|
[query.sortBy]: query.order ?? 'ASC',
|
||||||
|
}
|
||||||
|
: { createdAt: 'DESC' },
|
||||||
|
});
|
||||||
|
|
||||||
|
return new PaginatedResponse(questions, {
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
total,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findOneForAdmin(id: number) {
|
||||||
|
try {
|
||||||
|
return await this.questionsRepository.findOne({
|
||||||
|
where: { id },
|
||||||
|
relations: { test: true },
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,194 +1,41 @@
|
|||||||
import {
|
import { Injectable } from '@nestjs/common';
|
||||||
ConflictException,
|
|
||||||
HttpStatus,
|
|
||||||
Injectable,
|
|
||||||
NotFoundException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { QueryFailedError, Repository } from 'typeorm';
|
|
||||||
import { Question } from '../entities/question.entity';
|
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
import { CreateQuestionDTO } from '../dtos/create-question.dto';
|
import { CreateQuestionDTO } from '../dtos/create-question.dto';
|
||||||
import { TestsService } from '@/modules/tests/providers/tests.service';
|
|
||||||
import { AppTimeoutException } from '@/common/exceptions/app-timeout.exception';
|
|
||||||
import { AppResponse, PaginatedResponse } from '@/common/responses';
|
|
||||||
import { I18nService } from 'nestjs-i18n';
|
|
||||||
import { UNIQUE_TEST_ORDER_IDX } from '../constants';
|
|
||||||
import { FindQuestionsDTO } from '../dtos/find-questions.dto';
|
import { FindQuestionsDTO } from '../dtos/find-questions.dto';
|
||||||
import { filterQuestions } from '../helpers/filter-questions.helper';
|
|
||||||
import Utils from '@/common/utils';
|
|
||||||
import { Test } from '@/modules/tests/entities/test.entity';
|
|
||||||
import { UpdateQuestionDTO } from '../dtos/update-question.dto';
|
import { UpdateQuestionDTO } from '../dtos/update-question.dto';
|
||||||
import { validateMetadata } from '../helpers/validate-metadata.helper';
|
import { QuestionsCommandProvider } from './questions-command.provider';
|
||||||
import { AppException } from '@/common/exceptions/app.exception';
|
import { QuestionsQueryProvider } from './questions-query.provider';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QuestionsService {
|
export class QuestionsService {
|
||||||
constructor(
|
constructor(
|
||||||
/**
|
/**
|
||||||
* Inject Questions Repository
|
* Inject Questions Command Provider
|
||||||
*/
|
*/
|
||||||
@InjectRepository(Question)
|
private readonly questionsCommandProvider: QuestionsCommandProvider,
|
||||||
private readonly questionsRepository: Repository<Question>,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inject Tests Service
|
* Inject Questions Query Provider
|
||||||
*/
|
*/
|
||||||
private readonly testsService: TestsService,
|
private readonly questionsQueryProvider: QuestionsQueryProvider,
|
||||||
|
|
||||||
/**
|
|
||||||
* Inject I18n Service
|
|
||||||
*/
|
|
||||||
private readonly i18nService: I18nService,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async createOne(createQuestionDto: CreateQuestionDTO) {
|
public async createOne(createQuestionDto: CreateQuestionDTO) {
|
||||||
const test = await this.testsService.adminGetOne(createQuestionDto.test);
|
return await this.questionsCommandProvider.createOne(createQuestionDto);
|
||||||
|
|
||||||
if (!test) {
|
|
||||||
throw new NotFoundException('tests.errors.testNotFound');
|
|
||||||
}
|
|
||||||
|
|
||||||
const newQuestion = this.questionsRepository.create({
|
|
||||||
...createQuestionDto,
|
|
||||||
test,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.questionsRepository.save(newQuestion);
|
|
||||||
return new AppResponse(
|
|
||||||
newQuestion,
|
|
||||||
this.i18nService.translate('questions.messages.createSuccess'),
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
if (
|
|
||||||
err instanceof QueryFailedError &&
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
||||||
(err as any).constraint === UNIQUE_TEST_ORDER_IDX
|
|
||||||
) {
|
|
||||||
throw new ConflictException('questions.errors.uniqueOrderConstraint');
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new AppTimeoutException(err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findAllForAdmin(query: FindQuestionsDTO) {
|
public async findAllForAdmin(query: FindQuestionsDTO) {
|
||||||
const filters = filterQuestions(query);
|
return await this.questionsQueryProvider.findAllForAdmin(query);
|
||||||
const { limit, page, skip, take } =
|
|
||||||
Utils.PaginationUtils.getPaginationOptions(query);
|
|
||||||
|
|
||||||
let selectedTest: Test | null = null;
|
|
||||||
|
|
||||||
if (query.test) {
|
|
||||||
try {
|
|
||||||
selectedTest = await this.testsService.adminGetOne(query.test);
|
|
||||||
} catch (err) {
|
|
||||||
throw new AppTimeoutException(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const [questions, total] = await this.questionsRepository.findAndCount({
|
|
||||||
where: {
|
|
||||||
...filters,
|
|
||||||
...(selectedTest && {
|
|
||||||
test: selectedTest,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
loadRelationIds: true,
|
|
||||||
skip,
|
|
||||||
take,
|
|
||||||
order: query.sortBy
|
|
||||||
? {
|
|
||||||
[query.sortBy]: query.order ?? 'ASC',
|
|
||||||
}
|
|
||||||
: { createdAt: 'DESC' },
|
|
||||||
});
|
|
||||||
|
|
||||||
return new PaginatedResponse(questions, {
|
|
||||||
page,
|
|
||||||
limit,
|
|
||||||
total,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
throw new AppTimeoutException(err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findOneForAdmin(id: number) {
|
public async findOneForAdmin(id: number) {
|
||||||
try {
|
return await this.questionsQueryProvider.findOneForAdmin(id);
|
||||||
return await this.questionsRepository.findOne({
|
|
||||||
where: { id },
|
|
||||||
relations: { test: true },
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
throw new AppTimeoutException(err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateOne(id: number, updateQuestionDto: UpdateQuestionDTO) {
|
public async updateOne(id: number, updateQuestionDto: UpdateQuestionDTO) {
|
||||||
const question = await this.questionsRepository.findOneBy({ id });
|
return await this.questionsCommandProvider.updateOne(id, updateQuestionDto);
|
||||||
|
|
||||||
if (!question) {
|
|
||||||
throw new NotFoundException('questions.errors.questionNotFound');
|
|
||||||
}
|
|
||||||
|
|
||||||
const type =
|
|
||||||
updateQuestionDto.type !== undefined
|
|
||||||
? updateQuestionDto.type
|
|
||||||
: question.type;
|
|
||||||
|
|
||||||
const metadata =
|
|
||||||
updateQuestionDto.metadata !== undefined
|
|
||||||
? updateQuestionDto.metadata
|
|
||||||
: question.metadata;
|
|
||||||
|
|
||||||
const errors = await validateMetadata(type, metadata);
|
|
||||||
|
|
||||||
if (errors && errors.length > 0) {
|
|
||||||
throw new AppException(
|
|
||||||
'validation.wrongFieldFormat',
|
|
||||||
HttpStatus.BAD_REQUEST,
|
|
||||||
{ field: '$t(questions.fields.metadata)' },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(question, updateQuestionDto);
|
|
||||||
|
|
||||||
console.log(question);
|
|
||||||
|
|
||||||
try {
|
|
||||||
return await this.questionsRepository.save(question);
|
|
||||||
} catch (err) {
|
|
||||||
if (
|
|
||||||
err instanceof QueryFailedError &&
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
||||||
(err as any).constraint === UNIQUE_TEST_ORDER_IDX
|
|
||||||
) {
|
|
||||||
throw new ConflictException('questions.errors.uniqueOrderConstraint');
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new AppTimeoutException(err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteOne(id: number) {
|
public async deleteOne(id: number) {
|
||||||
let question: Question | null = null;
|
return await this.questionsCommandProvider.deleteOne(id);
|
||||||
|
|
||||||
try {
|
|
||||||
question = await this.questionsRepository.findOneBy({ id });
|
|
||||||
} catch (err) {
|
|
||||||
throw new AppTimeoutException(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!question)
|
|
||||||
throw new NotFoundException('questions.errors.questionNotFound');
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.questionsRepository.delete(question.id);
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
throw new AppTimeoutException(err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,23 @@ import { Choice } from './entities/choice.entity';
|
|||||||
import { QuestionsController } from './questions.controller';
|
import { QuestionsController } from './questions.controller';
|
||||||
import { TestsModule } from '../tests/tests.module';
|
import { TestsModule } from '../tests/tests.module';
|
||||||
import { QuestionsService } from './providers/questions.service';
|
import { QuestionsService } from './providers/questions.service';
|
||||||
|
import { QuestionsCommandProvider } from './providers/questions-command.provider';
|
||||||
|
import { QuestionsQueryProvider } from './providers/questions-query.provider';
|
||||||
|
import { ChoicesCommandProvider } from './providers/choices-command.provider';
|
||||||
|
import { ChoicesService } from './providers/choices.service';
|
||||||
|
import { ChoicesQueryProvider } from './providers/choices-query.provider';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([Question, Choice]), TestsModule],
|
imports: [TypeOrmModule.forFeature([Question, Choice]), TestsModule],
|
||||||
controllers: [QuestionsController],
|
controllers: [QuestionsController],
|
||||||
providers: [QuestionsService],
|
providers: [
|
||||||
exports: [QuestionsService],
|
QuestionsCommandProvider,
|
||||||
|
QuestionsQueryProvider,
|
||||||
|
QuestionsService,
|
||||||
|
ChoicesCommandProvider,
|
||||||
|
ChoicesQueryProvider,
|
||||||
|
ChoicesService,
|
||||||
|
],
|
||||||
|
exports: [QuestionsService, ChoicesService],
|
||||||
})
|
})
|
||||||
export class QuestionsModule {}
|
export class QuestionsModule {}
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
import {
|
import { BadRequestException, HttpStatus, Injectable } from '@nestjs/common';
|
||||||
BadRequestException,
|
|
||||||
HttpStatus,
|
|
||||||
Injectable,
|
|
||||||
NotFoundException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { CreateTestDTO } from '../dtos/create-test.dto';
|
import { CreateTestDTO } from '../dtos/create-test.dto';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Test } from '../entities/test.entity';
|
import { Test } from '../entities/test.entity';
|
||||||
@@ -49,7 +44,9 @@ export class TestsCommandProvider {
|
|||||||
await this.testRepository.save(newTest);
|
await this.testRepository.save(newTest);
|
||||||
return new AppResponse(
|
return new AppResponse(
|
||||||
newTest,
|
newTest,
|
||||||
this.i18nService.translate('tests.messages.createSuccess'),
|
this.i18nService.translate('common.messages.createSuccess', {
|
||||||
|
args: { entity: '$t(tests.fields.test)' },
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new AppTimeoutException(err);
|
throw new AppTimeoutException(err);
|
||||||
@@ -60,7 +57,11 @@ export class TestsCommandProvider {
|
|||||||
const test = await this.testRepository.findOneBy({ id });
|
const test = await this.testRepository.findOneBy({ id });
|
||||||
|
|
||||||
if (!test) {
|
if (!test) {
|
||||||
throw new NotFoundException('tests.errors.testNotFound');
|
throw new AppException(
|
||||||
|
'common.errors.entityNotFound',
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
{ entity: '$t(tests.fields.test)' },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const accessType = updateTestDto.accessType ?? test.accessType;
|
const accessType = updateTestDto.accessType ?? test.accessType;
|
||||||
@@ -91,7 +92,12 @@ export class TestsCommandProvider {
|
|||||||
throw new AppTimeoutException(err);
|
throw new AppTimeoutException(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!test) throw new NotFoundException('tests.errors.testNotFound');
|
if (!test)
|
||||||
|
throw new AppException(
|
||||||
|
'common.errors.entityNotFound',
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
{ entity: '$t(tests.fields.test)' },
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.testRepository.delete(test.id);
|
await this.testRepository.delete(test.id);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
import { HttpStatus, Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { PaginatedResponse } from '@/common/responses';
|
import { PaginatedResponse } from '@/common/responses';
|
||||||
@@ -7,6 +7,7 @@ import { FindTestsDTO } from '../dtos/find-tests.dto';
|
|||||||
import { filterTests } from '../helpers/filter-tests.helper';
|
import { filterTests } from '../helpers/filter-tests.helper';
|
||||||
import Utils from '@/common/utils';
|
import Utils from '@/common/utils';
|
||||||
import { Test } from '../entities/test.entity';
|
import { Test } from '../entities/test.entity';
|
||||||
|
import { AppException } from '@/common/exceptions/app.exception';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TestsQueryProvider {
|
export class TestsQueryProvider {
|
||||||
@@ -90,7 +91,11 @@ export class TestsQueryProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!test) {
|
if (!test) {
|
||||||
throw new NotFoundException('tests.errors.testNotFound');
|
throw new AppException(
|
||||||
|
'common.errors.entityNotFound',
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
{ entity: '$t(tests.fields.test)' },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return test;
|
return test;
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export class UpdateProfileDTO {
|
|||||||
@MaxLength(96, {
|
@MaxLength(96, {
|
||||||
message: t('validation.maxLengthField', {
|
message: t('validation.maxLengthField', {
|
||||||
field: '$t(users.fields.lastName)',
|
field: '$t(users.fields.lastName)',
|
||||||
min: 3,
|
max: 96,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
lastName?: string;
|
lastName?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user