chore: add slider module

This commit is contained in:
mahyargdz
2025-05-31 16:49:21 +03:30
parent 4ca50a73bb
commit bc26b59911
11 changed files with 275 additions and 5 deletions
+2
View File
@@ -23,6 +23,7 @@ import { IndustriesModule } from "./modules/industries/industries.module";
import { InvoicesModule } from "./modules/invoices/invoices.module";
import { NotificationModule } from "./modules/notifications/notifications.module";
import { PaymentsModule } from "./modules/payments/payments.module";
import { SlidersModule } from "./modules/sliders/sliders.module";
import { TicketsModule } from "./modules/tickets/tickets.module";
import { UploaderModule } from "./modules/uploader/uploader.module";
import { UsersModule } from "./modules/users/users.module";
@@ -59,6 +60,7 @@ import { UtilsModule } from "./modules/utils/utils.module";
AnnouncementsModule,
CriticismModule,
PaymentsModule,
SlidersModule,
],
})
export class AppModule implements NestModule {
+16
View File
@@ -650,3 +650,19 @@ export const enum BusinessMessage {
DOMAIN_SETUP_TROUBLESHOOTING_5 = "- برخی از ارائه‌دهندگان DNS ممکن است نیاز به تنظیمات خاص برای رکوردهای TXT داشته باشند",
DOMAIN_SETUP_TROUBLESHOOTING_6 = "- اگر از Cloudflare استفاده می‌کنید، مطمئن شوید که رکوردهای TXT را بدون پروکسی تنظیم کرده‌اید",
}
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 = "SLIDER_NOT_FOUND",
}
@@ -7,6 +7,7 @@ import { Company } from "../../companies/entities/company.entity";
import { Criticism } from "../../criticisms/entities/criticism.entity";
import { Industry } from "../../industries/entities/industry.entity";
import { Invoice } from "../../invoices/entities/invoice.entity";
import { Slider } from "../../sliders/entities/slider.entity";
import { TicketCategory } from "../../tickets/entities/ticket-category.entity";
import { Ticket } from "../../tickets/entities/ticket.entity";
import { User } from "../../users/entities/user.entity";
@@ -65,6 +66,9 @@ export class Business extends BaseEntity {
@OneToMany(() => Criticism, (criticism) => criticism.business)
criticisms = new Collection<Criticism>(this);
@OneToMany(() => Slider, (slider) => slider.business)
sliders = new Collection<Slider>(this);
@OneToMany(() => User, (user) => user.business)
users = new Collection<User>(this);
+40
View File
@@ -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;
}
+5
View File
@@ -0,0 +1,5 @@
import { PartialType } from "@nestjs/swagger";
import { CreateSliderDto } from "./create-slider.dto";
export class UpdateSliderDto extends PartialType(CreateSliderDto) {}
+33
View File
@@ -0,0 +1,33 @@
import { Entity, EntityRepositoryType, ManyToOne, Opt, Property } from "@mikro-orm/core";
import { BaseEntity } from "../../../common/entities/base.entity";
import { Business } from "../../businesses/entities/business.entity";
import { SliderRepository } from "../repositories/sliders.repository";
@Entity({ repository: () => SliderRepository })
export class Slider extends BaseEntity {
@Property({ type: "varchar", length: 150 })
title: string;
@Property({ type: "varchar", length: 255, nullable: true })
description?: string;
@Property({ type: "varchar", length: 255 })
imageUrl: string;
@Property({ type: "varchar", length: 255, nullable: true })
link?: string;
@Property({ type: "int", default: 0 })
order: number & Opt;
@Property({ type: "boolean", default: true })
isActive: boolean & Opt;
@ManyToOne(() => Business, { deleteRule: "restrict" })
business!: Business;
//-----------------------------
[EntityRepositoryType]?: SliderRepository;
}
+85
View File
@@ -0,0 +1,85 @@
import { EntityManager } from "@mikro-orm/postgresql";
import { BadRequestException, Injectable } from "@nestjs/common";
import { PaginationDto } from "../../../common/DTO/pagination.dto";
import { CommonMessage, SliderMessage } from "../../../common/enums/message.enum";
import { Business } from "../../businesses/entities/business.entity";
import { CreateSliderDto } from "../DTO/create-slider.dto";
import { UpdateSliderDto } from "../DTO/update-slider.dto";
import { SliderRepository } from "../repositories/sliders.repository";
@Injectable()
export class SlidersService {
constructor(
private readonly slidersRepository: SliderRepository,
private readonly em: EntityManager,
) {}
async create(createSliderDto: CreateSliderDto, business: Business) {
const slider = this.slidersRepository.create({ ...createSliderDto, business });
await this.em.persistAndFlush(slider);
return {
message: CommonMessage.CREATED,
slider,
};
}
//*************************** */
async getSlidersList(queryDto: PaginationDto, businessId: string) {
const [sliders, count] = await this.slidersRepository.getSlidersListForAdmin(queryDto, businessId);
return {
sliders,
count,
paginate: true,
};
}
//************************* */
async getSliderListPublic(businessId: string) {
const sliders = await this.slidersRepository.find({ isActive: true, deletedAt: null, business: { id: businessId } });
return { sliders };
}
//************************* */
async getSliderById(id: string, businessId: string) {
const slider = await this.slidersRepository.findOne({ id, business: { id: businessId }, deletedAt: null });
if (!slider) throw new BadRequestException(SliderMessage.SLIDER_NOT_FOUND);
return {
slider,
};
}
//****************** */
async update(id: string, updateSliderDto: UpdateSliderDto, businessId: string) {
const { slider } = await this.getSliderById(id, businessId);
this.slidersRepository.assign(slider, updateSliderDto);
await this.em.persistAndFlush(slider);
return {
message: CommonMessage.UPDATE_SUCCESS,
};
}
//****************** */
async remove(id: string, businessId: string) {
const { slider } = await this.getSliderById(id, businessId);
slider.deletedAt = new Date();
await this.em.flush();
return {
message: CommonMessage.DELETED,
};
}
//****************** */
async toggleSliderStatus(id: string, businessId: string) {
const { slider } = await this.getSliderById(id, businessId);
slider.isActive = !slider.isActive;
await this.em.flush();
return {
message: CommonMessage.UPDATE_SUCCESS,
};
}
//****************** */
}
+13
View File
@@ -0,0 +1,13 @@
import { EntityRepository } from "@mikro-orm/postgresql";
import { PaginationDto } from "../../../common/DTO/pagination.dto";
import { PaginationUtils } from "../../utils/providers/pagination.utils";
import { Slider } from "../entities/slider.entity";
export class SliderRepository extends EntityRepository<Slider> {
async getSlidersListForAdmin(queryDto: PaginationDto, businessId: string) {
const { skip, limit } = PaginationUtils(queryDto);
return this.findAndCount({ deletedAt: null, business: { id: businessId } }, { offset: skip, limit, orderBy: { order: "ASC" } });
}
}
+57
View File
@@ -0,0 +1,57 @@
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseInterceptors } from "@nestjs/common";
import { ApiOperation } from "@nestjs/swagger";
import { CreateSliderDto } from "./DTO/create-slider.dto";
import { UpdateSliderDto } from "./DTO/update-slider.dto";
import { SlidersService } from "./providers/sliders.service";
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
import { BusinessDec } from "../../common/decorators/business.decorator";
import { SkipAuth } from "../../common/decorators/skip-auth.decorator";
import { PaginationDto } from "../../common/DTO/pagination.dto";
import { ParamDto } from "../../common/DTO/param.dto";
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
import { Business } from "../businesses/entities/business.entity";
@Controller("sliders")
@AuthGuards()
@UseInterceptors(BusinessInterceptor)
export class SlidersController {
constructor(private readonly slidersService: SlidersService) {}
@ApiOperation({ summary: "create slider (admin route)" })
@Post()
create(@Body() createSliderDto: CreateSliderDto, @BusinessDec() business: Business) {
return this.slidersService.create(createSliderDto, business);
}
@ApiOperation({ summary: "Get all sliders (admin route)" })
@Get()
getSlidersList(@Query() queryDto: PaginationDto, @BusinessDec("id") businessId: string) {
return this.slidersService.getSlidersList(queryDto, businessId);
}
@Get("list/public")
@SkipAuth()
@ApiOperation({ summary: "get all slider public" })
getSliderListPublic(@BusinessDec("id") businessId: string) {
return this.slidersService.getSliderListPublic(businessId);
}
@ApiOperation({ summary: "Get a slider by id (admin route)" })
@Get(":id")
getSliderById(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
return this.slidersService.getSliderById(paramDto.id, businessId);
}
@ApiOperation({ summary: "Update a slider (admin route)" })
@Patch(":id")
update(@Param() paramDto: ParamDto, @Body() updateSliderDto: UpdateSliderDto, @BusinessDec("id") businessId: string) {
return this.slidersService.update(paramDto.id, updateSliderDto, businessId);
}
@ApiOperation({ summary: "Delete a slider (admin route)" })
@Delete(":id")
remove(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
return this.slidersService.remove(paramDto.id, businessId);
}
}
+15
View File
@@ -0,0 +1,15 @@
import { MikroOrmModule } from "@mikro-orm/nestjs";
import { Module } from "@nestjs/common";
import { Slider } from "./entities/slider.entity";
import { SlidersService } from "./providers/sliders.service";
import { SlidersController } from "./sliders.controller";
import { BusinessesModule } from "../businesses/businesses.module";
@Module({
imports: [MikroOrmModule.forFeature([Slider]), BusinessesModule],
controllers: [SlidersController],
providers: [SlidersService],
exports: [SlidersService],
})
export class SlidersModule {}
+5 -5
View File
@@ -94,22 +94,22 @@ export class UsersService {
/*******************************/
async updateUser(id: string, updateData: Partial<User>, em: EntityManager) {
const user = await em.findOne(User, { id, deletedAt: null });
async updateUser(userId: string, updateData: Partial<User>, em: EntityManager) {
const user = await em.findOne(User, { id: userId, deletedAt: null });
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
if (updateData.phone) {
const existingUser = await em.findOne(User, { phone: updateData.phone, deletedAt: null, id: { $ne: id } });
const existingUser = await em.findOne(User, { phone: updateData.phone, deletedAt: null, id: { $ne: userId } });
if (existingUser) throw new BadRequestException(UserMessage.PHONE_EXIST);
}
if (updateData.email) {
const existingUser = await em.findOne(User, { email: updateData.email, deletedAt: null, id: { $ne: id } });
const existingUser = await em.findOne(User, { email: updateData.email, deletedAt: null, id: { $ne: userId } });
if (existingUser) throw new BadRequestException(UserMessage.EMAIL_EXIST);
}
if (updateData.nationalCode) {
const existingUser = await em.findOne(User, { nationalCode: updateData.nationalCode, deletedAt: null, id: { $ne: id } });
const existingUser = await em.findOne(User, { nationalCode: updateData.nationalCode, deletedAt: null, id: { $ne: userId } });
if (existingUser) throw new BadRequestException(UserMessage.NATIONAL_CODE_EXIST);
}