fix: bug in the sliders and blog module
This commit is contained in:
@@ -666,3 +666,19 @@ export const enum BlogMessage {
|
|||||||
SEARCH_QUERY_STRING = "رشته جستجو باید یک رشته باشد",
|
SEARCH_QUERY_STRING = "رشته جستجو باید یک رشته باشد",
|
||||||
IS_PINNED_SHOULD_BE_A_BOOLEAN = "وضعیت سنجاق بودن باید یک بولین باشد",
|
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 = "اسلایدر مورد نظر یافت نشد",
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ export function bullMqConfig(): SharedBullAsyncConfiguration {
|
|||||||
connection: {
|
connection: {
|
||||||
url: configService.getOrThrow<string>("REDIS_URI"),
|
url: configService.getOrThrow<string>("REDIS_URI"),
|
||||||
},
|
},
|
||||||
|
prefix: "danak-console",
|
||||||
defaultJobOptions: {
|
defaultJobOptions: {
|
||||||
removeOnComplete: false,
|
removeOnComplete: false,
|
||||||
removeOnFail: false,
|
removeOnFail: false,
|
||||||
attempts: 5, // Reasonable default, adjust based on job criticality
|
attempts: 5,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export function cacheConfig(): CacheModuleAsyncOptions {
|
|||||||
useFactory: async (configService: ConfigService) => {
|
useFactory: async (configService: ConfigService) => {
|
||||||
return {
|
return {
|
||||||
ttl: configService.getOrThrow<string>("CACHE_TTL"),
|
ttl: configService.getOrThrow<string>("CACHE_TTL"),
|
||||||
stores: [new KeyvRedis(configService.getOrThrow<string>("REDIS_URI"))],
|
stores: [new KeyvRedis(configService.getOrThrow<string>("REDIS_URI"), { namespace: "danak-console:" })],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException, Injectable, Logger } from "@nestjs/common";
|
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||||
import { DataSource } from "typeorm";
|
import { DataSource } from "typeorm";
|
||||||
|
|
||||||
import { TokensService } from "./tokens.service";
|
import { TokensService } from "./tokens.service";
|
||||||
@@ -17,7 +17,6 @@ import { VerifyOtpDto } from "../DTO/verify-otp.dto";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
private readonly logger = new Logger(AuthService.name);
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly usersService: UsersService,
|
private readonly usersService: UsersService,
|
||||||
private readonly passwordService: PasswordService,
|
private readonly passwordService: PasswordService,
|
||||||
@@ -44,11 +43,9 @@ export class AuthService {
|
|||||||
const otpCode = await this.otpService.generateAndSetInCache(phone, "REGISTER");
|
const otpCode = await this.otpService.generateAndSetInCache(phone, "REGISTER");
|
||||||
//
|
//
|
||||||
await this.smsService.sendSmsVerifyCode(phone, otpCode);
|
await this.smsService.sendSmsVerifyCode(phone, otpCode);
|
||||||
this.logger.debug(`OTP sent to ${phone}: ${otpCode}`);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: AuthMessage.OTP_SENT,
|
message: AuthMessage.OTP_SENT,
|
||||||
otpCode,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
//****************** */
|
//****************** */
|
||||||
@@ -154,11 +151,9 @@ export class AuthService {
|
|||||||
const otpCode = await this.otpService.generateAndSetInCache(phone, "LOGIN");
|
const otpCode = await this.otpService.generateAndSetInCache(phone, "LOGIN");
|
||||||
//
|
//
|
||||||
await this.smsService.sendSmsVerifyCode(phone, otpCode);
|
await this.smsService.sendSmsVerifyCode(phone, otpCode);
|
||||||
this.logger.debug(`OTP sent to ${phone}: ${otpCode}`);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: AuthMessage.OTP_SENT,
|
message: AuthMessage.OTP_SENT,
|
||||||
otpCode,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ import { BlogsService } from "./providers/blogs.service";
|
|||||||
import { BlogCategoriesRepository } from "./repositories/blog-categories.repository";
|
import { BlogCategoriesRepository } from "./repositories/blog-categories.repository";
|
||||||
import { BlogCommentsRepository } from "./repositories/blog-comments.repository";
|
import { BlogCommentsRepository } from "./repositories/blog-comments.repository";
|
||||||
import { BlogsRepository } from "./repositories/blogs.repository";
|
import { BlogsRepository } from "./repositories/blogs.repository";
|
||||||
|
import { UsersModule } from "../users/users.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([Blog, BlogComment, BlogCategory])],
|
imports: [TypeOrmModule.forFeature([Blog, BlogComment, BlogCategory]), UsersModule],
|
||||||
controllers: [BlogsController],
|
controllers: [BlogsController],
|
||||||
providers: [BlogsService, BlogsRepository, BlogCommentsRepository, BlogCategoriesRepository],
|
providers: [BlogsService, BlogsRepository, BlogCommentsRepository, BlogCategoriesRepository],
|
||||||
exports: [BlogsService],
|
exports: [BlogsService],
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { PartialType } from "@nestjs/swagger";
|
||||||
|
|
||||||
|
import { CreateSliderDto } from "./create-slider.dto";
|
||||||
|
|
||||||
|
export class UpdateSliderDto extends PartialType(CreateSliderDto) {}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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<Slider>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async create(createSliderDto: CreateSliderDto): Promise<Slider> {
|
||||||
|
const slider = this.sliderRepository.create(createSliderDto);
|
||||||
|
return await this.sliderRepository.save(slider);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAll(): Promise<Slider[]> {
|
||||||
|
return await this.sliderRepository.find({
|
||||||
|
order: {
|
||||||
|
order: "ASC",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSliderById(id: string): Promise<Slider> {
|
||||||
|
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<Slider> {
|
||||||
|
const slider = await this.getSliderById(id);
|
||||||
|
Object.assign(slider, updateSliderDto);
|
||||||
|
return await this.sliderRepository.save(slider);
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(id: string): Promise<void> {
|
||||||
|
const slider = await this.getSliderById(id);
|
||||||
|
await this.sliderRepository.remove(slider);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Slider> {
|
||||||
|
constructor(@InjectRepository(Slider) repository: Repository<Slider>) {
|
||||||
|
super(repository.target, repository.manager, repository.queryRunner);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {}
|
||||||
@@ -243,8 +243,11 @@ export class UsersService {
|
|||||||
const existUser = await queryRunner.manager.findOneBy(User, { nationalCode: registerDto.nationalCode });
|
const existUser = await queryRunner.manager.findOneBy(User, { nationalCode: registerDto.nationalCode });
|
||||||
if (existUser) throw new BadRequestException(UserMessage.NATIONAL_CODE_EXIST);
|
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, {
|
const user = queryRunner.manager.create(User, {
|
||||||
...registerDto,
|
...registerDto,
|
||||||
|
userName,
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
roles: [role],
|
roles: [role],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export class OTPService {
|
|||||||
async generateAndSetInCache(identifier: string, cacheKey: OtpCacheKeyType) {
|
async generateAndSetInCache(identifier: string, cacheKey: OtpCacheKeyType) {
|
||||||
const otp = this.generateOTP();
|
const otp = this.generateOTP();
|
||||||
const key = `${identifier}:${cacheKey}:OTP`;
|
const key = `${identifier}:${cacheKey}:OTP`;
|
||||||
await this.cacheService.set(key, otp);
|
await this.cacheService.set(key, otp, 180 * 1000);
|
||||||
return otp;
|
return otp;
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
|
|||||||
Reference in New Issue
Block a user