diff --git a/database/seeders/admin.seeder.ts b/database/seeders/admin.seeder.ts index d5f0e8a..1fc2b34 100644 --- a/database/seeders/admin.seeder.ts +++ b/database/seeders/admin.seeder.ts @@ -1,4 +1,5 @@ import { Logger } from "@nestjs/common"; +import { hash } from "bcrypt"; import { DataSource, DeepPartial } from "typeorm"; import { Role } from "../../src/modules/users/entities/role.entity"; @@ -23,7 +24,9 @@ export const seedAdmin = async (dataSource: DataSource, logger: Logger) => { const adminRole = await roleRepo.findOneBy({ name: RoleEnum.ADMIN }); if (!adminRole) throw new Error("Role not found"); - const admin = userRepo.create({ ...defaultAdmin, role: adminRole }); + const hashedPassword = await hash(defaultAdmin.password!, 10); + + const admin = userRepo.create({ ...defaultAdmin, password: hashedPassword, role: adminRole }); await userRepo.save(admin); logger.log("admin created successfully"); } catch (error) { diff --git a/src/common/DTO/pagination.dto.ts b/src/common/DTO/pagination.dto.ts new file mode 100644 index 0000000..6dd4287 --- /dev/null +++ b/src/common/DTO/pagination.dto.ts @@ -0,0 +1,22 @@ +import { ApiPropertyOptional } from "@nestjs/swagger"; +import { Type } from "class-transformer"; +import { IsNotEmpty, IsNumber, IsOptional, Max, Min } from "class-validator"; + +export class PaginationDto { + @IsOptional() + @IsNotEmpty() + @IsNumber() + @Min(1) + @Type(() => Number) + @ApiPropertyOptional({ type: "number", required: false }) + page?: number; + + @IsOptional() + @IsNotEmpty() + @IsNumber() + @Min(1) + @Max(50) + @Type(() => Number) + @ApiPropertyOptional({ type: "number", required: false }) + limit?: number; +} diff --git a/src/common/DTO/param.dto.ts b/src/common/DTO/param.dto.ts new file mode 100644 index 0000000..32d9674 --- /dev/null +++ b/src/common/DTO/param.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsNotEmpty, IsUUID } from "class-validator"; + +import { CommonMessage } from "../enums/message.enum"; + +export class ParamDto { + @IsNotEmpty({ message: CommonMessage.ID_REQUIRED }) + @IsUUID("4", { message: CommonMessage.ID_SHOULD_BE_UUID }) + @ApiProperty({ description: "Id of the entity", example: "8b1e8b1e-8b1e-8b1e-8b1e-8b1e8b1e8b1e" }) + id: string; +} diff --git a/src/common/decorators/pagination.decorator.ts b/src/common/decorators/pagination.decorator.ts new file mode 100644 index 0000000..f416721 --- /dev/null +++ b/src/common/decorators/pagination.decorator.ts @@ -0,0 +1,9 @@ +import { applyDecorators } from "@nestjs/common"; +import { ApiQuery } from "@nestjs/swagger"; + +export function Pagination() { + return applyDecorators( + ApiQuery({ name: "page", example: 1, required: false, type: "number" }), + ApiQuery({ name: "limit", example: 10, required: false, type: "number" }), + ); +} diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index f7ce2cf..9cbd211 100644 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -68,6 +68,8 @@ export const enum CommonMessage { VALID_FOR_CHOOSE = "معتبر برای انتخاب", UPDATE_SUCCESS = "با موفقیت به روز رسانی شد", CREATED = "با موفقیت ایجاد شد", + ID_REQUIRED = "شناسه مورد نیاز است", + ID_SHOULD_BE_UUID = "شناسه باید یک یو یو آی دی باشد", } export const enum CategoryMessage { @@ -79,6 +81,40 @@ export const enum CategoryMessage { PARENT_ID_REQUIRED = "شناسه والد دسته بندی مورد نیاز است", PARENT_ID_SHOULD_BE_UUID = "شناسه والد باید یک یو یو آی دی باشد", TITLE_EXIST = "دسته بندی با این نام قبلا ثبت شده است", + IS_ACTIVE_REQUIRED = "وضعیت دسته بندی مورد نیاز است", + CAT_ID_SHOULD_BE_UUID = "شناسه دسته بندی باید یک یو یو آی دی باشد", + CAT_ID_NOT_EMPTY = "شناسه دسته بندی نمیتواند خالی باشد", + CATEGORY_NOT_EXIST = "دسته بندی مورد نظر یافت نشد", + IS_ACTIVE_SHOULD_BE_BOOLEAN = "وضعیت دسته بندی باید یک بولین باشد", + SEARCH_QUERY_STRING = "رشته جستجو باید یک رشته باشد", +} + +export const enum ServiceMessage { + NAME_REQUIRED = "نام سرویس مورد نیاز است", + NAME_STRING = "نام سرویس باید یک رشته باشد", + NAME_LENGTH = "نام سرویس باید بین ۳ تا ۵۰ کاراکتر باشد", + DESCRIPTION_REQUIRED = "توضیحات سرویس مورد نیاز است", + DESCRIPTION_STRING = "توضیحات سرویس باید یک رشته باشد", + AUTHOR_REQUIRED = "نویسنده سرویس مورد نیاز است", + AUTHOR_STRING = "نویسنده سرویس باید یک رشته باشد", + CREATE_DATE_REQUIRED = "تاریخ ایجاد سرویس مورد نیاز است", + CREATE_DATE_STRING = "تاریخ ایجاد سرویس باید یک رشته باشد", + USER_COUNT_REQUIRED = "تعداد کاربران سرویس مورد نیاز است", + USER_COUNT_INT = "تعداد کاربران سرویس باید یک عدد صحیح باشد", + SERVICE_LANGUAGE_REQUIRED = "زبان سرویس مورد نیاز است", + SOFTWARE_LANGUAGE_REQUIRED = "زبان نرم افزار سرویس مورد نیاز است", + SOFTWARE_LANGUAGE_STRING = "زبان نرم افزار سرویس باید یک رشته باشد", + META_DESCRIPTION_REQUIRED = "متا توضیحات سرویس مورد نیاز است", + LINK_REQUIRED = "لینک سرویس مورد نیاز است", + IS_DANAK_SUGGEST_REQUIRED = "آیا سرویس پیشنهادی داناک است؟", + IS_DANAK_SUGGEST_BOOLEAN = "وضعیت سرویس پیشنهادی داناک باید یک بولین باشد", + ICON_REQUIRED = "آیکون سرویس مورد نیاز است", + LINK_SHOULD_BE_URL = "لینک باید یک آدرس یو آر ال باشد", + ICON_SHOULD_BE_URL = "آیکون باید یک آدرس یو آر ال باشد", + IMAGES_REQUIRED = "تصاویر سرویس مورد نیاز است", + DESCRIPTION_LENGTH = "توضیحات سرویس باید بین ۱۰ تا ۵۰۰ کاراکتر باشد", + SOFTWARE_LANGUAGE_LENGTH = "زبان نرم افزار سرویس باید بین ۳ تا ۱۵۰ کاراکتر باشد", + AUTHOR_LENGTH = "نویسنده سرویس باید بین 3 تا 100 کاراکتر باشد", } export const enum AnnouncementMessage { diff --git a/src/modules/auth/guards/role.guard.ts b/src/modules/auth/guards/role.guard.ts index fd3356e..cb13e1c 100644 --- a/src/modules/auth/guards/role.guard.ts +++ b/src/modules/auth/guards/role.guard.ts @@ -19,7 +19,7 @@ export class RoleGuard implements CanActivate { if (!user) throw new ForbiddenException(AuthMessage.UNAUTHORIZED_ACCESS); - const hasRequiredRole = requiredRole.includes(user.role.name); + const hasRequiredRole = requiredRole.includes(user.role as unknown as RoleEnum); if (!hasRequiredRole) throw new ForbiddenException(AuthMessage.UNAUTHORIZED_ACCESS); return true; diff --git a/src/modules/auth/providers/auth.service.ts b/src/modules/auth/providers/auth.service.ts index 3257195..5664574 100644 --- a/src/modules/auth/providers/auth.service.ts +++ b/src/modules/auth/providers/auth.service.ts @@ -98,11 +98,14 @@ export class AuthService { } //****************** */ - async requestLoginOtp(requestOtpDto: RequestOtpDto) { + async requestLoginOtp(requestOtpDto: RequestOtpDto, isAdmin: boolean = false) { const { phone } = requestOtpDto; const user = await this.usersService.findOneWithPhone(phone); if (!user) throw new BadRequestException(AuthMessage.PHONE_NOT_FOUND); + //check the if the method call is from admin or not + if (isAdmin && user.role.name !== RoleEnum.ADMIN) throw new BadRequestException(AuthMessage.NOT_ADMIN); + const existCode = await this.otpService.checkExistOtp(phone, "LOGIN"); if (existCode) { return { diff --git a/src/modules/danak-services/DTO/category-search-query.dto.ts b/src/modules/danak-services/DTO/category-search-query.dto.ts new file mode 100644 index 0000000..2b887f7 --- /dev/null +++ b/src/modules/danak-services/DTO/category-search-query.dto.ts @@ -0,0 +1,26 @@ +import { ApiPropertyOptional } from "@nestjs/swagger"; +import { Type } from "class-transformer"; +import { IsOptional, IsString, IsUUID } from "class-validator"; + +import { PaginationDto } from "../../../common/DTO/pagination.dto"; +import { CategoryMessage } from "../../../common/enums/message.enum"; + +export class CategorySearchQueryDto { + @IsOptional() + @IsUUID("4", { message: CategoryMessage.PARENT_ID_SHOULD_BE_UUID }) + @ApiPropertyOptional({ description: "Parent category id", example: "8b1e8b1e-8b1e-8b1e-8b1e-8b1e8b1e8b1e" }) + parentId?: string; +} + +export class CategoryListSearchQueryDto extends PaginationDto { + @IsOptional() + @Type(() => Number) + // @IsBoolean({ message: CategoryMessage.IS_ACTIVE_SHOULD_BE_BOOLEAN }) + @ApiPropertyOptional({ description: "Category status", example: 1 }) + isActive?: number; + + @IsOptional() + @IsString({ message: CategoryMessage.SEARCH_QUERY_STRING }) + @ApiPropertyOptional({ description: "Search query", example: "search query" }) + q?: string; +} diff --git a/src/modules/danak-services/DTO/create-category.dto.ts b/src/modules/danak-services/DTO/create-category.dto.ts index 45ebf43..1bea5fe 100644 --- a/src/modules/danak-services/DTO/create-category.dto.ts +++ b/src/modules/danak-services/DTO/create-category.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; -import { IsNotEmpty, IsOptional, IsString, IsUUID, IsUrl, Length } from "class-validator"; +import { IsBoolean, IsNotEmpty, IsOptional, IsString, IsUUID, IsUrl, Length } from "class-validator"; import { CategoryMessage } from "../../../common/enums/message.enum"; @@ -15,6 +15,11 @@ export class CreateCategoryDto { @ApiProperty({ description: "Category icon", example: "https://example.com/icon.png" }) icon: string; + @IsNotEmpty({ message: CategoryMessage.IS_ACTIVE_REQUIRED }) + @IsBoolean({ message: CategoryMessage.IS_ACTIVE_REQUIRED }) + @ApiProperty({ description: "Category status", example: true }) + isActive: boolean; + @IsOptional() @IsNotEmpty({ message: CategoryMessage.PARENT_ID_REQUIRED }) @IsUUID("4", { message: CategoryMessage.PARENT_ID_SHOULD_BE_UUID }) diff --git a/src/modules/danak-services/DTO/create-service.dto.ts b/src/modules/danak-services/DTO/create-service.dto.ts new file mode 100644 index 0000000..b73219d --- /dev/null +++ b/src/modules/danak-services/DTO/create-service.dto.ts @@ -0,0 +1,76 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsBoolean, IsDateString, IsEnum, IsInt, IsNotEmpty, IsString, IsUUID, IsUrl, Length, MaxLength } from "class-validator"; + +import { CategoryMessage, ServiceMessage } from "../../../common/enums/message.enum"; +import { ServicesLanguage } from "../enums/services-language.enum"; + +export class CreateServiceDto { + @IsNotEmpty({ message: CategoryMessage.CAT_ID_NOT_EMPTY }) + @IsUUID("4", { message: CategoryMessage.CAT_ID_SHOULD_BE_UUID }) + @ApiProperty({ description: "The category id of the service", example: "00000000-0000-0000-0000-000000000000" }) + categoryId: string; + + @IsNotEmpty({ message: ServiceMessage.NAME_REQUIRED }) + @IsString({ message: ServiceMessage.NAME_STRING }) + @Length(3, 50, { message: ServiceMessage.NAME_LENGTH }) + @ApiProperty({ description: "The name of the service", example: "Service Name" }) + name: string; + + @IsNotEmpty({ message: ServiceMessage.DESCRIPTION_REQUIRED }) + @IsString({ message: ServiceMessage.DESCRIPTION_STRING }) + @Length(10, 500, { message: ServiceMessage.DESCRIPTION_LENGTH }) + @ApiProperty({ description: "The description of the service", example: "Service Description" }) + description: string; + + @IsNotEmpty({ message: ServiceMessage.AUTHOR_REQUIRED }) + @IsString({ message: ServiceMessage.AUTHOR_STRING }) + @Length(3, 100, { message: ServiceMessage.AUTHOR_LENGTH }) + @ApiProperty({ description: "The author of the service", example: "danak corp" }) + author: string; + + @IsNotEmpty({ message: ServiceMessage.CREATE_DATE_REQUIRED }) + @IsDateString({}, { message: ServiceMessage.CREATE_DATE_STRING }) + @ApiProperty({ description: "The create date of the service", example: "2021-08-01T00:00:00.000Z" }) + createDate: Date; + + @IsNotEmpty({ message: ServiceMessage.USER_COUNT_REQUIRED }) + @IsInt({ message: ServiceMessage.USER_COUNT_INT }) + @ApiProperty({ description: "The user count of the service", example: 1000 }) + userCount: number; + + @IsNotEmpty({ message: ServiceMessage.SERVICE_LANGUAGE_REQUIRED }) + @IsEnum(ServicesLanguage) + @ApiProperty({ enum: ServicesLanguage, description: "The language of the service", example: ServicesLanguage.ENGLISH }) + serviceLanguage: ServicesLanguage; + + @IsNotEmpty({ message: ServiceMessage.SOFTWARE_LANGUAGE_REQUIRED }) + @IsString({ message: ServiceMessage.SOFTWARE_LANGUAGE_STRING }) + @MaxLength(150, { message: ServiceMessage.SOFTWARE_LANGUAGE_LENGTH }) + @ApiProperty({ description: "The software language of the service", example: "Python" }) + softwareLanguage: string; + + @IsNotEmpty({ message: ServiceMessage.META_DESCRIPTION_REQUIRED }) + @IsString({ message: ServiceMessage.META_DESCRIPTION_REQUIRED }) + @ApiProperty({ description: "The meta description of the service", example: "Service Meta Description" }) + metaDescription: string; + + @IsNotEmpty({ message: ServiceMessage.LINK_REQUIRED }) + @IsUrl({ protocols: ["http", "https"], require_protocol: true }, { message: ServiceMessage.LINK_SHOULD_BE_URL }) + @ApiProperty({ description: "The status of the service", example: "https://example.com" }) + link: string; + + @IsNotEmpty({ message: ServiceMessage.IS_DANAK_SUGGEST_REQUIRED }) + @IsBoolean({ message: ServiceMessage.IS_DANAK_SUGGEST_BOOLEAN }) + @ApiProperty({ description: "Is the service a Danak suggest?", example: true }) + isDanakSuggest: boolean; + + @IsNotEmpty({ message: ServiceMessage.ICON_REQUIRED }) + @IsUrl({ protocols: ["http", "https"], require_protocol: true }, { message: ServiceMessage.ICON_SHOULD_BE_URL }) + @ApiProperty({ description: "The icon url of the service", example: "https://example.com/icon.png" }) + icon: string; + + @IsNotEmpty({ message: ServiceMessage.IMAGES_REQUIRED }) + @IsString({ each: true }) + @ApiProperty({ description: "The images of the service", example: ["https://example.com/image1.png", "https://example.com/image2.png"] }) + images: string[]; +} diff --git a/src/modules/danak-services/danak-services.controller.ts b/src/modules/danak-services/danak-services.controller.ts index 0f9e773..7e3bd1f 100644 --- a/src/modules/danak-services/danak-services.controller.ts +++ b/src/modules/danak-services/danak-services.controller.ts @@ -1,18 +1,61 @@ -import { Body, Controller, Post } from "@nestjs/common"; +import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Query } from "@nestjs/common"; import { ApiOperation, ApiTags } from "@nestjs/swagger"; +import { CategoryListSearchQueryDto, CategorySearchQueryDto } from "./DTO/category-search-query.dto"; import { CreateCategoryDto } from "./DTO/create-category.dto"; +import { CreateServiceDto } from "./DTO/create-service.dto"; import { DanakServicesService } from "./providers/danak-services.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; +import { Pagination } from "../../common/decorators/pagination.decorator"; +import { Roles } from "../../common/decorators/roles.decorator"; +import { ParamDto } from "../../common/DTO/param.dto"; +import { RoleEnum } from "../users/enums/role.enum"; @Controller("services") @ApiTags("Danak-Services") export class DanakServicesController { constructor(private readonly danakServicesService: DanakServicesService) {} + + // @AuthGuards() + @Roles(RoleEnum.ADMIN) @ApiOperation({ summary: "Create a new service category" }) @Post("category") createCategory(@Body() createDto: CreateCategoryDto) { return this.danakServicesService.createCategory(createDto); } + + @AuthGuards() + @Roles(RoleEnum.ADMIN) + @ApiOperation({ summary: "Get all service categories" }) + @Get("category") + getCategories(@Query() queryDto: CategorySearchQueryDto) { + return this.danakServicesService.getCategories(queryDto); + } + + @AuthGuards() + @Roles(RoleEnum.ADMIN) + @ApiOperation({ summary: "Get all service categories" }) + @Pagination() + @Get("category-list") + getCategoryList(@Query() queryDto: CategoryListSearchQueryDto) { + return this.danakServicesService.getCategoryList(queryDto); + } + + @AuthGuards() + @Roles(RoleEnum.ADMIN) + @ApiOperation({ summary: "toggle status of categories" }) + @HttpCode(HttpStatus.OK) + @Post("category/toggle-status/:id") + toggleCategoryStatus(@Param() paramDto: ParamDto) { + return this.danakServicesService.toggleCategoryStatus(paramDto); + } + + @AuthGuards() + @Roles(RoleEnum.ADMIN) + @ApiOperation({ summary: "create new danak services" }) + @Post() + createService(@Body() createDto: CreateServiceDto) { + return this.danakServicesService.createService(createDto); + } } diff --git a/src/modules/danak-services/entities/danak-service-category.entity.ts b/src/modules/danak-services/entities/danak-service-category.entity.ts index 61637e8..b87bf07 100644 --- a/src/modules/danak-services/entities/danak-service-category.entity.ts +++ b/src/modules/danak-services/entities/danak-service-category.entity.ts @@ -11,6 +11,9 @@ export class DanakServiceCategory extends BaseEntity { @Column({ type: "varchar", length: 150, nullable: false }) icon: string; + @Column({ type: "boolean", default: true }) + isActive: boolean; + @ManyToOne(() => DanakServiceCategory, (category) => category.children) @JoinColumn({ name: "parentId" }) parent: DanakServiceCategory; @@ -19,7 +22,7 @@ export class DanakServiceCategory extends BaseEntity { children: DanakServiceCategory[]; @Column({ nullable: true }) - parentId: string; + parentId: string | null; @OneToMany(() => DanakService, (danakService) => danakService.category) danakService: DanakService[]; diff --git a/src/modules/danak-services/entities/danak-service.entity.ts b/src/modules/danak-services/entities/danak-service.entity.ts index beb6562..0c56cb3 100644 --- a/src/modules/danak-services/entities/danak-service.entity.ts +++ b/src/modules/danak-services/entities/danak-service.entity.ts @@ -8,9 +8,12 @@ import { ServicesLanguage } from "../enums/services-language.enum"; @Entity() export class DanakService extends BaseEntity { - @Column({ type: "varchar", length: 255, nullable: false }) + @Column({ type: "varchar", length: 100, nullable: false }) name: string; + @Column({ type: "boolean", nullable: false, default: false }) + isDanakSuggest: boolean; + @Column({ type: "text", nullable: false }) description: string; @@ -41,7 +44,7 @@ export class DanakService extends BaseEntity { @Column({ type: "varchar", length: 255, nullable: false }) icon: string; - @OneToMany(() => DanakServiceImage, (danakServiceImage) => danakServiceImage.danakService, { eager: true }) + @OneToMany(() => DanakServiceImage, (danakServiceImage) => danakServiceImage.danakService, { eager: true, cascade: true }) images: DanakServiceImage[]; @ManyToOne(() => DanakServiceCategory, (danakServiceCategory) => danakServiceCategory.danakService, { diff --git a/src/modules/danak-services/providers/danak-services.service.ts b/src/modules/danak-services/providers/danak-services.service.ts index 06710a7..e948a53 100644 --- a/src/modules/danak-services/providers/danak-services.service.ts +++ b/src/modules/danak-services/providers/danak-services.service.ts @@ -1,17 +1,27 @@ -import { BadRequestException, Injectable, Logger } from "@nestjs/common"; +import { BadRequestException, Injectable } from "@nestjs/common"; +import { FindOptionsWhere, IsNull } from "typeorm"; +import { ParamDto } from "../../../common/DTO/param.dto"; import { CategoryMessage, CommonMessage } from "../../../common/enums/message.enum"; +import { PaginationUtils } from "../../utils/providers/pagination.utils"; +import { CategoryListSearchQueryDto, CategorySearchQueryDto } from "../DTO/category-search-query.dto"; import { CreateCategoryDto } from "../DTO/create-category.dto"; +import { CreateServiceDto } from "../DTO/create-service.dto"; +import { DanakServiceCategory } from "../entities/danak-service-category.entity"; import { DanakServicesCategoryRepository } from "../repositories/danak-services-category.repository"; +import { DanakServicesRepository } from "../repositories/danak-services.repository"; @Injectable() export class DanakServicesService { - private readonly logger = new Logger(DanakServicesService.name); - constructor(private readonly danakServicesCategoryRepository: DanakServicesCategoryRepository) {} - /********** */ + // private readonly logger = new Logger(DanakServicesService.name); + constructor( + private readonly danakServicesRepository: DanakServicesRepository, + private readonly danakServicesCategoryRepository: DanakServicesCategoryRepository, + ) {} + /******************************************** */ + async createCategory(createDto: CreateCategoryDto) { - this.logger.log(`Creating a new category with title: ${createDto.title}`); - const existCategory = await this.danakServicesCategoryRepository.findOneBy({ title: createDto.title }); + const existCategory = await this.danakServicesCategoryRepository.findOneByTitle(createDto.title); if (existCategory) throw new BadRequestException(CategoryMessage.TITLE_EXIST); const category = this.danakServicesCategoryRepository.create(createDto); await this.danakServicesCategoryRepository.save(category); @@ -20,5 +30,81 @@ export class DanakServicesService { category, }; } - /********** */ + /******************************************** */ + async getCategories(queryDto: CategorySearchQueryDto) { + const where: FindOptionsWhere = { + isActive: true, + }; + + if (queryDto.parentId === undefined) { + where.parentId = IsNull(); + } else { + // If parentId is provided, use it + where.parentId = queryDto.parentId; + } + + const categories = await this.danakServicesCategoryRepository.find({ + where, + order: { createdAt: "ASC" }, + }); + + return { categories }; + } + + /******************************************** */ + async getCategoryList(queryDto: CategoryListSearchQueryDto) { + const { limit, skip } = PaginationUtils(queryDto); + const queryBuilder = this.danakServicesCategoryRepository + .createQueryBuilder("category") + .loadRelationCountAndMap("category.servicesCount", "category.danakService") + .loadRelationCountAndMap("category.childrenCount", "category.children"); + + if (queryDto.isActive !== undefined) { + queryBuilder.andWhere("category.isActive = :isActive", { isActive: queryDto.isActive === 1 }); + } + + if (queryDto.q) { + queryBuilder.andWhere("category.title LIKE :q", { q: `%${queryDto.q}%` }); + } + + queryBuilder + .leftJoinAndSelect("category.parent", "parent") + .select(["category", "parent.id", "parent.title"]) + .orderBy("category.createdAt", "DESC"); + + const [categories, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount(); + + return { categories, count }; + } + /******************************************** */ + + async toggleCategoryStatus(paramDto: ParamDto) { + const category = await this.danakServicesCategoryRepository.findOneById(paramDto.id); + if (!category) throw new BadRequestException(CategoryMessage.CATEGORY_NOT_EXIST); + category.isActive = !category.isActive; + await this.danakServicesCategoryRepository.save(category); + return { + message: CommonMessage.UPDATE_SUCCESS, + isActive: category.isActive, + }; + } + /******************************************** */ + async createService(createDto: CreateServiceDto) { + const category = await this.danakServicesCategoryRepository.findOneById(createDto.categoryId); + if (!category) throw new BadRequestException(CategoryMessage.CATEGORY_NOT_EXIST); + + const images = createDto.images.map((image) => { + return { imageUrl: image }; + }); + + const service = this.danakServicesRepository.create({ ...createDto, category, images }); + await this.danakServicesRepository.save(service); + return { + message: CommonMessage.CREATED, + service, + }; + } + + /******************************************** */ + /******************************************** */ } diff --git a/src/modules/danak-services/repositories/danak-services-category.repository.ts b/src/modules/danak-services/repositories/danak-services-category.repository.ts index 712b1c1..3337db0 100644 --- a/src/modules/danak-services/repositories/danak-services-category.repository.ts +++ b/src/modules/danak-services/repositories/danak-services-category.repository.ts @@ -9,4 +9,12 @@ export class DanakServicesCategoryRepository extends Repository) { super(danakServicesCategoryRepository.target, danakServicesCategoryRepository.manager, danakServicesCategoryRepository.queryRunner); } + + async findOneByTitle(title: string): Promise { + return this.findOneBy({ title }); + } + + async findOneById(id: string): Promise { + return this.findOneBy({ id }); + } } diff --git a/src/modules/users/providers/users.service.ts b/src/modules/users/providers/users.service.ts index c3d64f4..38a66bf 100644 --- a/src/modules/users/providers/users.service.ts +++ b/src/modules/users/providers/users.service.ts @@ -2,7 +2,6 @@ import { BadRequestException, Injectable, Logger } from "@nestjs/common"; import slugify from "slugify"; import { In, Not } from "typeorm"; -import { RoleRepository } from "../repositories/roles.repository"; // import { roles } from "../../../../database/seeders/role.seeder"; import { CommonMessage, UserMessage } from "../../../common/enums/message.enum"; import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto"; @@ -12,6 +11,7 @@ import { CreateUserGroupDto } from "../DTO/user-group.dto"; import { User } from "../entities/user.entity"; import { RoleEnum } from "../enums/role.enum"; import { ValidityType } from "../enums/validity-type.enum"; +import { RoleRepository } from "../repositories/roles.repository"; import { UserRepository } from "../repositories/users.repository"; @Injectable() diff --git a/src/modules/utils/providers/pagination.utils.ts b/src/modules/utils/providers/pagination.utils.ts new file mode 100644 index 0000000..fd29c31 --- /dev/null +++ b/src/modules/utils/providers/pagination.utils.ts @@ -0,0 +1,17 @@ +import { PaginationDto } from "../../../common/DTO/pagination.dto"; + +export function PaginationUtils(paginationData: PaginationDto) { + const { limit = 10, page = 1 } = paginationData; + + // const pageN = Number.parseInt(page) || 1; + // const limitN = Number.parseInt(limit) || 10; + + const skip = (page - 1) * limit; + + return { + // page: page, + // limit: limit > 50 ? 50 : limit, + limit: limit, + skip: skip, + }; +}