import { EntityManager } from "@mikro-orm/postgresql"; import { HttpService } from "@nestjs/axios"; import { BadRequestException, Inject, Injectable, InternalServerErrorException, Logger } from "@nestjs/common"; import { AxiosError } from "axios"; import { catchError, firstValueFrom } from "rxjs"; import { QuotaPurchaseService } from "./quota-purchase.service"; import { BusinessMessage } from "../../../common/enums/message.enum"; import { DANAKCORP_CONFIG, IDanakCorpConfig } from "../../../configs/danakcorp.config"; import { QUOTA_CONSTANTS, SUBSCRIPTIONS } from "../constant"; import { PurchaseQuotaDto } from "../DTO/purchase-quota.dto"; import { UpdateBusinessSettingsDto } from "../DTO/update-business-settings.dto"; import { BusinessStaff } from "../entities/business-staff.entity"; import { Business } from "../entities/business.entity"; import { IDanakCheckAccessResponse } from "../interfaces/IBusiness"; import { BusinessRepository } from "../repositories/business.repository"; import { FindBusinessesDto } from "../DTO/find-businesses.dto"; @Injectable() export class BusinessesService { private readonly logger = new Logger(BusinessesService.name); constructor( private readonly businessRepository: BusinessRepository, @Inject(DANAKCORP_CONFIG) private readonly danakCorpConfig: IDanakCorpConfig, private readonly httpService: HttpService, private readonly em: EntityManager, private readonly quotaPurchaseService: QuotaPurchaseService, ) { } async getBusinessByDanakSubscriptionId(danakSubscriptionId: string) { const business = await this.businessRepository.findOne({ danakSubscriptionId, deletedAt: null }); if (!business) throw new BadRequestException(BusinessMessage.NOT_FOUND); return business; } //************************************************** */ async hasAccessToService(business: Business, userId: string) { try { const { data } = await firstValueFrom( this.httpService .get(`${this.danakCorpConfig.apiUrl}/subscriptions/check-access/${SUBSCRIPTIONS.DMAIL_SERVICE_ID}`, { params: { userId, danakSubscriptionId: business.danakSubscriptionId, }, headers: { "X-API-Key": this.danakCorpConfig.apiKey, }, timeout: this.danakCorpConfig.timeout, }) .pipe( catchError((error: AxiosError) => { this.logger.error(`Failed to check access to service for business ${business.id}:`, error.message); throw new InternalServerErrorException(BusinessMessage.USER_DOES_NOT_HAVE_ACCESS_TO_SERVICE); }), ), ); if (!data.success || !data.data?.hasAccess) { this.logger.error(`DanakCorp API returned error for business ${business.id}:`, data.error); throw new InternalServerErrorException(BusinessMessage.QUOTA_PURCHASE_FAILED); } return data.data?.hasAccess; } catch (error) { this.logger.error(`Failed to check access to service for business ${business.id}:`, error); throw new InternalServerErrorException(BusinessMessage.USER_DOES_NOT_HAVE_ACCESS_TO_SERVICE); } } //************************************************** */ async getBusinessBySlug(slug: string) { const business = await this.businessRepository.findOne({ slug, deletedAt: null }); if (!business) throw new BadRequestException(BusinessMessage.NOT_FOUND); return { business }; } //************************************************** */ async updateBusinessSettings(businessId: string, settingsDto: UpdateBusinessSettingsDto) { const business = await this.businessRepository.findOne({ id: businessId, deletedAt: null }); if (!business) throw new BadRequestException(BusinessMessage.NOT_FOUND); this.businessRepository.assign(business, settingsDto); await this.em.flush(); return { message: BusinessMessage.BUSINESS_SETTINGS_UPDATED, business, }; } //************************************************** */ async getBusinessSettings(businessId: string) { const business = await this.businessRepository.findOne({ id: businessId, deletedAt: null }); if (!business) throw new BadRequestException(BusinessMessage.NOT_FOUND); return { business }; } //************************************************** */ async getBusinessById(id: string) { const business = await this.businessRepository.findOne({ id, deletedAt: null }); if (!business) throw new BadRequestException(BusinessMessage.NOT_FOUND); return business; } //************************************************** */ async findAdminWithLeastTickets() { // First query to get the ID of the business staff with the least tickets const result = await this.em.getConnection().execute( ` SELECT bs.id FROM business_staff bs WHERE bs.deleted_at IS NULL ORDER BY ( SELECT COUNT(*) FROM ticket t WHERE t.assigned_to_id = bs.id ) ASC LIMIT 1 `, ); if (result.length === 0) return null; return this.em.findOne(BusinessStaff, { id: result[0].id }); } /*******************************/ async findOneByIdWithEntityManager(staffId: string, em: EntityManager) { const staff = await em.findOne(BusinessStaff, { id: staffId }); if (!staff) throw new BadRequestException(BusinessMessage.STAFF_NOT_FOUND); return staff; } //************************************************** */ async getBusinessQuota(businessId: string) { const business = await this.businessRepository.findOne({ id: businessId, deletedAt: null }); if (!business) throw new BadRequestException(BusinessMessage.NOT_FOUND); return { quota: { quotaPricePerGB: QUOTA_CONSTANTS.QUOTA_PRICE_PER_GB, total: Number(business.quota), used: Number(business.usedQuota), remaining: Number(business.remainingQuota), totalInMB: Math.round(Number(business.quota) / (1024 * 1024)), totalInGB: Math.round(Number(business.quota) / (1024 * 1024 * 1024)), usedInMB: Math.round(Number(business.usedQuota) / (1024 * 1024)), usedInGB: Math.round(Number(business.usedQuota) / (1024 * 1024 * 1024)), remainingInMB: Math.round(Number(business.remainingQuota) / (1024 * 1024)), remainingInGB: Math.round(Number(business.remainingQuota) / (1024 * 1024 * 1024)), usagePercentage: Math.round((Number(business.usedQuota) / Number(business.quota)) * 100), }, }; } //************************************************** */ async purchaseQuota(businessId: string, purchaseDto: PurchaseQuotaDto) { const business = await this.businessRepository.findOne({ id: businessId, deletedAt: null }); if (!business) throw new BadRequestException(BusinessMessage.NOT_FOUND); return this.quotaPurchaseService.purchaseQuota(business, purchaseDto); } //********************************************* */ async findBusinesses(query: FindBusinessesDto) { return this.businessRepository.findAllPaginated(query) } //********************************************* */ async updateBusinesStatus(businessId: string, isActive: boolean) { const business = await this.getBusinessById(businessId) business.isActive = isActive await this.em.flush() return business } }