From 78f481871cf98157b7e98b6ecf28d32eca1098e2 Mon Sep 17 00:00:00 2001 From: mahyargdz Date: Sun, 13 Apr 2025 10:30:59 +0330 Subject: [PATCH] fix: bug in the sliders and blog module --- src/common/enums/message.enum.ts | 16 ++++++ src/configs/bullmq.config.ts | 3 +- src/configs/cache.config.ts | 2 +- src/modules/auth/providers/auth.service.ts | 7 +-- src/modules/blogs/blogs.module.ts | 3 +- src/modules/sliders/DTO/create-slider.dto.ts | 40 +++++++++++++++ src/modules/sliders/DTO/update-slider.dto.ts | 5 ++ src/modules/sliders/entities/slider.entity.ts | 24 +++++++++ .../sliders/providers/slider.service.ts | 48 ++++++++++++++++++ .../repositories/sliders.repository.ts | 12 +++++ src/modules/sliders/slider.controller.ts | 49 +++++++++++++++++++ src/modules/sliders/slider.module.ts | 15 ++++++ src/modules/users/providers/users.service.ts | 3 ++ src/modules/utils/providers/otp.service.ts | 2 +- 14 files changed, 219 insertions(+), 10 deletions(-) create mode 100644 src/modules/sliders/DTO/create-slider.dto.ts create mode 100644 src/modules/sliders/DTO/update-slider.dto.ts create mode 100644 src/modules/sliders/entities/slider.entity.ts create mode 100644 src/modules/sliders/providers/slider.service.ts create mode 100644 src/modules/sliders/repositories/sliders.repository.ts create mode 100644 src/modules/sliders/slider.controller.ts create mode 100644 src/modules/sliders/slider.module.ts diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index 0269178..a15774f 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -666,3 +666,19 @@ export const enum BlogMessage { SEARCH_QUERY_STRING = "رشته جستجو باید یک رشته باشد", IS_PINNED_SHOULD_BE_A_BOOLEAN = "وضعیت سنجاق بودن باید یک بولین باشد", } + +export const enum SliderMessage { + TITLE_REQUIRED = "عنوان اسلایدر الزامی است", + TITLE_STRING = "عنوان اسلایدر باید یک رشته باشد", + TITLE_MAX_LENGTH = "حداکثر طول عنوان اسلایدر باید ۱۵۰ کاراکتر باشد", + DESCRIPTION_STRING = "توضیحات اسلایدر باید یک رشته باشد", + DESCRIPTION_MAX_LENGTH = "حداکثر طول توضیحات اسلایدر باید ۲۵۵ کاراکتر باشد", + IMAGE_URL_REQUIRED = "آدرس تصویر اسلایدر الزامی است", + IMAGE_URL_STRING = "آدرس تصویر اسلایدر باید یک رشته باشد", + IMAGE_URL_MAX_LENGTH = "حداکثر طول آدرس تصویر اسلایدر باید ۲۵۵ کاراکتر باشد", + LINK_STRING = "لینک اسلایدر باید یک رشته باشد", + LINK_MAX_LENGTH = "حداکثر طول لینک اسلایدر باید ۲۵۵ کاراکتر باشد", + ORDER_NUMBER = "ترتیب اسلایدر باید یک عدد باشد", + IS_ACTIVE_SHOULD_BE_A_BOOLEAN = "وضعیت فعال بودن اسلایدر باید یک بولین باشد", + SLIDER_NOT_FOUND = "اسلایدر مورد نظر یافت نشد", +} diff --git a/src/configs/bullmq.config.ts b/src/configs/bullmq.config.ts index c11aa0f..cbbdba9 100755 --- a/src/configs/bullmq.config.ts +++ b/src/configs/bullmq.config.ts @@ -8,10 +8,11 @@ export function bullMqConfig(): SharedBullAsyncConfiguration { connection: { url: configService.getOrThrow("REDIS_URI"), }, + prefix: "danak-console", defaultJobOptions: { removeOnComplete: false, removeOnFail: false, - attempts: 5, // Reasonable default, adjust based on job criticality + attempts: 5, }, }), }; diff --git a/src/configs/cache.config.ts b/src/configs/cache.config.ts index fdf7c43..7f1aa63 100755 --- a/src/configs/cache.config.ts +++ b/src/configs/cache.config.ts @@ -9,7 +9,7 @@ export function cacheConfig(): CacheModuleAsyncOptions { useFactory: async (configService: ConfigService) => { return { ttl: configService.getOrThrow("CACHE_TTL"), - stores: [new KeyvRedis(configService.getOrThrow("REDIS_URI"))], + stores: [new KeyvRedis(configService.getOrThrow("REDIS_URI"), { namespace: "danak-console:" })], }; }, }; diff --git a/src/modules/auth/providers/auth.service.ts b/src/modules/auth/providers/auth.service.ts index 90a8f62..43332ae 100755 --- a/src/modules/auth/providers/auth.service.ts +++ b/src/modules/auth/providers/auth.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Injectable, Logger } from "@nestjs/common"; +import { BadRequestException, Injectable } from "@nestjs/common"; import { DataSource } from "typeorm"; import { TokensService } from "./tokens.service"; @@ -17,7 +17,6 @@ import { VerifyOtpDto } from "../DTO/verify-otp.dto"; @Injectable() export class AuthService { - private readonly logger = new Logger(AuthService.name); constructor( private readonly usersService: UsersService, private readonly passwordService: PasswordService, @@ -44,11 +43,9 @@ export class AuthService { const otpCode = await this.otpService.generateAndSetInCache(phone, "REGISTER"); // await this.smsService.sendSmsVerifyCode(phone, otpCode); - this.logger.debug(`OTP sent to ${phone}: ${otpCode}`); return { message: AuthMessage.OTP_SENT, - otpCode, }; } //****************** */ @@ -154,11 +151,9 @@ export class AuthService { const otpCode = await this.otpService.generateAndSetInCache(phone, "LOGIN"); // await this.smsService.sendSmsVerifyCode(phone, otpCode); - this.logger.debug(`OTP sent to ${phone}: ${otpCode}`); return { message: AuthMessage.OTP_SENT, - otpCode, }; } diff --git a/src/modules/blogs/blogs.module.ts b/src/modules/blogs/blogs.module.ts index cd9445a..6b64901 100644 --- a/src/modules/blogs/blogs.module.ts +++ b/src/modules/blogs/blogs.module.ts @@ -9,9 +9,10 @@ import { BlogsService } from "./providers/blogs.service"; import { BlogCategoriesRepository } from "./repositories/blog-categories.repository"; import { BlogCommentsRepository } from "./repositories/blog-comments.repository"; import { BlogsRepository } from "./repositories/blogs.repository"; +import { UsersModule } from "../users/users.module"; @Module({ - imports: [TypeOrmModule.forFeature([Blog, BlogComment, BlogCategory])], + imports: [TypeOrmModule.forFeature([Blog, BlogComment, BlogCategory]), UsersModule], controllers: [BlogsController], providers: [BlogsService, BlogsRepository, BlogCommentsRepository, BlogCategoriesRepository], exports: [BlogsService], diff --git a/src/modules/sliders/DTO/create-slider.dto.ts b/src/modules/sliders/DTO/create-slider.dto.ts new file mode 100644 index 0000000..283f686 --- /dev/null +++ b/src/modules/sliders/DTO/create-slider.dto.ts @@ -0,0 +1,40 @@ +import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; +import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, MaxLength } from "class-validator"; + +import { SliderMessage } from "../../../common/enums/message.enum"; + +export class CreateSliderDto { + @ApiProperty({ description: "Title of the slider", example: "Summer Sale" }) + @IsNotEmpty({ message: SliderMessage.TITLE_REQUIRED }) + @IsString({ message: SliderMessage.TITLE_STRING }) + @MaxLength(150, { message: SliderMessage.TITLE_MAX_LENGTH }) + title: string; + + @ApiPropertyOptional({ description: "Description of the slider", example: "Get up to 50% off" }) + @IsOptional() + @IsString({ message: SliderMessage.DESCRIPTION_STRING }) + @MaxLength(255, { message: SliderMessage.DESCRIPTION_MAX_LENGTH }) + description?: string; + + @ApiProperty({ description: "URL of the slider image", example: "https://example.com/image.jpg" }) + @IsNotEmpty({ message: SliderMessage.IMAGE_URL_REQUIRED }) + @IsString({ message: SliderMessage.IMAGE_URL_STRING }) + @MaxLength(255, { message: SliderMessage.IMAGE_URL_MAX_LENGTH }) + imageUrl: string; + + @ApiPropertyOptional({ description: "Link URL for the slider", example: "https://example.com/sale" }) + @IsOptional() + @IsString({ message: SliderMessage.LINK_STRING }) + @MaxLength(255, { message: SliderMessage.LINK_MAX_LENGTH }) + link?: string; + + @ApiPropertyOptional({ description: "Order of the slider", example: 1, default: 0 }) + @IsOptional() + @IsNumber({}, { message: SliderMessage.ORDER_NUMBER }) + order?: number; + + @ApiPropertyOptional({ description: "Whether the slider is active", example: true, default: true }) + @IsOptional() + @IsBoolean({ message: SliderMessage.IS_ACTIVE_SHOULD_BE_A_BOOLEAN }) + isActive?: boolean; +} diff --git a/src/modules/sliders/DTO/update-slider.dto.ts b/src/modules/sliders/DTO/update-slider.dto.ts new file mode 100644 index 0000000..3025f3d --- /dev/null +++ b/src/modules/sliders/DTO/update-slider.dto.ts @@ -0,0 +1,5 @@ +import { PartialType } from "@nestjs/swagger"; + +import { CreateSliderDto } from "./create-slider.dto"; + +export class UpdateSliderDto extends PartialType(CreateSliderDto) {} diff --git a/src/modules/sliders/entities/slider.entity.ts b/src/modules/sliders/entities/slider.entity.ts new file mode 100644 index 0000000..0f39ebd --- /dev/null +++ b/src/modules/sliders/entities/slider.entity.ts @@ -0,0 +1,24 @@ +import { Column, Entity } from "typeorm"; + +import { BaseEntity } from "../../../common/entities/base.entity"; + +@Entity() +export class Slider extends BaseEntity { + @Column({ length: 150 }) + title: string; + + @Column({ length: 255, nullable: true }) + description: string; + + @Column({ length: 255 }) + imageUrl: string; + + @Column({ length: 255, nullable: true }) + link: string; + + @Column({ default: 0 }) + order: number; + + @Column({ default: true }) + isActive: boolean; +} diff --git a/src/modules/sliders/providers/slider.service.ts b/src/modules/sliders/providers/slider.service.ts new file mode 100644 index 0000000..f19ae50 --- /dev/null +++ b/src/modules/sliders/providers/slider.service.ts @@ -0,0 +1,48 @@ +import { Injectable, NotFoundException } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { Repository } from "typeorm"; + +import { SliderMessage } from "../../../common/enums/message.enum"; +import { CreateSliderDto } from "../DTO/create-slider.dto"; +import { UpdateSliderDto } from "../DTO/update-slider.dto"; +import { Slider } from "../entities/slider.entity"; + +@Injectable() +export class SliderService { + constructor( + @InjectRepository(Slider) + private readonly sliderRepository: Repository, + ) {} + + async create(createSliderDto: CreateSliderDto): Promise { + const slider = this.sliderRepository.create(createSliderDto); + return await this.sliderRepository.save(slider); + } + + async findAll(): Promise { + return await this.sliderRepository.find({ + order: { + order: "ASC", + }, + }); + } + + async getSliderById(id: string): Promise { + const slider = await this.sliderRepository.findOne({ where: { id } }); + if (!slider) { + throw new NotFoundException(SliderMessage.SLIDER_NOT_FOUND); + } + return slider; + } + + async update(id: string, updateSliderDto: UpdateSliderDto): Promise { + const slider = await this.getSliderById(id); + Object.assign(slider, updateSliderDto); + return await this.sliderRepository.save(slider); + } + + async remove(id: string): Promise { + const slider = await this.getSliderById(id); + await this.sliderRepository.remove(slider); + } +} diff --git a/src/modules/sliders/repositories/sliders.repository.ts b/src/modules/sliders/repositories/sliders.repository.ts new file mode 100644 index 0000000..61b363c --- /dev/null +++ b/src/modules/sliders/repositories/sliders.repository.ts @@ -0,0 +1,12 @@ +import { Injectable } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { Repository } from "typeorm"; + +import { Slider } from "../entities/slider.entity"; + +@Injectable() +export class SlidersRepository extends Repository { + constructor(@InjectRepository(Slider) repository: Repository) { + super(repository.target, repository.manager, repository.queryRunner); + } +} diff --git a/src/modules/sliders/slider.controller.ts b/src/modules/sliders/slider.controller.ts new file mode 100644 index 0000000..7b31b6b --- /dev/null +++ b/src/modules/sliders/slider.controller.ts @@ -0,0 +1,49 @@ +import { Body, Controller, Delete, Get, Param, Patch, Post } from "@nestjs/common"; +import { ApiOperation } from "@nestjs/swagger"; + +import { CreateSliderDto } from "./DTO/create-slider.dto"; +import { UpdateSliderDto } from "./DTO/update-slider.dto"; +import { SliderService } from "./providers/slider.service"; +import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; +import { SkipAuth } from "../../common/decorators/skip-auth.decorator"; +import { ParamDto } from "../../common/DTO/param.dto"; +// import { PermissionsDec } from "../../common/decorators/permission.decorator"; + +@Controller("sliders") +@AuthGuards() +export class SliderController { + constructor(private readonly sliderService: SliderService) {} + + @ApiOperation({ summary: "create slider (admin route)" }) + // @PermissionsDec(PermissionEnum.SLIDER) + @Post() + create(@Body() createSliderDto: CreateSliderDto) { + return this.sliderService.create(createSliderDto); + } + + @ApiOperation({ summary: "Get all sliders" }) + @SkipAuth() + @Get() + findAll() { + return this.sliderService.findAll(); + } + + @Get(":id") + @ApiOperation({ summary: "Get a slider by id" }) + @SkipAuth() + getSliderById(@Param() paramDto: ParamDto) { + return this.sliderService.getSliderById(paramDto.id); + } + + @ApiOperation({ summary: "Update a slider" }) + @Patch(":id") + update(@Param() paramDto: ParamDto, @Body() updateSliderDto: UpdateSliderDto) { + return this.sliderService.update(paramDto.id, updateSliderDto); + } + + @ApiOperation({ summary: "Delete a slider" }) + @Delete(":id") + remove(@Param() paramDto: ParamDto) { + return this.sliderService.remove(paramDto.id); + } +} diff --git a/src/modules/sliders/slider.module.ts b/src/modules/sliders/slider.module.ts new file mode 100644 index 0000000..f43f77e --- /dev/null +++ b/src/modules/sliders/slider.module.ts @@ -0,0 +1,15 @@ +import { Module } from "@nestjs/common"; +import { TypeOrmModule } from "@nestjs/typeorm"; + +import { Slider } from "./entities/slider.entity"; +import { SliderService } from "./providers/slider.service"; +import { SlidersRepository } from "./repositories/sliders.repository"; +import { SliderController } from "./slider.controller"; + +@Module({ + imports: [TypeOrmModule.forFeature([Slider])], + controllers: [SliderController], + providers: [SliderService, SlidersRepository], + exports: [SliderService], +}) +export class SliderModule {} diff --git a/src/modules/users/providers/users.service.ts b/src/modules/users/providers/users.service.ts index 6de84d7..ba2ed9f 100755 --- a/src/modules/users/providers/users.service.ts +++ b/src/modules/users/providers/users.service.ts @@ -243,8 +243,11 @@ export class UsersService { const existUser = await queryRunner.manager.findOneBy(User, { nationalCode: registerDto.nationalCode }); if (existUser) throw new BadRequestException(UserMessage.NATIONAL_CODE_EXIST); + const userName = slugify(`${registerDto.firstName} ${Date.now().toString().slice(-5)}`, { lower: true, trim: true }); + const user = queryRunner.manager.create(User, { ...registerDto, + userName, password: hashedPassword, roles: [role], }); diff --git a/src/modules/utils/providers/otp.service.ts b/src/modules/utils/providers/otp.service.ts index 13c3c64..130d399 100755 --- a/src/modules/utils/providers/otp.service.ts +++ b/src/modules/utils/providers/otp.service.ts @@ -16,7 +16,7 @@ export class OTPService { async generateAndSetInCache(identifier: string, cacheKey: OtpCacheKeyType) { const otp = this.generateOTP(); const key = `${identifier}:${cacheKey}:OTP`; - await this.cacheService.set(key, otp); + await this.cacheService.set(key, otp, 180 * 1000); return otp; } //