feat: enhance danak services with feedback system and improved metadata tracking
- Add service feedback system with CRUD operations - Add service guide functionality - Enhance metadata tracking with comprehensive headers (user-agent, referer, language, etc.) - Add new DTOs for feedback and guide management - Add repositories for feedback and guide entities - Update controller with new endpoints for feedback management - Improve service organization and method structure - Add payment and user entity enhancements
This commit is contained in:
@@ -212,6 +212,17 @@ export const enum ServiceMessage {
|
||||
PAGE_DESCRIPTION_REQUIRED = "توضیحات صفحه سرویس مورد نیاز است",
|
||||
PAGE_DESCRIPTION_STRING = "توضیحات صفحه سرویس باید یک رشته باشد",
|
||||
PAGE_DESCRIPTION_LENGTH = "توضیحات صفحه سرویس باید بین ۱۰ تا ۵۰۰ کاراکتر باشد",
|
||||
SERVICE_GUIDE_NOT_EXIST = "راهنمای سرویس مورد نظر یافت نشد",
|
||||
GUIDE_TITLE_STRING = "عنوان راهنمای سرویس باید یک رشته باشد",
|
||||
GUIDE_TITLE_REQUIRED = "عنوان راهنمای سرویس مورد نیاز است",
|
||||
GUIDE_DESCRIPTION_STRING = "توضیحات راهنمای سرویس باید یک رشته باشد",
|
||||
GUIDE_DESCRIPTION_REQUIRED = "توضیحات راهنمای سرویس مورد نیاز است",
|
||||
GUIDE_CONTENT_STRING = "متن راهنمای سرویس باید یک رشته باشد",
|
||||
GUIDE_CONTENT_REQUIRED = "متن راهنمای سرویس مورد نیاز است",
|
||||
GUIDE_ORDER_IS_REQUIRED = "ترتیب راهنمای سرویس مورد نیاز است",
|
||||
GUIDE_ORDER_IS_NUMBER = "ترتیب راهنمای سرویس باید یک عدد صحیح باشد",
|
||||
GUIDE_IS_ACTIVE_BOOLEAN = "وضعیت راهنمای سرویس باید یک بولین باشد",
|
||||
GUIDE_TITLE_LENGTH = "عنوان راهنمای سرویس باید بین ۳ تا ۲۰۰ کاراکتر باشد",
|
||||
}
|
||||
|
||||
export const enum AnnouncementMessage {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsIn, IsInt, IsNotEmpty, IsString, IsUUID, Length } from "class-validator";
|
||||
|
||||
import { ServiceMessage, UserMessage } from "../../../common/enums/message.enum";
|
||||
|
||||
export class CreateServiceFeedbackDto {
|
||||
@IsNotEmpty({ message: ServiceMessage.COMMENT_REQUIRED })
|
||||
@IsString({ message: ServiceMessage.COMMENT_STRING })
|
||||
@Length(1, 1000, { message: ServiceMessage.COMMENT_LENGTH })
|
||||
@ApiProperty({ description: "Feedback content", example: "This service works perfectly for my needs" })
|
||||
content: string;
|
||||
|
||||
@IsNotEmpty({ message: ServiceMessage.RATING_REQUIRED })
|
||||
@IsInt({ message: ServiceMessage.RATING_INT })
|
||||
@IsIn([1, 2, 3, 4, 5], { message: ServiceMessage.RATING_RANGE })
|
||||
@ApiProperty({ description: "Rating (1-5, only for reviews)", example: 5, required: false })
|
||||
rating: number;
|
||||
|
||||
@IsUUID("4", { message: ServiceMessage.SERVICE_ID_SHOULD_BE_A_UUID })
|
||||
@IsNotEmpty({ message: ServiceMessage.SERVICE_ID_REQUIRED })
|
||||
@ApiProperty({ description: "Service ID", example: "d290f1ee-6c54-4b01-90e6-d701748f0851" })
|
||||
serviceId: string;
|
||||
|
||||
@IsNotEmpty({ message: UserMessage.USER_ID_REQUIRED })
|
||||
@IsUUID("all", { message: UserMessage.USER_ID_SHOULD_BE_A_UUID })
|
||||
@ApiProperty({ description: "User ID", example: "d290f1ee-6c54-4b01-90e6-d701748f0851" })
|
||||
userId: string;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, IsUUID, MaxLength } from "class-validator";
|
||||
|
||||
import { ServiceMessage } from "../../../common/enums/message.enum";
|
||||
|
||||
export class CreateServiceGuideDto {
|
||||
@IsString({ message: ServiceMessage.GUIDE_TITLE_STRING })
|
||||
@IsNotEmpty({ message: ServiceMessage.GUIDE_TITLE_REQUIRED })
|
||||
@MaxLength(200, { message: ServiceMessage.GUIDE_TITLE_LENGTH })
|
||||
@ApiProperty({ description: "Guide title", example: "Getting Started with Service X" })
|
||||
title: string;
|
||||
|
||||
@IsString({ message: ServiceMessage.GUIDE_DESCRIPTION_STRING })
|
||||
@IsNotEmpty({ message: ServiceMessage.GUIDE_DESCRIPTION_REQUIRED })
|
||||
@MaxLength(200)
|
||||
@ApiProperty({ description: "Guide description", example: "This guide will help you get started with Service X" })
|
||||
description: string;
|
||||
|
||||
@IsString({ message: ServiceMessage.GUIDE_CONTENT_STRING })
|
||||
@IsNotEmpty({ message: ServiceMessage.GUIDE_CONTENT_REQUIRED })
|
||||
@ApiProperty({ description: "Guide content in markdown or HTML", example: "# Getting Started\n\nThis guide will help you..." })
|
||||
content: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNotEmpty({ message: ServiceMessage.GUIDE_ORDER_IS_REQUIRED })
|
||||
@IsNumber({ allowInfinity: false, allowNaN: false }, { message: ServiceMessage.GUIDE_ORDER_IS_NUMBER })
|
||||
@ApiProperty({ description: "Order for displaying guides", example: 1 })
|
||||
order?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean({ message: ServiceMessage.GUIDE_IS_ACTIVE_BOOLEAN })
|
||||
@ApiProperty({ description: "Whether the guide is active", example: true, required: false })
|
||||
isActive?: boolean;
|
||||
|
||||
@IsNotEmpty({ message: ServiceMessage.SERVICE_ID_REQUIRED })
|
||||
@IsUUID("4", { message: ServiceMessage.SERVICE_ID_SHOULD_BE_A_UUID })
|
||||
@ApiProperty({ description: "Service ID this guide belongs to" })
|
||||
serviceId: string;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { IsOptional, IsString, IsUUID } from "class-validator";
|
||||
|
||||
import { PaginationDto } from "../../../common/DTO/pagination.dto";
|
||||
import { CommonMessage, ServiceMessage } from "../../../common/enums/message.enum";
|
||||
|
||||
export class ServiceFeedbackQueryDto extends PaginationDto {
|
||||
@IsOptional()
|
||||
@IsUUID("4", { message: ServiceMessage.SERVICE_ID_SHOULD_BE_A_UUID })
|
||||
@ApiPropertyOptional({ description: "Service ID", example: "d290f1ee-6c54-4b01-90e6-d701748f0851" })
|
||||
serviceId: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString({ message: CommonMessage.SEARCH_QUERY_STRING })
|
||||
@ApiPropertyOptional({ description: "Search term for content", example: "search query" })
|
||||
q?: string;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsIn, IsOptional, IsString } from "class-validator";
|
||||
|
||||
import { ServiceMessage } from "../../../common/enums/message.enum";
|
||||
|
||||
export class ServiceGuideQueryDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@ApiPropertyOptional({ description: "Search term for title and content", example: "search query" })
|
||||
q?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsIn([1, 0], { message: ServiceMessage.IS_ACTIVE_SHOULD_BE_1_0 })
|
||||
@ApiPropertyOptional({ description: "guide status", example: 1 })
|
||||
isActive?: number;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsEnum, IsUUID } from "class-validator";
|
||||
|
||||
import { ReviewStatus } from "../enums/review-status.enum";
|
||||
|
||||
export class UpdateFeedbackStatusDto {
|
||||
@ApiProperty({ description: "Feedback ID" })
|
||||
@IsUUID()
|
||||
id: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "New status for the feedback",
|
||||
enum: ReviewStatus,
|
||||
example: ReviewStatus.APPROVED,
|
||||
})
|
||||
@IsEnum(ReviewStatus)
|
||||
status: ReviewStatus;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { PartialType } from "@nestjs/swagger";
|
||||
|
||||
import { CreateServiceGuideDto } from "./create-service-guide.dto";
|
||||
|
||||
export class UpdateServiceGuideDto extends PartialType(CreateServiceGuideDto) {}
|
||||
@@ -1,15 +1,20 @@
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Query } from "@nestjs/common";
|
||||
import { Body, Controller, Delete, Get, Headers, HttpCode, HttpStatus, Ip, Param, Patch, Post, Query } from "@nestjs/common";
|
||||
import { ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||
|
||||
import { AddReviewDto } from "./DTO/add-review.dto";
|
||||
import { CategoryListSearchQueryDto, CategorySearchQueryDto } from "./DTO/category-search-query.dto";
|
||||
import { CreateDanakServiceCategoryDto } from "./DTO/create-category.dto";
|
||||
import { CreateServiceFeedbackDto } from "./DTO/create-service-feedback.dto";
|
||||
import { CreateServiceGuideDto } from "./DTO/create-service-guide.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 { ServiceFeedbackQueryDto } from "./DTO/service-feedback-query.dto";
|
||||
import { ServiceGuideQueryDto } from "./DTO/service-guide-query.dto";
|
||||
import { UpdateDanakServiceCategoryDto } from "./DTO/update-category.dto";
|
||||
import { UpdateReviewStatusDto } from "./DTO/update-review-status.dto";
|
||||
import { UpdateServiceGuideDto } from "./DTO/update-service-guide.dto";
|
||||
import { UpdateServiceDto } from "./DTO/update-service.dto";
|
||||
import { DanakServicesService } from "./providers/danak-services.service";
|
||||
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||
@@ -223,11 +228,60 @@ export class DanakServicesController {
|
||||
return this.danakServicesService.updateDanakServiceReviewStatus(updateParamDto);
|
||||
}
|
||||
|
||||
// @AuthGuards()
|
||||
//
|
||||
// @ApiOperation({ summary: "get all user purchased danak services" })
|
||||
// @Get("user-services")
|
||||
// getUserServices(@UserDec() user: User) {
|
||||
// return this.danakServicesService.getUserServices(user.id);
|
||||
// }
|
||||
//------------------------ service guides ------------------------
|
||||
@ApiOperation({ summary: "Create a service guide => admin route" })
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@Post("guides")
|
||||
createServiceGuide(@Body() createDto: CreateServiceGuideDto) {
|
||||
return this.danakServicesService.createServiceGuide(createDto);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Get service guides => public route" })
|
||||
@Get(":id/guides")
|
||||
getServiceGuides(@Param() paramDto: ParamDto, @Query() queryDto: ServiceGuideQueryDto) {
|
||||
return this.danakServicesService.getServiceGuides(paramDto.id, queryDto);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Update service guide => admin route" })
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@Patch("guides/:id")
|
||||
updateServiceGuide(@Param() paramDto: ParamDto, @Body() updateDto: UpdateServiceGuideDto) {
|
||||
return this.danakServicesService.updateServiceGuide(paramDto.id, updateDto);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Delete service guide => admin route" })
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@Delete("guides/:id")
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
deleteServiceGuide(@Param() paramDto: ParamDto) {
|
||||
return this.danakServicesService.deleteServiceGuide(paramDto.id);
|
||||
}
|
||||
|
||||
//------------------------ service feedback ------------------------
|
||||
@ApiOperation({ summary: "Create service feedback => user route" })
|
||||
@AuthGuards()
|
||||
@Post("feedback")
|
||||
createServiceFeedback(@Body() createDto: CreateServiceFeedbackDto, @Headers() headers: Record<string, string>, @Ip() ip: string) {
|
||||
return this.danakServicesService.createServiceFeedback(createDto, headers, ip);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Get service feedbacks => admin route" })
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@Pagination()
|
||||
@Get("feedback")
|
||||
getServiceFeedbacks(@Query() queryDto: ServiceFeedbackQueryDto) {
|
||||
return this.danakServicesService.getServicesFeedbacks(queryDto);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Get service feedback statistics => admin route" })
|
||||
@AuthGuards()
|
||||
@PermissionsDec(PermissionEnum.SERVICES)
|
||||
@Get(":id/feedback")
|
||||
getServiceFeedbackStats(@Param() paramDto: ParamDto) {
|
||||
return this.danakServicesService.getServiceFeedback(paramDto.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,14 @@ import { TypeOrmModule } from "@nestjs/typeorm";
|
||||
|
||||
import { DanakServicesController } from "./danak-services.controller";
|
||||
import { DanakServiceCategory } from "./entities/danak-service-category.entity";
|
||||
import { DanakServiceFeedback } from "./entities/danak-service-feedback.entity";
|
||||
import { DanakServiceGuide } from "./entities/danak-service-guide.entity";
|
||||
import { DanakServiceImage } from "./entities/danak-service-image.entity";
|
||||
import { DanakServiceReview } from "./entities/danak-service-review.entity";
|
||||
import { DanakService } from "./entities/danak-service.entity";
|
||||
import { DanakServicesService } from "./providers/danak-services.service";
|
||||
import { DanakServiceFeedbackRepository } from "./repositories/danak-service-feedback.repository";
|
||||
import { DanakServiceGuideRepository } from "./repositories/danak-service-guide.repository";
|
||||
import { DanakServicesCategoryRepository } from "./repositories/danak-services-category.repository";
|
||||
import { DanakServicesImageRepository } from "./repositories/danak-services-image.repository";
|
||||
import { DanakServiceReviewRepository } from "./repositories/danak-services-review.repository";
|
||||
@@ -16,7 +20,14 @@ import { UsersModule } from "../users/users.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([DanakService, DanakServiceImage, DanakServiceCategory, DanakServiceReview]),
|
||||
TypeOrmModule.forFeature([
|
||||
DanakService,
|
||||
DanakServiceImage,
|
||||
DanakServiceCategory,
|
||||
DanakServiceReview,
|
||||
DanakServiceFeedback,
|
||||
DanakServiceGuide,
|
||||
]),
|
||||
UsersModule,
|
||||
NotificationModule,
|
||||
],
|
||||
@@ -26,6 +37,8 @@ import { UsersModule } from "../users/users.module";
|
||||
DanakServicesCategoryRepository,
|
||||
DanakServicesImageRepository,
|
||||
DanakServiceReviewRepository,
|
||||
DanakServiceFeedbackRepository,
|
||||
DanakServiceGuideRepository,
|
||||
],
|
||||
controllers: [DanakServicesController],
|
||||
exports: [DanakServicesService],
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Column, DeleteDateColumn, Entity, ManyToOne } from "typeorm";
|
||||
|
||||
import { DanakService } from "./danak-service.entity";
|
||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
|
||||
@Entity()
|
||||
export class DanakServiceFeedback extends BaseEntity {
|
||||
@Column({ type: "text", nullable: false })
|
||||
content: string;
|
||||
|
||||
@Column({ type: "int", nullable: false })
|
||||
rating: number;
|
||||
|
||||
@Column({ type: "json", nullable: true })
|
||||
metadata: Record<string, unknown> | null;
|
||||
|
||||
@ManyToOne(() => User, (user) => user.serviceFeedbacks, { onDelete: "SET NULL", nullable: true })
|
||||
user: User | null;
|
||||
|
||||
@ManyToOne(() => DanakService, (service) => service.feedbacks, { onDelete: "CASCADE", nullable: false })
|
||||
service: DanakService;
|
||||
|
||||
@DeleteDateColumn({ nullable: true })
|
||||
deletedAt?: Date;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Column, DeleteDateColumn, Entity, ManyToOne } from "typeorm";
|
||||
|
||||
import { DanakService } from "./danak-service.entity";
|
||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||
|
||||
@Entity()
|
||||
export class DanakServiceGuide extends BaseEntity {
|
||||
@Column({ type: "varchar", length: 200, nullable: false })
|
||||
title: string;
|
||||
|
||||
@Column({ type: "varchar", length: 200, nullable: false })
|
||||
description: string;
|
||||
|
||||
@Column({ type: "text", nullable: false })
|
||||
content: string;
|
||||
|
||||
@Column({ type: "int", nullable: false, default: 0 })
|
||||
order: number;
|
||||
|
||||
@Column({ type: "boolean", default: true })
|
||||
isActive: boolean;
|
||||
|
||||
@ManyToOne(() => DanakService, (service) => service.guides, { onDelete: "CASCADE", nullable: false })
|
||||
service: DanakService;
|
||||
|
||||
@DeleteDateColumn({ type: "timestamptz" })
|
||||
deletedAt?: Date;
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Column, DeleteDateColumn, Entity, Index, ManyToOne, OneToMany } from "typeorm";
|
||||
|
||||
import { DanakServiceCategory } from "./danak-service-category.entity";
|
||||
import { DanakServiceFeedback } from "./danak-service-feedback.entity";
|
||||
import { DanakServiceGuide } from "./danak-service-guide.entity";
|
||||
import { DanakServiceImage } from "./danak-service-image.entity";
|
||||
import { DanakServiceReview } from "./danak-service-review.entity";
|
||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||
@@ -92,6 +94,12 @@ export class DanakService extends BaseEntity {
|
||||
@OneToMany(() => DanakServiceReview, (danakServiceReview) => danakServiceReview.service)
|
||||
reviews: DanakServiceReview[];
|
||||
|
||||
@OneToMany(() => DanakServiceFeedback, (feedback) => feedback.service, { cascade: true })
|
||||
feedbacks: DanakServiceFeedback[];
|
||||
|
||||
@OneToMany(() => DanakServiceGuide, (guide) => guide.service, { cascade: true })
|
||||
guides: DanakServiceGuide[];
|
||||
|
||||
@DeleteDateColumn()
|
||||
deletedAt?: Date;
|
||||
}
|
||||
|
||||
@@ -5,21 +5,29 @@ import { ParamDto } from "../../../common/DTO/param.dto";
|
||||
import { CategoryMessage, CommonMessage, ServiceMessage, SubscriptionMessage } from "../../../common/enums/message.enum";
|
||||
import { NotificationQueue } from "../../notifications/queue/notification.queue";
|
||||
import { SubscriptionStatus } from "../../subscriptions/enums/subscription-status.enum";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { AdminsService } from "../../users/providers/admins.service";
|
||||
import { UsersService } from "../../users/providers/users.service";
|
||||
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
||||
import { AddReviewDto } from "../DTO/add-review.dto";
|
||||
import { CategoryListSearchQueryDto, CategorySearchQueryDto } from "../DTO/category-search-query.dto";
|
||||
import { CreateDanakServiceCategoryDto } from "../DTO/create-category.dto";
|
||||
import { CreateServiceFeedbackDto } from "../DTO/create-service-feedback.dto";
|
||||
import { CreateServiceGuideDto } from "../DTO/create-service-guide.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 { ServiceFeedbackQueryDto } from "../DTO/service-feedback-query.dto";
|
||||
import { ServiceGuideQueryDto } from "../DTO/service-guide-query.dto";
|
||||
import { UpdateDanakServiceCategoryDto } from "../DTO/update-category.dto";
|
||||
import { UpdateReviewStatusDto } from "../DTO/update-review-status.dto";
|
||||
import { UpdateServiceGuideDto } from "../DTO/update-service-guide.dto";
|
||||
import { UpdateServiceDto } from "../DTO/update-service.dto";
|
||||
import { DanakServiceCategory } from "../entities/danak-service-category.entity";
|
||||
import { DanakServiceImage } from "../entities/danak-service-image.entity";
|
||||
import { ReviewStatus } from "../enums/review-status.enum";
|
||||
import { DanakServiceFeedbackRepository } from "../repositories/danak-service-feedback.repository";
|
||||
import { DanakServiceGuideRepository } from "../repositories/danak-service-guide.repository";
|
||||
import { DanakServicesCategoryRepository } from "../repositories/danak-services-category.repository";
|
||||
import { DanakServicesImageRepository } from "../repositories/danak-services-image.repository";
|
||||
import { DanakServiceReviewRepository } from "../repositories/danak-services-review.repository";
|
||||
@@ -31,6 +39,8 @@ export class DanakServicesService {
|
||||
private readonly danakServicesRepository: DanakServicesRepository,
|
||||
private readonly danakServicesCategoryRepository: DanakServicesCategoryRepository,
|
||||
private readonly danakServiceReviewRepository: DanakServiceReviewRepository,
|
||||
private readonly danakServiceFeedbackRepository: DanakServiceFeedbackRepository,
|
||||
private readonly danakServiceGuideRepository: DanakServiceGuideRepository,
|
||||
private readonly danakServicesImageRepository: DanakServicesImageRepository,
|
||||
private readonly dataSource: DataSource,
|
||||
private readonly adminsService: AdminsService,
|
||||
@@ -601,6 +611,146 @@ export class DanakServicesService {
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/******************************************** */
|
||||
async createServiceGuide(createDto: CreateServiceGuideDto) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
try {
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
const service = await this.findServiceById(createDto.serviceId);
|
||||
|
||||
const guide = queryRunner.manager.create(this.danakServiceGuideRepository.target, {
|
||||
...createDto,
|
||||
service,
|
||||
order: createDto.order || 0,
|
||||
});
|
||||
|
||||
await queryRunner.manager.save(this.danakServiceGuideRepository.target, guide);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
return { message: CommonMessage.CREATED, guide };
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
this.logger.error("Error creating service guide", error);
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
/******************************************** */
|
||||
async getServiceGuides(serviceId: string, queryDto: ServiceGuideQueryDto) {
|
||||
const guides = await this.danakServiceGuideRepository.findServiceGuides(serviceId, queryDto);
|
||||
|
||||
return { guides };
|
||||
}
|
||||
|
||||
/******************************************** */
|
||||
|
||||
async updateServiceGuide(guideId: string, updateDto: UpdateServiceGuideDto) {
|
||||
const guide = await this.danakServiceGuideRepository.findOneBy({
|
||||
id: guideId,
|
||||
service: { id: updateDto.serviceId },
|
||||
deletedAt: IsNull(),
|
||||
});
|
||||
|
||||
if (!guide) throw new BadRequestException(ServiceMessage.SERVICE_GUIDE_NOT_EXIST);
|
||||
|
||||
await this.danakServiceGuideRepository.save({ ...guide, ...updateDto });
|
||||
|
||||
return { message: CommonMessage.UPDATED, guide };
|
||||
}
|
||||
|
||||
/******************************************** */
|
||||
|
||||
async deleteServiceGuide(guideId: string) {
|
||||
const guide = await this.danakServiceGuideRepository.findOneBy({ id: guideId, deletedAt: IsNull() });
|
||||
|
||||
if (!guide) throw new BadRequestException(ServiceMessage.SERVICE_GUIDE_NOT_EXIST);
|
||||
|
||||
await this.danakServiceGuideRepository.softDelete(guideId);
|
||||
|
||||
return {
|
||||
message: CommonMessage.DELETED,
|
||||
};
|
||||
}
|
||||
|
||||
/******************************************** */
|
||||
|
||||
async createServiceFeedback(createDto: CreateServiceFeedbackDto, headers: Record<string, string>, ip: string) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
try {
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
const service = await queryRunner.manager.findOne(this.danakServicesRepository.target, {
|
||||
where: { id: createDto.serviceId, deletedAt: IsNull() },
|
||||
});
|
||||
|
||||
if (!service) throw new BadRequestException(ServiceMessage.SERVICE_NOT_EXIST);
|
||||
|
||||
const user = await queryRunner.manager.findOne(User, { where: { id: createDto.userId, deletedAt: IsNull() } });
|
||||
|
||||
const feedback = queryRunner.manager.create(this.danakServiceFeedbackRepository.target, {
|
||||
...createDto,
|
||||
service,
|
||||
user,
|
||||
metadata: {
|
||||
userIp: ip,
|
||||
referer: headers["referer"] || headers["referrer"],
|
||||
userAgent: headers["user-agent"],
|
||||
acceptLanguage: headers["accept-language"],
|
||||
acceptEncoding: headers["accept-encoding"],
|
||||
contentType: headers["content-type"],
|
||||
origin: headers["origin"],
|
||||
host: headers["host"],
|
||||
xForwardedFor: headers["x-forwarded-for"],
|
||||
xRealIp: headers["x-real-ip"],
|
||||
timestamp: new Date().toISOString(),
|
||||
platform: headers["sec-ch-ua-platform"],
|
||||
mobile: headers["sec-ch-ua-mobile"],
|
||||
},
|
||||
});
|
||||
|
||||
await queryRunner.manager.save(this.danakServiceFeedbackRepository.target, feedback);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
return {
|
||||
message: CommonMessage.CREATED,
|
||||
feedback,
|
||||
};
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
this.logger.error("Error creating service feedback", error);
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************** */
|
||||
async getServicesFeedbacks(queryDto: ServiceFeedbackQueryDto) {
|
||||
const [feedbacks, count] = await this.danakServiceFeedbackRepository.findServiceFeedbacks(queryDto);
|
||||
return { feedbacks, count, paginate: true };
|
||||
}
|
||||
|
||||
/******************************************** */
|
||||
|
||||
async getServiceFeedback(serviceId: string) {
|
||||
const service = await this.findServiceById(serviceId);
|
||||
|
||||
const feedback = await this.danakServiceFeedbackRepository.findByServiceId(serviceId);
|
||||
|
||||
return {
|
||||
service: { id: service.id, name: service.name, description: service.description, isActive: service.isActive },
|
||||
feedback,
|
||||
};
|
||||
}
|
||||
|
||||
/******************************************** */
|
||||
|
||||
private generateSlug(slug: string): string {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { FindOptionsWhere, IsNull, Repository } from "typeorm";
|
||||
|
||||
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
||||
import { ServiceFeedbackQueryDto } from "../DTO/service-feedback-query.dto";
|
||||
import { DanakServiceFeedback } from "../entities/danak-service-feedback.entity";
|
||||
|
||||
@Injectable()
|
||||
export class DanakServiceFeedbackRepository extends Repository<DanakServiceFeedback> {
|
||||
constructor(@InjectRepository(DanakServiceFeedback) repository: Repository<DanakServiceFeedback>) {
|
||||
super(repository.target, repository.manager, repository.queryRunner);
|
||||
}
|
||||
|
||||
async findByServiceId(serviceId: string): Promise<DanakServiceFeedback[]> {
|
||||
const where: FindOptionsWhere<DanakServiceFeedback> = { service: { id: serviceId }, deletedAt: IsNull() };
|
||||
|
||||
return this.find({ where, order: { createdAt: "DESC" }, relations: ["user", "service"] });
|
||||
}
|
||||
/******************************************** */
|
||||
async findServiceFeedbacks(queryDto: ServiceFeedbackQueryDto) {
|
||||
const { limit, skip } = PaginationUtils(queryDto);
|
||||
|
||||
const queryBuilder = this.createQueryBuilder("feedback")
|
||||
.leftJoinAndSelect("feedback.service", "service")
|
||||
.leftJoinAndSelect("feedback.user", "user")
|
||||
.where("feedback.deletedAt IS NULL");
|
||||
|
||||
if (queryDto.serviceId) {
|
||||
queryBuilder.andWhere("feedback.serviceId = :serviceId", { serviceId: queryDto.serviceId });
|
||||
}
|
||||
|
||||
if (queryDto.q) {
|
||||
queryBuilder.andWhere("feedback.content ILIKE :search", { search: `%${queryDto.q}%` });
|
||||
}
|
||||
|
||||
queryBuilder.orderBy("feedback.createdAt", "DESC").skip(skip).take(limit);
|
||||
|
||||
return queryBuilder.getManyAndCount();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
|
||||
import { ServiceGuideQueryDto } from "../DTO/service-guide-query.dto";
|
||||
import { DanakServiceGuide } from "../entities/danak-service-guide.entity";
|
||||
|
||||
@Injectable()
|
||||
export class DanakServiceGuideRepository extends Repository<DanakServiceGuide> {
|
||||
constructor(@InjectRepository(DanakServiceGuide) repository: Repository<DanakServiceGuide>) {
|
||||
super(repository.target, repository.manager, repository.queryRunner);
|
||||
}
|
||||
|
||||
async findByServiceId(serviceId: string): Promise<DanakServiceGuide[]> {
|
||||
return this.find({
|
||||
where: { service: { id: serviceId }, isActive: true },
|
||||
order: { order: "ASC", createdAt: "DESC" },
|
||||
});
|
||||
}
|
||||
|
||||
async findServiceGuides(serviceId: string, queryDto: ServiceGuideQueryDto) {
|
||||
const queryBuilder = this.createQueryBuilder("guide").leftJoinAndSelect("guide.service", "service").where("guide.deletedAt IS NULL");
|
||||
|
||||
queryBuilder.andWhere("guide.serviceId = :serviceId", { serviceId });
|
||||
|
||||
if (queryDto.q) {
|
||||
queryBuilder.andWhere("(guide.title ILIKE :search OR guide.content ILIKE :search OR guide.description ILIKE :search)", {
|
||||
search: `%${queryDto.q}%`,
|
||||
});
|
||||
}
|
||||
|
||||
if (queryDto.isActive !== undefined) {
|
||||
queryBuilder.andWhere("guide.isActive = :isActive", { isActive: queryDto.isActive === 1 });
|
||||
}
|
||||
|
||||
queryBuilder.orderBy("guide.order", "ASC").addOrderBy("guide.createdAt", "DESC");
|
||||
|
||||
return queryBuilder.getMany();
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ export class DepositRequest extends BaseEntity {
|
||||
receiptUrl: string;
|
||||
|
||||
@Column({ type: "varchar", length: 250, nullable: true })
|
||||
comment: string;
|
||||
comment: string | null;
|
||||
|
||||
@Column({ type: "enum", enum: TransferType, nullable: false })
|
||||
type: TransferType;
|
||||
|
||||
@@ -622,12 +622,7 @@ export class PaymentsService {
|
||||
|
||||
//===============================================
|
||||
private async updateDepositRequestStatus(depositId: string, status: DepositRequestStatus, comment: string | null, qryRnr: QueryRunner) {
|
||||
const updateData: Partial<DepositRequest> = { status };
|
||||
if (comment !== null) {
|
||||
updateData.comment = comment;
|
||||
}
|
||||
|
||||
await qryRnr.manager.update(DepositRequest, { id: depositId }, updateData);
|
||||
await qryRnr.manager.update(DepositRequest, { id: depositId }, { status, comment });
|
||||
}
|
||||
//===============================================
|
||||
private async addPaymentReminderToQueue(payment: Payment) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { UserAnnouncement } from "../../announcements/entities/user-announcement
|
||||
import { BlogComment } from "../../blogs/entities/blog-comment.entity";
|
||||
import { Blog } from "../../blogs/entities/blog.entity";
|
||||
import { Criticism } from "../../criticisms/entities/criticism.entity";
|
||||
import { DanakServiceFeedback } from "../../danak-services/entities/danak-service-feedback.entity";
|
||||
import { DanakServiceReview } from "../../danak-services/entities/danak-service-review.entity";
|
||||
import { Discount } from "../../discounts/entities/discount.entity";
|
||||
import { Invoice } from "../../invoices/entities/invoice.entity";
|
||||
@@ -81,6 +82,9 @@ export class User extends BaseEntity {
|
||||
@OneToMany(() => DanakServiceReview, (danakServiceReview) => danakServiceReview.user)
|
||||
reviews: DanakServiceReview[];
|
||||
|
||||
@OneToMany(() => DanakServiceFeedback, (feedback) => feedback.user)
|
||||
serviceFeedbacks: DanakServiceFeedback[];
|
||||
|
||||
@OneToMany(() => Blog, (blog) => blog.author)
|
||||
blogs: Blog[];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user