184 lines
7.2 KiB
TypeScript
184 lines
7.2 KiB
TypeScript
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<IDanakCheckAccessResponse>(`${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
|
|
}
|
|
}
|