import fs from "fs"; import path from "path"; import { rm } from "fs/promises"; import { ServicesAdapterMongoose } from "./Service.Adapter.Mongoose"; import { CompanyServiceView, CreateServiceParams, UpdateServiceParams, FindServicesByCompanyParams, FindServicesByIdParams, IService, IServicesManager, PaginateServicesParams, PaginateServicesResults, SetServiceFileParams, PublicServicesResults, PublicServiceView, DeleteServicesByCompany, SetPublishedStatusParams, SERVICE_PUBLISHED_STATUS, FindServicesParams, ApplyDiscountParams, DeleteDiscountParams, SysAdminPaginateServicesParams, SysAdminSetServiceBannedParams, } from "./Service.Interface"; import { EmployeeRoles } from "../Employees/Employees.Interface"; import UsersManager from "../Users/Users"; import CompaniesManager from "../Companies/Companies"; import EmployeesList from "../Employees/Employee"; import { NoPermissionMessage } from "../../helpers/NoPermissionMessage"; import { isNull } from "../../helpers/IsNull"; import getServiceImage from "../../helpers/getServiceImage"; import sharp from "sharp"; import MetricsList from "../../Models/Metrics/Metrics"; import { resizeAndCrop } from "../../helpers/resizeAndCrop"; import { TextObjectFilterResult } from "../TextObjectFilter.model"; import { validatePermissionsByCompany, validateSessionUser } from "../../helpers/check"; import Discounts from "../Discounts/Discounts"; import { DiscountType } from "../Discounts/Discounts.Interface"; class ServiceManager implements IServicesManager { services: ServicesAdapterMongoose; constructor() { this.services = new ServicesAdapterMongoose(); } private async validatePublicRatingFlagsPlan(ownerId: string): Promise { const { default: PlanSubscriptionsList } = await import("../PlanSubscriptions/PlanSubscriptons"); const subscription = await PlanSubscriptionsList.getSubscriptionByUser({ sessionUser: ownerId, }); if (!subscription || subscription.plan.price === 0) { throw new Error("Esta configuración está disponible solo para planes pagos."); } } public async setPublishedStatus(data: SetPublishedStatusParams): Promise { const sessionUser = await UsersManager.users.findOne({ _id: data.sessionUser, }); if (!sessionUser) { throw new Error("No se ha encontrado el usuario o no tiene permisos para realizar esta acción"); } const service = await this.services.findOne({ _id: data.serviceId }); if (!service) { throw new Error("El servicio no existe"); } const company = await CompaniesManager.companies.findOne({ _id: service.companyId, }); if (!company) { throw new Error("La compañia no existe"); } if (!(await EmployeesList.checkPermission(company, data.sessionUser, EmployeeRoles.ADMIN))) { throw new Error(NoPermissionMessage()); } service.published = data.published; await service.save(); } public async deleteServicesByCompany(data: DeleteServicesByCompany): Promise { const services = await this.services.find({ companyId: data.companyId, }); const rootDir = process.env.NODE_ENV !== "production" ? process.env.UPLOAD_SERVICE_FILES_ROOT_DEV : process.env.UPLOAD_SERVICE_FILES_ROOT_PRO; if (!rootDir) { throw new Error("No se ha encontrado la ruta de los archivos"); } for (const service of services) { if (!service.id) { continue; } const serviceDir = path.join(__dirname, rootDir, service.id); try { await rm(serviceDir, { recursive: true, force: true }); } catch (err) { console.error(`Error al eliminar el directorio ${serviceDir}:`, err); } } await this.services.serviceList.deleteMany({ companyId: data.companyId, }); } public async createService(data: CreateServiceParams): Promise { const sessionUser = await UsersManager.users.findOne({ _id: data.sessionUser, }); if (!sessionUser) { throw new Error("No se ha encontrado el usuario o no tiene permisos para realizar esta acción"); } const companyCheck = await CompaniesManager.companies.findOne({ _id: data.companyId, }); if (!companyCheck) { throw new Error("La compañia no existe"); } if (!(await EmployeesList.checkPermission(companyCheck, data.sessionUser, EmployeeRoles.ADMIN))) { throw new Error(NoPermissionMessage()); } const checkService = await this.services.findOne({ name: data.name, companyId: data.companyId, }); if (checkService) { throw new Error("Ya existe un servicio con ese nombre"); } const canAdd = await MetricsList.canAddService(companyCheck.ownerId); if (!canAdd) { throw new Error("Ha alcanzado el limite de servicios permitidos de acuerdo a su plan."); } if (data.showPublicScores !== undefined || data.showPublicOpinions !== undefined) { await this.validatePublicRatingFlagsPlan(String(companyCheck.ownerId)); } const newService = await this.services.create({ ...data, ...{ categoryId: companyCheck.categoryId, block: companyCheck.block, city: companyCheck.city, state: companyCheck.state, country: companyCheck.country, zipCode: companyCheck.zipCode, latitude: companyCheck.latitude, longitude: companyCheck.longitude, originalPrice: data.originalPrice, price: data.originalPrice, }, }); await MetricsList.addService({ userId: companyCheck.ownerId, quantity: 1, }); return newService; } public async updateService(data: UpdateServiceParams): Promise { const sessionUser = await UsersManager.users.findOne({ _id: data.sessionUser, }); if (!sessionUser) { throw new Error("No se ha encontrado el usuario o no tiene permisos para realizar esta acción"); } const companyCheck = await CompaniesManager.companies.findOne({ _id: data.companyId, }); if (!companyCheck) { throw new Error("La compañia no existe"); } if (!(await EmployeesList.checkPermission(companyCheck, data.sessionUser, EmployeeRoles.ADMIN))) { throw new Error(NoPermissionMessage()); } if (data.showPublicScores !== undefined || data.showPublicOpinions !== undefined) { await this.validatePublicRatingFlagsPlan(String(companyCheck.ownerId)); } await this.services.update(data); } public async applyDiscount(data: ApplyDiscountParams): Promise { await validateSessionUser({ sessionUser: data.sessionUser }); await validatePermissionsByCompany({ companyId: data.companyId, sessionUser: data.sessionUser, }); const service = await this.services.findOne({ _id: data.serviceId, companyId: data.companyId, }); if (!service) { throw new Error("El servicio no existe"); } const discount = await Discounts.discounts.discountsList.findOne({ _id: data.discountId, companyId: data.companyId, }); if (!discount) { throw new Error("El descuento no existe"); } if (discount.type == DiscountType.FIXED) { service.price = service.originalPrice - discount.value; } else if (discount.type == DiscountType.PERCENTAGE) { service.price = service.originalPrice - service.originalPrice * (discount.value / 100); } service.discountId = data.discountId; await service.save(); } public async deleteDiscount(data: DeleteDiscountParams): Promise { await validateSessionUser({ sessionUser: data.sessionUser }); await validatePermissionsByCompany({ companyId: data.companyId, sessionUser: data.sessionUser, }); const service = await this.services.findOne({ _id: data.serviceId, companyId: data.companyId, }); if (!service) { throw new Error("El servicio no existe"); } service.discountId = undefined; service.price = service.originalPrice; await service.save(); } public async paginate(filters: PaginateServicesParams): Promise { return await this.services.paginate(filters); } public async findByCompanyId(data: FindServicesByCompanyParams): Promise { const returnValue: CompanyServiceView[] = []; const companyServices = await this.services.find({ companyId: data.companyId, }); const defColor = "#" + String(process.env.DEFAULT_SERVICE_COLOR); const defFontColor = "#" + String(process.env.DEFAULT_SERVICE_FONT_COLOR); const defFontShadowColor = "#" + String(process.env.DEFAULT_SERVICE_FONT_SHADOW_COLOR); for (const service of companyServices) { let discountId = ""; let discountName = ""; let discountCode = ""; let discountType = ""; let discountValue = 0; if (service.discountId) { const discount = await Discounts.discounts.discountsList.findOne({ _id: String(service.discountId), companyId: data.companyId, }); if (discount) { discountId = discount.id; discountName = discount.name; discountCode = discount.code; discountType = discount.type; discountValue = discount.value; } } returnValue.push({ id: isNull(service.id, ""), name: service.name, description: service.description, length: service.length, price: service.price, originalPrice: service.originalPrice, discountId: discountId, discountName: discountName, discountCode: discountCode, discountType: discountType, discountValue: discountValue, limit: service.limit, image: getServiceImage(isNull(service.id, ""), isNull(service.image, ""), ""), color: isNull(service.color, defColor), fontColor: isNull(service.fontColor, defFontColor), fontShadowColor: isNull(service.fontShadowColor, defFontShadowColor), published: isNull( service.published, SERVICE_PUBLISHED_STATUS.PRIVATE ), showPublicScores: service.showPublicScores ?? true, showPublicOpinions: service.showPublicOpinions ?? true, }); } return returnValue; } public async findById(data: FindServicesByIdParams): Promise { const service = await this.services.findOne({ _id: data.serviceId }); if (!service) { throw new Error("El servicio no existe"); } const defColor = "#" + String(process.env.DEFAULT_SERVICE_COLOR); const defFontColor = "#" + String(process.env.DEFAULT_SERVICE_FONT_COLOR); const defFontShadowColor = "#" + String(process.env.DEFAULT_SERVICE_FONT_SHADOW_COLOR); let discountId = ""; let discountName = ""; let discountCode = ""; let discountType = ""; let discountValue = 0; if (service.discountId) { const discount = await Discounts.discounts.discountsList.findOne({ _id: String(service.discountId), companyId: String(service.companyId), }); if (discount) { discountId = discount.id; discountName = discount.name; discountCode = discount.code; discountType = discount.type; discountValue = discount.value; } } return { id: isNull(service.id, ""), name: service.name, description: service.description, length: service.length, price: service.price, originalPrice: service.originalPrice, discountId: discountId, discountName: discountName, discountCode: discountCode, discountType: discountType, discountValue: discountValue, limit: service.limit, image: getServiceImage(isNull(service.id, ""), isNull(service.image, ""), ""), color: isNull(service.color, defColor), fontColor: isNull(service.fontColor, defFontColor), fontShadowColor: isNull(service.fontShadowColor, defFontShadowColor), published: isNull(service.published, SERVICE_PUBLISHED_STATUS.PRIVATE), showPublicScores: service.showPublicScores ?? true, showPublicOpinions: service.showPublicOpinions ?? true, }; } public async setServiceImage(data: SetServiceFileParams): Promise { const sessionUser = await UsersManager.users.findOne({ _id: data.sessionUser, }); if (!sessionUser) { throw new Error("No se ha encontrado el usuario o no tiene permisos para realizar esta acción"); } const companyCheck = await CompaniesManager.companies.findOne({ _id: data.companyId, }); if (!companyCheck) { throw new Error("La compañia no existe"); } if (!(await EmployeesList.checkPermission(companyCheck, data.sessionUser, EmployeeRoles.ADMIN))) { throw new Error(NoPermissionMessage()); } const serviceCheck = await this.services.findOne({ _id: data.serviceId, }); if (!serviceCheck) { throw new Error("El servicio no existe"); } if (!data.file) { throw new Error("No se ha enviado el archivo"); } const rootDir = process.env.NODE_ENV !== "production" ? process.env.UPLOAD_SERVICE_FILES_ROOT_DEV : process.env.UPLOAD_SERVICE_FILES_ROOT_PRO; const uploadPath = path.join(__dirname, rootDir + data.serviceId); if (!fs.existsSync(uploadPath)) { fs.mkdirSync(uploadPath, { recursive: true }); } const fileExtension = data.file.originalname.split(".").pop(); const newFileName = "image." + fileExtension; const filePath = path.join(uploadPath, newFileName); const bufferImage = await resizeAndCrop(sharp(data.file.buffer), 378, 378); bufferImage.webp().toFile(filePath); //await sharp(data.file.buffer).resize({ height: 150 }).webp().toFile(filePath); serviceCheck.image = newFileName; await serviceCheck.save(); } public async publicServices(data: PaginateServicesParams): Promise { // Only fetch services that are marked as published data.published = SERVICE_PUBLISHED_STATUS.PUBLISHED; const services = await this.services.paginateFiltered(data); const formattedServices: PublicServiceView[] = []; for (const service of services.data) { const organization = await CompaniesManager.companies.findOne({ _id: service.companyId, }); // Only include services whose organization is also published if (!organization || organization.published !== "published" || organization.banned || service.banned) { continue; } const defColor = "#" + String(process.env.DEFAULT_HEADER_COLOR); const defFontColor = "#" + String(process.env.DEFAULT_HEADER_FONT_COLOR); const defFontShadowColor = "#" + String(process.env.DEFAULT_HEADER_FONT_SHADOW_COLOR); let discountId = ""; let discountName = ""; let discountCode = ""; let discountType = ""; let discountValue = 0; if (service.discountId) { const discount = await Discounts.discounts.discountsList.findOne({ _id: String(service.discountId), companyId: String(service.companyId), }); if (discount) { discountId = discount.id; discountName = discount.name; discountCode = discount.code; discountType = discount.type; discountValue = discount.value; } } formattedServices.push({ id: isNull(service.id, ""), name: service.name, description: service.description, length: service.length, price: service.price, originalPrice: service.originalPrice, discountId: discountId, discountName: discountName, discountCode: discountCode, discountType: discountType, discountValue: discountValue, limit: service.limit, image: getServiceImage(isNull(service.id, ""), isNull(service.image, ""), ""), organizationId: isNull(service.companyId, ""), organizationName: isNull(organization.name, ""), categoryId: service.categoryId, color: isNull(service.color, defColor), fontColor: isNull(service.fontColor, defFontColor), fontShadowColor: isNull(service.fontShadowColor, defFontShadowColor), showPublicScores: service.showPublicScores ?? true, showPublicOpinions: service.showPublicOpinions ?? true, }); } const results: PublicServicesResults = { data: formattedServices, page: services.page, pages: services.pages, }; return results; } public async textObjectFilter(data: FindServicesParams): Promise { const services = await this.services.find(data); const formattedResult: TextObjectFilterResult[] = []; for (const service of services) { formattedResult.push({ id: isNull(service.id, ""), name: service.name, image: "", }); } return formattedResult; } public async sysAdminPaginate(data: SysAdminPaginateServicesParams): Promise { return await this.services.sysAdminPaginate(data); } public async sysAdminSetBanned(data: SysAdminSetServiceBannedParams): Promise { const serviceCheck = await this.services.findOne({ _id: data.id }); if (!serviceCheck) { throw new Error("El servicio no existe"); } await this.services.update({ id: data.id, companyId: serviceCheck.companyId, // needed by UpdateServiceParams banned: data.banned, sessionUser: "" // sysadmin override }); } } const ServiceList = new ServiceManager(); export default ServiceList;