578 lines
20 KiB
TypeScript
Executable File
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;
|
|
}
|
|
}
|