From acd005b6cd6ed5b794ccd126e62ddd55276ab23c Mon Sep 17 00:00:00 2001 From: mahyargdz Date: Sun, 4 May 2025 16:58:47 +0330 Subject: [PATCH] update: add new route to get service with slug --- src/common/enums/message.enum.ts | 6 +-- .../DTO/get-service-by-slug-param.dto.ts | 11 +++++ .../danak-services.controller.ts | 13 ++++++ .../providers/danak-services.service.ts | 23 ++++++++++ .../repositories/danak-services.repository.ts | 46 +++++++++++++++++++ 5 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 src/modules/danak-services/DTO/get-service-by-slug-param.dto.ts diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index a546484..6926222 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -198,9 +198,9 @@ export const enum ServiceMessage { SHOW_IN_SLIDER_BOOLEAN = "وضعیت نمایش در اسلایدر سرویس باید یک بولین باشد", COVER_URL_REQUIRED = "آدرس عکس کاور سرویس مورد نیاز است", COVER_URL_SHOULD_BE_URL = "آدرس عکس کاور سرویس باید یک آدرس یو آر ال باشد", - SLUG_REQUIRED = "شناسه منحصر به فرد سرویس مورد نیاز است", - SLUG_STRING = "شناسه منحصر به فرد سرویس باید یک رشته باشد", - SLUG_LENGTH = "شناسه منحصر به فرد سرویس باید بین ۳ تا ۱۵۰ کاراکتر باشد", + SLUG_REQUIRED = "اسلاگ سرویس مورد نیاز است", + SLUG_STRING = "اسلاگ سرویس باید یک رشته باشد", + SLUG_LENGTH = "اسلاگ سرویس باید بین ۳ تا ۱۵۰ کاراکتر باشد", SERVICE_ALREADY_EXISTS_WITH_THIS_SLUG = "سرویسی با این اسلاگ قبلا ثبت شده است", } diff --git a/src/modules/danak-services/DTO/get-service-by-slug-param.dto.ts b/src/modules/danak-services/DTO/get-service-by-slug-param.dto.ts new file mode 100644 index 0000000..2ec7b38 --- /dev/null +++ b/src/modules/danak-services/DTO/get-service-by-slug-param.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsNotEmpty, IsString, Length } from "class-validator"; + +import { ServiceMessage } from "../../../common/enums/message.enum"; +export class GetServiceBySlugParamDto { + @IsNotEmpty({ message: ServiceMessage.SLUG_REQUIRED }) + @IsString({ message: ServiceMessage.SLUG_STRING }) + @Length(3, 150, { message: ServiceMessage.SLUG_LENGTH }) + @ApiProperty({ description: "slug of the service", example: "service-slug" }) + slug: string; +} diff --git a/src/modules/danak-services/danak-services.controller.ts b/src/modules/danak-services/danak-services.controller.ts index c982026..159e8f9 100755 --- a/src/modules/danak-services/danak-services.controller.ts +++ b/src/modules/danak-services/danak-services.controller.ts @@ -7,6 +7,7 @@ import { CreateDanakServiceCategoryDto } from "./DTO/create-category.dto"; import { CreateServiceDto } from "./DTO/create-service.dto"; import { DanakServiceReviewQueryDto } from "./DTO/danak-service-review-query.dto"; import { DanakServicesGlobalSearchDto, DanakServicesQueryDto, DanakServicesSearchQueryDto } from "./DTO/danak-services-search-query.dto"; +import { GetServiceBySlugParamDto } from "./DTO/get-service-by-slug-param.dto"; import { UpdateDanakServiceCategoryDto } from "./DTO/update-category.dto"; import { UpdateReviewStatusDto } from "./DTO/update-review-status.dto"; import { UpdateServiceDto } from "./DTO/update-service.dto"; @@ -156,12 +157,24 @@ export class DanakServicesController { return this.danakServicesService.getDanakServiceByIdWithSubs(paramDto.id, isAdmin, userId); } + @ApiOperation({ summary: "get danakService by slug" }) + @Get("slug/:slug") + getServiceBySlug(@Param() paramDto: GetServiceBySlugParamDto, @UserDec("isAdmin") isAdmin: boolean, @UserDec("id") userId: string) { + return this.danakServicesService.getServiceBySlugWithSubs(paramDto.slug, isAdmin, userId); + } + @ApiOperation({ summary: "get danakService by id public" }) @Get(":id/public") getDanakServiceByIdPublic(@Param() paramDto: ParamDto) { return this.danakServicesService.getDanakServiceByIdPublic(paramDto.id); } + @ApiOperation({ summary: "get danakService by slug public" }) + @Get("slug/:slug/public") + getServiceBySlugPublic(@Param() paramDto: GetServiceBySlugParamDto) { + return this.danakServicesService.getServiceBySlugPublic(paramDto.slug); + } + @ApiOperation({ summary: "update danak service" }) @AuthGuards() @PermissionsDec(PermissionEnum.SERVICES) diff --git a/src/modules/danak-services/providers/danak-services.service.ts b/src/modules/danak-services/providers/danak-services.service.ts index fefa122..b1c822e 100755 --- a/src/modules/danak-services/providers/danak-services.service.ts +++ b/src/modules/danak-services/providers/danak-services.service.ts @@ -441,6 +441,21 @@ export class DanakServicesService { }; } /******************************************** */ + async getServiceBySlugWithSubs(slug: string, isAdmin: boolean, userId: string) { + const danakService = await this.danakServicesRepository.getServiceBySlug(slug, isAdmin); + + if (!danakService) throw new BadRequestException(ServiceMessage.SERVICE_NOT_FOUND_BY_ID); + + const averageRating = await this.danakServiceReviewRepository.getAverageRating(danakService.id); + + const hasSubscription = await this.checkUserPurchasedService(userId, danakService.id); + return { + danakService, + averageRating, + purchased: !!hasSubscription, + }; + } + /******************************************** */ async getDanakServiceByIdPublic(serviceId: string) { const danakService = await this.danakServicesRepository.getDanakServiceById(serviceId, false); @@ -450,6 +465,14 @@ export class DanakServicesService { return { danakService, averageRating }; } /******************************************** */ + async getServiceBySlugPublic(slug: string) { + const danakService = await this.danakServicesRepository.getServiceBySlug(slug, false); + if (!danakService) throw new BadRequestException(ServiceMessage.SERVICE_NOT_FOUND_BY_ID); + const averageRating = await this.danakServiceReviewRepository.getAverageRating(danakService.id); + + return { danakService, averageRating }; + } + /******************************************** */ async getServiceUsers(serviceId: string) { await this.findServiceById(serviceId); diff --git a/src/modules/danak-services/repositories/danak-services.repository.ts b/src/modules/danak-services/repositories/danak-services.repository.ts index adbc6f5..04fd586 100755 --- a/src/modules/danak-services/repositories/danak-services.repository.ts +++ b/src/modules/danak-services/repositories/danak-services.repository.ts @@ -135,6 +135,52 @@ export class DanakServicesRepository extends Repository { return serviceQueryBuilder.getOne(); } //+******************** + async getServiceBySlug(slug: string, isAdmin: boolean) { + const serviceQueryBuilder = this.createQueryBuilder("service") + .where("service.deletedAt IS NULL") + .leftJoin("service.category", "category") + .addSelect(["category.id", "category.title"]) + .leftJoinAndSelect("service.images", "images") + .leftJoinAndSelect("service.subscriptionPlans", "subscriptionPlans") + .leftJoinAndSelect("subscriptionPlans.directDiscount", "directDiscount", "directDiscount.id IS NOT NULL") + .leftJoin("service.reviews", "reviews", "reviews.status = :reviewStatus", { reviewStatus: ReviewStatus.APPROVED }) + .leftJoin("reviews.user", "user") + .addSelect([ + "reviews.id", + "reviews.comment", + "reviews.rating", + "reviews.title", + "reviews.createdAt", + "reviews.status", + "user.id", + "user.firstName", + "user.lastName", + "user.profilePic", + ]) + // .loadRelationCountAndMap("service.reviewCount", "service.reviews") + // .addSelect( + // (subQuery) => + // subQuery + // .select("CAST(AVG(rating) AS DECIMAL(10,2))", "averageRating") + // .from(DanakServiceReview, "r") + // .where("r.serviceId = service.id") + // .andWhere("r.status = :reviewStatus", { reviewStatus: ReviewStatus.APPROVED }), + // "averageRating", + // ) + .andWhere("service.slug = :slug", { slug }) + .groupBy("service.id,category.id, images.id, subscriptionPlans.id, reviews.id, user.id, directDiscount.id"); + + if (!isAdmin) { + serviceQueryBuilder + .andWhere("service.isActive = :isActive", { isActive: true }) + .andWhere("subscriptionPlans.isActive = :planActive", { planActive: true }); + } + + serviceQueryBuilder.orderBy("subscriptionPlans.price", "ASC"); + + return serviceQueryBuilder.getOne(); + } + //+******************** async getUsersByService(serviceId: string) { const users = await this.createQueryBuilder("service") .innerJoin("service.subscriptionPlans", "subscriptionPlans")