Files
dsc-api/src/modules/danak-services/providers/danak-services.service.ts
T
2025-04-22 12:28:11 +03:30

578 lines
20 KiB
TypeScript
Executable File

import { BadRequestException, Injectable, Logger } from "@nestjs/common";
import { DataSource, FindOptionsWhere, In, IsNull, Not, QueryRunner } from "typeorm";
import { ParamDto } from "../../../common/DTO/param.dto";
import { CategoryMessage, CommonMessage, ServiceMessage, SubscriptionMessage } from "../../../common/enums/message.enum";
import { SubscriptionStatus } from "../../subscriptions/enums/subscription-status.enum";
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 { 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 { UpdateDanakServiceCategoryDto } from "../DTO/update-category.dto";
import { UpdateReviewStatusDto } from "../DTO/update-review-status.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 { DanakServicesCategoryRepository } from "../repositories/danak-services-category.repository";
import { DanakServicesImageRepository } from "../repositories/danak-services-image.repository";
import { DanakServiceReviewRepository } from "../repositories/danak-services-review.repository";
import { DanakServicesRepository } from "../repositories/danak-services.repository";
@Injectable()
export class DanakServicesService {
private readonly logger = new Logger(DanakServicesService.name);
constructor(
private readonly danakServicesRepository: DanakServicesRepository,
private readonly danakServicesCategoryRepository: DanakServicesCategoryRepository,
private readonly danakServiceReviewRepository: DanakServiceReviewRepository,
private readonly danakServicesImageRepository: DanakServicesImageRepository,
private readonly dataSource: DataSource,
) {}
/******************************************** */
async createCategory(createDto: CreateDanakServiceCategoryDto) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const existCategory = await queryRunner.manager.findOne(this.danakServicesCategoryRepository.target, {
where: { title: createDto.title },
});
if (existCategory) {
throw new BadRequestException(CategoryMessage.TITLE_EXIST);
}
const category = queryRunner.manager.create(this.danakServicesCategoryRepository.target, createDto);
await queryRunner.manager.save(this.danakServicesCategoryRepository.target, category);
await this.handleOrderCollision(queryRunner, category);
await queryRunner.commitTransaction();
return {
message: CommonMessage.CREATED,
category,
};
} catch (error) {
await queryRunner.rollbackTransaction();
this.logger.error("Error in create category", error);
throw new BadRequestException(CategoryMessage.CREATION_FAILED);
} finally {
await queryRunner.release();
}
}
//*********************************** */
private async handleOrderCollision(queryRunner: QueryRunner, newCategory: DanakServiceCategory): Promise<void> {
const conflictingCategory = await queryRunner.manager.findOne(this.danakServicesCategoryRepository.target, {
where: {
order: newCategory.order,
id: Not(newCategory.id),
deletedAt: IsNull(),
},
});
if (conflictingCategory) {
const highestOrderCategory = await queryRunner.manager.findOne(this.danakServicesCategoryRepository.target, {
order: { order: "DESC" },
where: { deletedAt: IsNull() },
});
const newOrder = highestOrderCategory ? highestOrderCategory.order + 1 : newCategory.order + 1;
await queryRunner.manager.update(this.danakServicesCategoryRepository.target, conflictingCategory.id, { order: newOrder });
}
}
/******************************************** */
async getCategoryById(categoryId: string) {
const category = await this.danakServicesCategoryRepository.findOneById(categoryId);
if (!category) throw new BadRequestException(CategoryMessage.CATEGORY_NOT_EXIST);
return {
category,
};
}
/******************************************** */
async updateCategory(categoryId: string, updateDto: UpdateDanakServiceCategoryDto) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const category = await queryRunner.manager.findOne(DanakServiceCategory, { where: { id: categoryId } });
if (!category) throw new BadRequestException(CategoryMessage.CATEGORY_NOT_EXIST);
if (updateDto.title) {
const existCategory = await queryRunner.manager.findOne(DanakServiceCategory, {
where: { title: updateDto.title, id: Not(categoryId) },
});
if (existCategory) throw new BadRequestException(CategoryMessage.TITLE_EXIST);
}
await queryRunner.manager.update(DanakServiceCategory, categoryId, updateDto);
if (updateDto.order !== undefined && updateDto.order !== category.order) {
await this.handleOrderCollision(queryRunner, { ...category, ...updateDto });
}
await queryRunner.commitTransaction();
return {
message: CommonMessage.UPDATE_SUCCESS,
};
} catch (error) {
await queryRunner.rollbackTransaction();
this.logger.error("Error in update category", error);
throw new BadRequestException(CategoryMessage.UPDATE_FAILED);
} finally {
await queryRunner.release();
}
}
/******************************************** */
async deleteCategoryById(categoryId: string) {
const category = await this.danakServicesCategoryRepository.findOneById(categoryId);
if (!category) throw new BadRequestException(CategoryMessage.CATEGORY_NOT_EXIST);
//
await this.danakServicesCategoryRepository.softDelete(categoryId);
return {
message: CommonMessage.DELETED,
};
}
/******************************************** */
async getCategories(queryDto: CategorySearchQueryDto) {
const where: FindOptionsWhere<DanakServiceCategory> = {
isActive: true,
deletedAt: IsNull(),
};
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")
.where("category.deletedAt IS NULL")
.loadRelationCountAndMap("category.servicesCount", "category.danakServices")
.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 ILIKE :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, paginate: true };
}
/******************************************** */
async getCategoriesUserSide() {
const categories = await this.danakServicesCategoryRepository.find({
where: { isActive: true, deletedAt: IsNull() },
order: { order: "ASC" },
});
return {
categories,
};
}
/******************************************** */
async getCategoryServices(categoryId?: string) {
const category = await this.danakServicesCategoryRepository.findOne({
where: { id: categoryId, isActive: true, deletedAt: IsNull(), danakServices: { isActive: true } },
relations: {
danakServices: true,
},
order: { createdAt: "DESC" },
});
if (!category) throw new BadRequestException(CategoryMessage.CATEGORY_NOT_EXIST_OR_HAS_NO_SERVICE);
return {
category,
};
}
/******************************************** */
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 toggleServiceStatus(paramDto: ParamDto) {
const service = await this.danakServicesRepository.findServiceById(paramDto.id);
if (!service) throw new BadRequestException(ServiceMessage.SERVICE_NOT_EXIST);
//
service.isActive = !service.isActive;
//
await this.danakServicesRepository.save(service);
//
return {
message: CommonMessage.UPDATE_SUCCESS,
isActive: service.isActive,
};
}
/******************************************** */
async toggleServiceSlider(paramDto: ParamDto) {
const service = await this.danakServicesRepository.findServiceById(paramDto.id);
if (!service) throw new BadRequestException(ServiceMessage.SERVICE_NOT_EXIST);
//
service.showInSlider = !service.showInSlider;
//
await this.danakServicesRepository.save(service);
//
return {
message: CommonMessage.UPDATE_SUCCESS,
showInSlider: service.showInSlider,
};
}
/******************************************** */
async createService(createDto: CreateServiceDto) {
const category = await this.danakServicesCategoryRepository.findOneById(createDto.categoryId);
if (!category) throw new BadRequestException(CategoryMessage.CATEGORY_NOT_EXIST);
const existService = await this.danakServicesRepository.findOneByName(createDto.name);
if (existService) throw new BadRequestException(ServiceMessage.NAME_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,
};
}
/******************************************** */
async updateDanakService(serviceId: string, updateDto: UpdateServiceDto) {
const service = await this.findServiceById(serviceId);
let images: DanakServiceImage[] = [];
if (updateDto.categoryId) {
const category = await this.danakServicesCategoryRepository.findOneById(updateDto.categoryId);
if (!category) throw new BadRequestException(CategoryMessage.CATEGORY_NOT_EXIST);
service.category = category;
}
//
if (updateDto.name) {
const existService = await this.danakServicesRepository.findOneByName(updateDto.name, serviceId);
if (existService) throw new BadRequestException(ServiceMessage.NAME_EXIST);
}
//
if (updateDto.images) {
await this.danakServicesImageRepository.delete({ danakService: { id: serviceId } });
const newImages = updateDto.images.map((image) => {
return this.danakServicesImageRepository.create({
imageUrl: image,
danakService: service,
});
});
await this.danakServicesImageRepository.save(newImages);
images = newImages;
}
await this.danakServicesRepository.save({ ...service, ...updateDto, images: images.length ? images : service.images });
return {
message: CommonMessage.UPDATE_SUCCESS,
service,
};
}
/******************************************** */
async deleteServiceById(serviceId: string) {
await this.findServiceById(serviceId);
await this.danakServicesRepository.softDelete(serviceId);
return {
message: CommonMessage.DELETED,
};
}
/******************************************** */
async getServicesList(queryDto: DanakServicesSearchQueryDto) {
return this.danakServicesRepository.getServicesForAdmin(queryDto);
}
/******************************************** */
async getServicesListForUser(queryDto: DanakServicesQueryDto) {
return this.danakServicesRepository.getServicesForUser(queryDto);
}
/******************************************** */
async searchServices(queryDto: DanakServicesGlobalSearchDto) {
const services = await this.danakServicesRepository.searchServices(queryDto);
return {
services,
};
}
/******************************************** */
async getDanakSuggestServices() {
const danakServices = await this.danakServicesRepository.find({
where: { isDanakSuggest: true, isActive: true, deletedAt: IsNull(), category: { isActive: true, deletedAt: IsNull() } },
relations: { category: true },
select: {
id: true,
name: true,
title: true,
description: true,
createDate: true,
icon: true,
createdAt: true,
isActive: true,
isDanakSuggest: true,
metaDescription: true,
category: {
id: true,
title: true,
},
},
order: { createdAt: "DESC" },
withDeleted: false,
});
return {
danakServices,
};
}
/******************************************** */
async getDanakServicesSlider() {
const danakServices = await this.danakServicesRepository.find({
where: { showInSlider: true, isActive: true, deletedAt: IsNull(), category: { isActive: true, deletedAt: IsNull() } },
relations: { category: true, images: true },
select: {
id: true,
name: true,
title: true,
description: true,
createDate: true,
icon: true,
createdAt: true,
isActive: true,
isDanakSuggest: true,
showInSlider: true,
metaDescription: true,
category: {
id: true,
title: true,
},
images: {
id: true,
imageUrl: true,
},
},
order: { createdAt: "DESC" },
withDeleted: false,
});
return { danakServices };
}
/******************************************** */
async getDanakServiceByIdWithSubs(serviceId: string, isAdmin: boolean, userId: string) {
const danakService = await this.danakServicesRepository.getDanakServiceById(serviceId, 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, serviceId);
return {
danakService,
averageRating,
purchased: !!hasSubscription,
};
}
/******************************************** */
async getDanakServiceByIdPublic(serviceId: string) {
const danakService = await this.danakServicesRepository.getDanakServiceById(serviceId, 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);
const users = await this.danakServicesRepository.getUsersByService(serviceId);
return { users };
}
/******************************************** */
async getDanakServiceReviews(queryDto: DanakServiceReviewQueryDto) {
const [reviews, count] = await this.danakServiceReviewRepository.getDanakServiceReviews(queryDto);
return {
reviews,
count,
paginate: true,
};
}
/******************************************** */
async getServiceByName(name: string) {
const danakService = await this.danakServicesRepository.findOneByName(name);
if (!danakService) throw new BadRequestException(ServiceMessage.SERVICE_NOT_EXIST);
return danakService;
}
/******************************************** */
async findServiceById(id: string) {
const service = await this.danakServicesRepository.findOne({ where: { id, deletedAt: IsNull() }, relations: { images: true } });
if (!service) throw new BadRequestException(ServiceMessage.SERVICE_NOT_EXIST);
return service;
}
/******************************************** */
async findServicesByIds(ids: string[]) {
const services = await this.danakServicesRepository.findBy({ id: In(ids), deletedAt: IsNull() });
return services;
}
/******************************************** */
async addReview(userId: string, serviceId: string, addReviewDto: AddReviewDto) {
const service = await this.findServiceById(serviceId);
const hasSubscription = await this.checkUserPurchasedService(userId, serviceId);
if (!hasSubscription) throw new BadRequestException(SubscriptionMessage.NOT_PURCHASED_CANNOT_REVIEW);
// // Check if user already reviewed this service
// const existingReview = await this.danakServiceReviewRepository.findOne({
// where: { user: { id: userId }, service: { id: serviceId } },
// });
// if (existingReview) {
// throw new BadRequestException(ServiceMessage.ALREADY_REVIEWED);
// }
const review = this.danakServiceReviewRepository.create({ user: { id: userId }, ...addReviewDto, service });
await this.danakServiceReviewRepository.save(review);
return {
message: ServiceMessage.REVIEW_ADDED,
review,
};
}
/******************************************** */
async updateDanakServiceReviewStatus(updateParamDto: UpdateReviewStatusDto) {
if (updateParamDto.status === ReviewStatus.APPROVED) {
return await this.approveReview(updateParamDto.id);
} else {
return await this.rejectReview(updateParamDto.id);
}
}
async approveReview(reviewId: string) {
const review = await this.danakServiceReviewRepository.findOneBy({ id: reviewId });
if (!review) throw new BadRequestException(ServiceMessage.REVIEW_NOT_EXIST);
review.status = ReviewStatus.APPROVED;
await this.danakServiceReviewRepository.save(review);
return {
message: ServiceMessage.REVIEW_APPROVED,
review,
};
}
/******************************************** */
async rejectReview(reviewId: string) {
const review = await this.danakServiceReviewRepository.findOneBy({ id: reviewId });
if (!review) throw new BadRequestException(ServiceMessage.REVIEW_NOT_EXIST);
review.status = ReviewStatus.REJECTED;
await this.danakServiceReviewRepository.save(review);
return {
message: ServiceMessage.REVIEW_REJECTED,
review,
};
}
/******************************************** */
async getServicesCount() {
const count = await this.danakServicesRepository.count({
where: {
isActive: true,
deletedAt: IsNull(),
},
});
return count;
}
/******************************************** */
private async checkUserPurchasedService(userId: string, serviceId: string) {
const subscription = await this.danakServicesRepository
.createQueryBuilder()
.select("1")
.from("user_subscription", "us")
.innerJoin("subscription_plan", "sp", "us.planId = sp.id")
.where("us.userId = :userId", { userId })
.andWhere("sp.serviceId = :serviceId", { serviceId })
.andWhere(`(us.status = '${SubscriptionStatus.ACTIVE}')`)
.andWhere("us.endDate > NOW()")
.andWhere("us.startDate < NOW()")
.limit(1)
.getRawOne();
return !!subscription;
}
}