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:
mahyargdz
2025-08-19 13:15:13 +03:30
parent 43a3714284
commit 0764c2689f
18 changed files with 511 additions and 16 deletions
+11
View File
@@ -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[];