feat: add pagination, sorting and filtering
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
import { Type } from 'class-transformer';
|
||||||
|
import { IsInt, IsOptional, IsString, Min } from 'class-validator';
|
||||||
|
|
||||||
|
export class PaginationQueryDTO {
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsInt()
|
||||||
|
@Min(1)
|
||||||
|
page?: number = 1;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsInt()
|
||||||
|
@Min(1)
|
||||||
|
limit?: number = 20;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
sortBy?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
order?: 'ASC' | 'DESC' = 'DESC';
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { RequestTimeoutException } from '@nestjs/common';
|
||||||
|
|
||||||
|
export class AppTimeoutException extends RequestTimeoutException {
|
||||||
|
constructor(cause?: unknown, description?: string) {
|
||||||
|
super('common.errors.requestTimeout', {
|
||||||
|
cause,
|
||||||
|
description: description ?? 'Error connecting to the database',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import * as StringUtils from './string-utils';
|
import * as StringUtils from './string-utils';
|
||||||
|
import * as PaginationUtils from './pagination-utils';
|
||||||
|
|
||||||
const Utils = {
|
const Utils = {
|
||||||
StringUtils,
|
StringUtils,
|
||||||
|
PaginationUtils,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Utils;
|
export default Utils;
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { PaginationQueryDTO } from '../dtos/pagination-query.dto';
|
||||||
|
|
||||||
|
export const getPaginationOptions = (query: PaginationQueryDTO) => {
|
||||||
|
const page = query.page ?? 1;
|
||||||
|
const limit = query.limit ?? 20;
|
||||||
|
|
||||||
|
return {
|
||||||
|
skip: (page - 1) * limit,
|
||||||
|
take: limit,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Body, Controller, HttpCode, Post } from '@nestjs/common';
|
import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common';
|
||||||
import { AdminAuthService } from './providers/admin-auth.service';
|
import { AdminAuthService } from './providers/admin-auth.service';
|
||||||
import { AdminLoginDTO } from './dtos/admin-login.dto';
|
import { AdminLoginDTO } from './dtos/admin-login.dto';
|
||||||
import { Public } from '@/modules/auth/decorators/public.decorator';
|
import { Public } from '@/modules/auth/decorators/public.decorator';
|
||||||
@@ -14,7 +14,7 @@ export class AdminAuthController {
|
|||||||
|
|
||||||
@Public()
|
@Public()
|
||||||
@Post('login')
|
@Post('login')
|
||||||
@HttpCode(200)
|
@HttpCode(HttpStatus.OK)
|
||||||
public async login(@Body() adminLoginDto: AdminLoginDTO) {
|
public async login(@Body() adminLoginDto: AdminLoginDTO) {
|
||||||
return await this.adminAuthService.login(adminLoginDto);
|
return await this.adminAuthService.login(adminLoginDto);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
import { Role } from '@/common/enums/roles.enum';
|
import { Role } from '@/common/enums/roles.enum';
|
||||||
import { Roles } from '@/modules/auth/decorators/roles.decorator';
|
import { Roles } from '@/modules/auth/decorators/roles.decorator';
|
||||||
import { CreateTestDTO } from '@/modules/tests/dtos/create-test.dto';
|
import { CreateTestDTO } from '@/modules/tests/dtos/create-test.dto';
|
||||||
|
import { FindTestsDTO } from '@/modules/tests/dtos/find-tests.dto';
|
||||||
import { UpdateTestDTO } from '@/modules/tests/dtos/update-test.dto';
|
import { UpdateTestDTO } from '@/modules/tests/dtos/update-test.dto';
|
||||||
import { TestsService } from '@/modules/tests/providers/tests.service';
|
import { TestsService } from '@/modules/tests/providers/tests.service';
|
||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
|
Delete,
|
||||||
Get,
|
Get,
|
||||||
|
HttpCode,
|
||||||
|
HttpStatus,
|
||||||
Param,
|
Param,
|
||||||
ParseIntPipe,
|
ParseIntPipe,
|
||||||
Patch,
|
Patch,
|
||||||
Post,
|
Post,
|
||||||
|
Query,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
|
||||||
@Controller('admin/tests')
|
@Controller('admin/tests')
|
||||||
@@ -29,8 +34,8 @@ export class AdminTestsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
public async getAllTests() {
|
public async getAllTests(@Query() query: FindTestsDTO) {
|
||||||
return await this.testsService.adminGetAll();
|
return await this.testsService.adminGetAll(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@@ -45,4 +50,10 @@ export class AdminTestsController {
|
|||||||
) {
|
) {
|
||||||
return await this.testsService.updateOne(id, updateTestDto);
|
return await this.testsService.updateOne(id, updateTestDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
|
public async deleteTestById(@Param('id', ParseIntPipe) id: number) {
|
||||||
|
return await this.testsService.deleteOne(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
DELETE http://localhost:3000/admin/tests/1
|
||||||
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJwaG9uZSI6IjA5MzMzMDI2MzYzIiwiaWF0IjoxNzgwNzQ5MTcwLCJleHAiOjE3ODA3NTI3NzAsImF1ZCI6ImxvY2FsaG9zdCIsImlzcyI6ImxvY2FsaG9zdCJ9.apRhtVZgFMFcyutZGO7uzckscch9Jw42s0RCSZZv4K0
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
GET http://localhost:3000/admin/tests
|
GET http://localhost:3000/admin/tests?title=test&isActive=true&accessType=paid&sortBy=title&order=DSC&page=2&limit=1
|
||||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJwaG9uZSI6IjA5MzMzMDI2MzYzIiwiaWF0IjoxNzgwMzk4MzI0LCJleHAiOjE3ODA0MDE5MjQsImF1ZCI6ImxvY2FsaG9zdCIsImlzcyI6ImxvY2FsaG9zdCJ9.s3NAErSAjMTz2s27obas3TEi5Og2HPeLgWCoIyzmzII
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJwaG9uZSI6IjA5MzMzMDI2MzYzIiwiaWF0IjoxNzgwNzQ5MTcwLCJleHAiOjE3ODA3NTI3NzAsImF1ZCI6ImxvY2FsaG9zdCIsImlzcyI6ImxvY2FsaG9zdCJ9.apRhtVZgFMFcyutZGO7uzckscch9Jw42s0RCSZZv4K0
|
||||||
|
|
||||||
GET http://localhost:3000/admin/tests/4
|
GET http://localhost:3000/admin/tests/4
|
||||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJwaG9uZSI6IjA5MzMzMDI2MzYzIiwiaWF0IjoxNzgwMzk4MzI0LCJleHAiOjE3ODA0MDE5MjQsImF1ZCI6ImxvY2FsaG9zdCIsImlzcyI6ImxvY2FsaG9zdCJ9.s3NAErSAjMTz2s27obas3TEi5Og2HPeLgWCoIyzmzII
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJwaG9uZSI6IjA5MzMzMDI2MzYzIiwiaWF0IjoxNzgwNzQ5MTcwLCJleHAiOjE3ODA3NTI3NzAsImF1ZCI6ImxvY2FsaG9zdCIsImlzcyI6ImxvY2FsaG9zdCJ9.apRhtVZgFMFcyutZGO7uzckscch9Jw42s0RCSZZv4K0
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Body, Controller, HttpCode, Post } from '@nestjs/common';
|
import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common';
|
||||||
import { LoginDTO } from './dtos/login.dto';
|
import { LoginDTO } from './dtos/login.dto';
|
||||||
import { AuthService } from './providers/auth.service';
|
import { AuthService } from './providers/auth.service';
|
||||||
import { VerifyOtpDTO } from './dtos/verify-otp.dto';
|
import { VerifyOtpDTO } from './dtos/verify-otp.dto';
|
||||||
@@ -16,21 +16,21 @@ export class AuthController {
|
|||||||
|
|
||||||
@Public()
|
@Public()
|
||||||
@Post('login')
|
@Post('login')
|
||||||
@HttpCode(200)
|
@HttpCode(HttpStatus.OK)
|
||||||
public async logIn(@Body() loginDto: LoginDTO) {
|
public async logIn(@Body() loginDto: LoginDTO) {
|
||||||
return await this.authService.logIn(loginDto);
|
return await this.authService.logIn(loginDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Public()
|
@Public()
|
||||||
@Post('verify-otp')
|
@Post('verify-otp')
|
||||||
@HttpCode(200)
|
@HttpCode(HttpStatus.OK)
|
||||||
public async verifyOTP(@Body() verifyOtpDto: VerifyOtpDTO) {
|
public async verifyOTP(@Body() verifyOtpDto: VerifyOtpDTO) {
|
||||||
return await this.authService.verifyOTP(verifyOtpDto);
|
return await this.authService.verifyOTP(verifyOtpDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Public()
|
@Public()
|
||||||
@Post('refresh')
|
@Post('refresh')
|
||||||
@HttpCode(200)
|
@HttpCode(HttpStatus.OK)
|
||||||
public async refreshToken(@Body() refreshTokenDto: RefreshTokenDTO) {
|
public async refreshToken(@Body() refreshTokenDto: RefreshTokenDTO) {
|
||||||
return await this.authService.refreshToken(refreshTokenDto);
|
return await this.authService.refreshToken(refreshTokenDto);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
import {
|
import { HttpStatus, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
HttpStatus,
|
|
||||||
Injectable,
|
|
||||||
RequestTimeoutException,
|
|
||||||
UnauthorizedException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { LessThan, MoreThanOrEqual, Repository } from 'typeorm';
|
import { LessThan, MoreThanOrEqual, Repository } from 'typeorm';
|
||||||
import { OTP } from '../entities/otp.entity';
|
import { OTP } from '../entities/otp.entity';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Cron } from '@nestjs/schedule';
|
import { Cron } from '@nestjs/schedule';
|
||||||
import { HashingProvider } from '@/common/modules/hashing/providers/hashing.provider';
|
import { HashingProvider } from '@/common/modules/hashing/providers/hashing.provider';
|
||||||
import { AppException } from '@/common/exceptions/app.exception';
|
import { AppException } from '@/common/exceptions/app.exception';
|
||||||
|
import { AppTimeoutException } from '@/common/exceptions/app-timeout.exception';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OtpService {
|
export class OtpService {
|
||||||
@@ -73,10 +69,7 @@ export class OtpService {
|
|||||||
order: { createdAt: 'DESC' },
|
order: { createdAt: 'DESC' },
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new RequestTimeoutException('common.errors.requestTimeout', {
|
throw new AppTimeoutException(err);
|
||||||
cause: err,
|
|
||||||
description: 'Error connecting to the database',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!otp) throw new UnauthorizedException('auth.errors.invalidOTP');
|
if (!otp) throw new UnauthorizedException('auth.errors.invalidOTP');
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { PaginationQueryDTO } from '@/common/dtos/pagination-query.dto';
|
||||||
|
import {
|
||||||
|
IsBoolean,
|
||||||
|
IsEnum,
|
||||||
|
IsNumber,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
} from 'class-validator';
|
||||||
|
import { AccessType } from '../enums/access-types.enum';
|
||||||
|
import { PartialType } from '@nestjs/swagger';
|
||||||
|
import { TestSortField } from '../enums/test-sort-field.enum';
|
||||||
|
|
||||||
|
export class FindTestsDTO extends PartialType(PaginationQueryDTO) {
|
||||||
|
@IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
id?: number;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
title?: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
price?: string;
|
||||||
|
|
||||||
|
@IsEnum(AccessType)
|
||||||
|
@IsOptional()
|
||||||
|
accessType?: AccessType;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
isActive?: boolean;
|
||||||
|
|
||||||
|
@IsEnum(TestSortField)
|
||||||
|
@IsOptional()
|
||||||
|
sortBy?: TestSortField;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export enum TestSortField {
|
||||||
|
ID = 'id',
|
||||||
|
TITLE = 'title',
|
||||||
|
DESCRIPTION = 'description',
|
||||||
|
ACCESS_TYPE = 'accessType',
|
||||||
|
PRICE = 'price',
|
||||||
|
IS_ACTIVE = 'isActive',
|
||||||
|
CREATED_AT = 'createdAt',
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { FindOptionsWhere, ILike } from 'typeorm';
|
||||||
|
import { FindTestsDTO } from '../dtos/find-tests.dto';
|
||||||
|
|
||||||
|
export const filterTests = (query: FindTestsDTO) => {
|
||||||
|
const where: FindOptionsWhere<any> = {};
|
||||||
|
|
||||||
|
if (query.id) {
|
||||||
|
where.id = query.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.title) {
|
||||||
|
where.title = ILike(`%${query.title}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.description) {
|
||||||
|
where.description = ILike(`%${query.description}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.accessType !== undefined) {
|
||||||
|
where.accessType = query.accessType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.price !== undefined) {
|
||||||
|
where.price = query.price;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.isActive !== undefined) {
|
||||||
|
where.isActive = query.isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
return where;
|
||||||
|
};
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import {
|
||||||
|
BadRequestException,
|
||||||
|
HttpStatus,
|
||||||
|
Injectable,
|
||||||
|
NotFoundException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { CreateTestDTO } from '../dtos/create-test.dto';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Test } from '../entities/test.entity';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
import { AppException } from '@/common/exceptions/app.exception';
|
||||||
|
import { AppResponse } from '@/common/responses';
|
||||||
|
import { AppTimeoutException } from '@/common/exceptions/app-timeout.exception';
|
||||||
|
import { UpdateTestDTO } from '../dtos/update-test.dto';
|
||||||
|
import { AccessType } from '../enums/access-types.enum';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TestsCommandProvider {
|
||||||
|
constructor(
|
||||||
|
/**
|
||||||
|
* Inject Tests Repository
|
||||||
|
*/
|
||||||
|
@InjectRepository(Test)
|
||||||
|
private readonly testRepository: Repository<Test>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject I18n Service
|
||||||
|
*/
|
||||||
|
private readonly i18nService: I18nService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async createOne(createTestDto: CreateTestDTO) {
|
||||||
|
const titleExists = await this.testRepository.findOne({
|
||||||
|
where: {
|
||||||
|
title: createTestDto.title,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (titleExists) {
|
||||||
|
throw new AppException('validation.duplicateField', HttpStatus.CONFLICT, {
|
||||||
|
field: '$t(tests.fields.title)',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTest = this.testRepository.create(createTestDto);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.testRepository.save(newTest);
|
||||||
|
return new AppResponse(
|
||||||
|
newTest,
|
||||||
|
this.i18nService.translate('tests.messages.createSuccess'),
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateOne(id: number, updateTestDto: UpdateTestDTO) {
|
||||||
|
const test = await this.testRepository.findOneBy({ id });
|
||||||
|
|
||||||
|
if (!test) {
|
||||||
|
throw new NotFoundException('tests.errors.testNotFound');
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessType = updateTestDto.accessType ?? test.accessType;
|
||||||
|
const price = updateTestDto.price ?? test.price;
|
||||||
|
|
||||||
|
if (
|
||||||
|
(accessType === AccessType.PAID && !price) ||
|
||||||
|
(accessType !== AccessType.PAID && price)
|
||||||
|
) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
this.i18nService.translate('tests.errors.priceAccessConstraint'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(test, updateTestDto);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await this.testRepository.save(test);
|
||||||
|
} catch (err) {
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteOne(id: number) {
|
||||||
|
let test: Test | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
test = await this.testRepository.findOneBy({ id });
|
||||||
|
} catch (err) {
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!test) throw new NotFoundException('tests.errors.testNotFound');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.testRepository.delete(test.id);
|
||||||
|
} catch (err) {
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { PaginatedResponse } from '@/common/responses';
|
||||||
|
import { AppTimeoutException } from '@/common/exceptions/app-timeout.exception';
|
||||||
|
import { FindTestsDTO } from '../dtos/find-tests.dto';
|
||||||
|
import { filterTests } from '../helpers/filter-tests.helper';
|
||||||
|
import Utils from '@/common/utils';
|
||||||
|
import { Test } from '../entities/test.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TestsQueryProvider {
|
||||||
|
constructor(
|
||||||
|
/**
|
||||||
|
* Inject Tests Repository
|
||||||
|
*/
|
||||||
|
@InjectRepository(Test)
|
||||||
|
private readonly testRepository: Repository<Test>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async findAllForAdmin(query: FindTestsDTO) {
|
||||||
|
const filterOptions = filterTests(query);
|
||||||
|
const { page, take, limit, skip } =
|
||||||
|
Utils.PaginationUtils.getPaginationOptions(query);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [tests, total] = await this.testRepository.findAndCount({
|
||||||
|
where: filterOptions,
|
||||||
|
skip,
|
||||||
|
take,
|
||||||
|
order: query.sortBy
|
||||||
|
? {
|
||||||
|
[query.sortBy]: query.order ?? 'ASC',
|
||||||
|
}
|
||||||
|
: { createdAt: 'DESC' },
|
||||||
|
});
|
||||||
|
|
||||||
|
return new PaginatedResponse(tests, {
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
total,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findAllForUser() {
|
||||||
|
try {
|
||||||
|
return await this.testRepository.find({
|
||||||
|
where: { isActive: true },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
accessType: true,
|
||||||
|
description: true,
|
||||||
|
price: true,
|
||||||
|
title: true,
|
||||||
|
},
|
||||||
|
order: { createdAt: 'DESC' },
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findOneForAdmin(id: number) {
|
||||||
|
try {
|
||||||
|
return await this.testRepository.findOneBy({ id });
|
||||||
|
} catch (err) {
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async findOneForUser(id: number) {
|
||||||
|
let test: Test | null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
test = await this.testRepository.findOne({
|
||||||
|
where: { id, isActive: true },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
accessType: true,
|
||||||
|
description: true,
|
||||||
|
price: true,
|
||||||
|
title: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
throw new AppTimeoutException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!test) {
|
||||||
|
throw new NotFoundException('tests.errors.testNotFound');
|
||||||
|
}
|
||||||
|
|
||||||
|
return test;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,163 +1,49 @@
|
|||||||
import {
|
import { Injectable } from '@nestjs/common';
|
||||||
BadRequestException,
|
|
||||||
HttpStatus,
|
|
||||||
Injectable,
|
|
||||||
NotFoundException,
|
|
||||||
RequestTimeoutException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { Test } from '../entities/test.entity';
|
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
import { CreateTestDTO } from '../dtos/create-test.dto';
|
import { CreateTestDTO } from '../dtos/create-test.dto';
|
||||||
import { AppException } from '@/common/exceptions/app.exception';
|
|
||||||
import { AppResponse } from '@/common/responses';
|
|
||||||
import { I18nService } from 'nestjs-i18n';
|
|
||||||
import { UpdateTestDTO } from '../dtos/update-test.dto';
|
import { UpdateTestDTO } from '../dtos/update-test.dto';
|
||||||
import { AccessType } from '../enums/access-types.enum';
|
import { FindTestsDTO } from '../dtos/find-tests.dto';
|
||||||
|
import { TestsQueryProvider } from './tests-query.provider';
|
||||||
|
import { TestsCommandProvider } from './tests-command.provider';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TestsService {
|
export class TestsService {
|
||||||
constructor(
|
constructor(
|
||||||
/**
|
/**
|
||||||
* Inject Tests Repository
|
* Inject Tests Query Provider
|
||||||
*/
|
*/
|
||||||
@InjectRepository(Test)
|
private readonly testsQueryProvider: TestsQueryProvider,
|
||||||
private readonly testRepository: Repository<Test>,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inject I18n Service
|
* Inject Tests Command Provider
|
||||||
*/
|
*/
|
||||||
private readonly i18nService: I18nService,
|
private readonly testsCommandProvider: TestsCommandProvider,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async createOne(createTestDto: CreateTestDTO) {
|
public async createOne(createTestDto: CreateTestDTO) {
|
||||||
const titleExists = await this.testRepository.findOne({
|
return await this.testsCommandProvider.createOne(createTestDto);
|
||||||
where: {
|
|
||||||
title: createTestDto.title,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (titleExists) {
|
|
||||||
throw new AppException('validation.duplicateField', HttpStatus.CONFLICT, {
|
|
||||||
field: '$t(tests.fields.title)',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newTest = this.testRepository.create(createTestDto);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.testRepository.save(newTest);
|
|
||||||
return new AppResponse(
|
|
||||||
newTest,
|
|
||||||
this.i18nService.translate('tests.messages.createSuccess'),
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
throw new RequestTimeoutException('common.errors.requestTimeout', {
|
|
||||||
cause: err,
|
|
||||||
description: 'Error connecting to the database',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async adminGetAll() {
|
public async adminGetAll(query: FindTestsDTO) {
|
||||||
try {
|
return await this.testsQueryProvider.findAllForAdmin(query);
|
||||||
return await this.testRepository.find({ order: { createdAt: 'DESC' } });
|
|
||||||
} catch (err) {
|
|
||||||
throw new RequestTimeoutException('common.errors.requestTimeout', {
|
|
||||||
cause: err,
|
|
||||||
description: 'Error connecting to the database',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async userGetAll() {
|
public async userGetAll() {
|
||||||
try {
|
return await this.testsQueryProvider.findAllForUser();
|
||||||
return await this.testRepository.find({
|
|
||||||
where: { isActive: true },
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
accessType: true,
|
|
||||||
description: true,
|
|
||||||
price: true,
|
|
||||||
title: true,
|
|
||||||
},
|
|
||||||
order: { createdAt: 'DESC' },
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
throw new RequestTimeoutException('common.errors.requestTimeout', {
|
|
||||||
cause: err,
|
|
||||||
description: 'Error connecting to the database',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async adminGetOne(id: number) {
|
public async adminGetOne(id: number) {
|
||||||
try {
|
return await this.testsQueryProvider.findOneForAdmin(id);
|
||||||
return await this.testRepository.findOneBy({ id });
|
|
||||||
} catch (err) {
|
|
||||||
throw new RequestTimeoutException('common.errors.requestTimeout', {
|
|
||||||
cause: err,
|
|
||||||
description: 'Error connecting to the database',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async userGetOne(id: number) {
|
public async userGetOne(id: number) {
|
||||||
let test: Test | null;
|
return await this.testsQueryProvider.findOneForUser(id);
|
||||||
|
|
||||||
try {
|
|
||||||
test = await this.testRepository.findOne({
|
|
||||||
where: { id, isActive: true },
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
accessType: true,
|
|
||||||
description: true,
|
|
||||||
price: true,
|
|
||||||
title: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
throw new RequestTimeoutException('common.errors.requestTimeout', {
|
|
||||||
cause: err,
|
|
||||||
description: 'Error connecting to the database',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!test) {
|
|
||||||
throw new NotFoundException('tests.errors.testNotFound');
|
|
||||||
}
|
|
||||||
|
|
||||||
return test;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateOne(id: number, updateTestDto: UpdateTestDTO) {
|
public async updateOne(id: number, updateTestDto: UpdateTestDTO) {
|
||||||
const test = await this.testRepository.findOneBy({ id });
|
return await this.testsCommandProvider.updateOne(id, updateTestDto);
|
||||||
|
}
|
||||||
|
|
||||||
if (!test) {
|
public async deleteOne(id: number) {
|
||||||
throw new NotFoundException('tests.errors.testNotFound');
|
return await this.testsCommandProvider.deleteOne(id);
|
||||||
}
|
|
||||||
|
|
||||||
const accessType = updateTestDto.accessType ?? test.accessType;
|
|
||||||
const price = updateTestDto.price ?? test.price;
|
|
||||||
|
|
||||||
if (
|
|
||||||
(accessType === AccessType.PAID && !price) ||
|
|
||||||
(accessType !== AccessType.PAID && price)
|
|
||||||
) {
|
|
||||||
throw new BadRequestException(
|
|
||||||
this.i18nService.translate('tests.errors.priceAccessConstraint'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(test, updateTestDto);
|
|
||||||
|
|
||||||
try {
|
|
||||||
return await this.testRepository.save(test);
|
|
||||||
} catch (err) {
|
|
||||||
throw new RequestTimeoutException('common.errors.requestTimeout', {
|
|
||||||
cause: err,
|
|
||||||
description: 'Error connecting to the database',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
|||||||
import { Test } from './entities/test.entity';
|
import { Test } from './entities/test.entity';
|
||||||
import { TestsService } from './providers/tests.service';
|
import { TestsService } from './providers/tests.service';
|
||||||
import { TestsController } from './tests.controller';
|
import { TestsController } from './tests.controller';
|
||||||
|
import { TestsQueryProvider } from './providers/tests-query.provider';
|
||||||
|
import { TestsCommandProvider } from './providers/tests-command.provider';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([Test])],
|
imports: [TypeOrmModule.forFeature([Test])],
|
||||||
controllers: [TestsController],
|
controllers: [TestsController],
|
||||||
providers: [TestsService],
|
providers: [TestsService, TestsQueryProvider, TestsCommandProvider],
|
||||||
exports: [TestsService],
|
exports: [TestsService],
|
||||||
})
|
})
|
||||||
export class TestsModule {}
|
export class TestsModule {}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { HashingProvider } from '@/common/modules/hashing/providers/hashing.provider';
|
import { HashingProvider } from '@/common/modules/hashing/providers/hashing.provider';
|
||||||
import { Injectable, RequestTimeoutException } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { I18nService } from 'nestjs-i18n';
|
import { I18nService } from 'nestjs-i18n';
|
||||||
import { User } from '../entities/user.entity';
|
import { User } from '../entities/user.entity';
|
||||||
import { UpdateProfileDTO } from '../dto/update-profile.dto';
|
import { UpdateProfileDTO } from '../dto/update-profile.dto';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { AppResponse } from '@/common/responses';
|
import { AppResponse } from '@/common/responses';
|
||||||
|
import { AppTimeoutException } from '@/common/exceptions/app-timeout.exception';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserProfileProvider {
|
export class UserProfileProvider {
|
||||||
@@ -59,10 +60,7 @@ export class UserProfileProvider {
|
|||||||
try {
|
try {
|
||||||
await this.userRepository.update(user.id, updateData);
|
await this.userRepository.update(user.id, updateData);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new RequestTimeoutException('common.errors.requestTimeout', {
|
throw new AppTimeoutException(err);
|
||||||
cause: err,
|
|
||||||
description: 'Error connecting to the database',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import {
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||||
BadRequestException,
|
|
||||||
Injectable,
|
|
||||||
RequestTimeoutException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { User } from '../entities/user.entity';
|
import { User } from '../entities/user.entity';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { UpdateProfileDTO } from '../dto/update-profile.dto';
|
import { UpdateProfileDTO } from '../dto/update-profile.dto';
|
||||||
import { UserProfileProvider } from './user-profile.provider';
|
import { UserProfileProvider } from './user-profile.provider';
|
||||||
|
import { AppTimeoutException } from '@/common/exceptions/app-timeout.exception';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UsersService {
|
export class UsersService {
|
||||||
@@ -29,10 +26,7 @@ export class UsersService {
|
|||||||
try {
|
try {
|
||||||
user = await this.userRepository.findOneById(id);
|
user = await this.userRepository.findOneById(id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new RequestTimeoutException('common.errors.requestTimeout', {
|
throw new AppTimeoutException(err);
|
||||||
cause: err,
|
|
||||||
description: 'Error connecting to the database',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@@ -46,10 +40,7 @@ export class UsersService {
|
|||||||
try {
|
try {
|
||||||
return await this.userRepository.findOneBy({ phone });
|
return await this.userRepository.findOneBy({ phone });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new RequestTimeoutException('common.errors.requestTimeout', {
|
throw new AppTimeoutException(err);
|
||||||
cause: err,
|
|
||||||
description: 'Error connecting to the database',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,10 +50,7 @@ export class UsersService {
|
|||||||
try {
|
try {
|
||||||
await this.userRepository.save(newUser);
|
await this.userRepository.save(newUser);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new RequestTimeoutException('common.errors.requestTimeout', {
|
throw new AppTimeoutException(err);
|
||||||
cause: err,
|
|
||||||
description: 'Error connecting to the database',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return newUser;
|
return newUser;
|
||||||
|
|||||||
Reference in New Issue
Block a user