import { UploadedFileTypes } from '@/common/enums/enums' import { translateEnumValue } from '@/common/utils' import { PasswordUtil } from '@/common/utils/password.util' import { RedisKeyMaker } from '@/common/utils/redisKeyMaker' import { AccountStatus, AccountType, PartnerRole, PartnerStatus, } from '@/generated/prisma/enums' import { PartnerSelect } from '@/generated/prisma/models' import { PartnersCacheInvalidationService } from '@/modules/partners/cache/partners-cache-invalidation.service' import { UploaderService } from '@/modules/uploader/uploader.service' import { PrismaService } from '@/prisma/prisma.service' import { RedisService } from '@/redis/redis.service' import { Injectable } from '@nestjs/common' import { ResponseMapper } from 'common/response/response-mapper' import { CreatePartnerDto, UpdatePartnerDto } from './dto/partner.dto' @Injectable() export class PartnersService { private now = new Date().getTime() constructor( private readonly prisma: PrismaService, private readonly uploaderService: UploaderService, private readonly redisService: RedisService, private readonly cacheInvalidationService: PartnersCacheInvalidationService, ) {} private readonly defaultSelect: PartnerSelect = { id: true, name: true, code: true, status: true, logo_url: true, tsp_provider: true, created_at: true, license_charge_transactions: { select: { activation_expires_at: true, purchased_count: true, _count: { select: { licenses: { where: { activation: { isNot: null, }, }, }, }, }, }, }, license_renew_charge_transactions: { select: { activation_expires_at: true, purchased_count: true, _count: { select: { license_renews: { where: { activation: { isNot: undefined, }, }, }, }, }, }, }, account_quota_charge_transactions: { select: { activation_expires_at: true, purchased_count: true, _count: { select: { credits: { where: { allocation: { isNot: null, }, }, }, }, }, }, }, } private readonly mapPartner = (partner: any) => { const { license_charge_transactions, account_quota_charge_transactions, license_renew_charge_transactions, status, ...rest } = partner const account_quota_status = { total: 0, used: 0, expired: 0 } account_quota_charge_transactions.forEach((account_quota_charge_transaction: any) => { const total = account_quota_charge_transaction.purchased_count const used = account_quota_charge_transaction._count.credits account_quota_status.total += total account_quota_status.used += used account_quota_status.expired += account_quota_charge_transaction.activation_expires_at.getTime() <= this.now ? total - used : 0 }) const licenses_status = { total: 0, used: 0, expired: 0 } license_charge_transactions.forEach((license_charge_transaction: any) => { const total = license_charge_transaction.purchased_count const used = license_charge_transaction._count.licenses licenses_status.total += total licenses_status.used += used licenses_status.expired += license_charge_transaction.activation_expires_at.getTime() <= this.now ? total - used : 0 }) const license_renew_status = { total: 0, used: 0, expired: 0 } license_renew_charge_transactions.forEach((license_renew_charge_transaction: any) => { const total = license_renew_charge_transaction.purchased_count const used = license_renew_charge_transaction._count.license_renews license_renew_status.total += total license_renew_status.used += used license_renew_status.expired += license_renew_charge_transaction.activation_expires_at.getTime() <= this.now ? total - used : 0 }) return { ...rest, status: translateEnumValue('PartnerStatus', status), licenses_status, license_renew_status, account_quota_status, } } async findAll() { const cacheKey = RedisKeyMaker.partnersList() const cached = await this.redisService.getJson(cacheKey) if (cached) { console.log('cached', cached) return ResponseMapper.list(cached) } const partners = await this.prisma.partner.findMany({ select: this.defaultSelect, }) const mappedPartners = partners.map(this.mapPartner) await this.redisService.setJson(cacheKey, mappedPartners, 300) return ResponseMapper.list(mappedPartners) } async findOne(id: string) { const cacheKey = RedisKeyMaker.partnerDetail(id) const cached = await this.redisService.getJson(cacheKey) if (cached) { return ResponseMapper.single(cached) } const partner = await this.prisma.partner.findUniqueOrThrow({ where: { id }, select: this.defaultSelect, }) const mappedPartner = this.mapPartner(partner) await this.redisService.setJson(cacheKey, mappedPartner, 300) return ResponseMapper.single(mappedPartner) } async create(data: CreatePartnerDto, logo?: any) { const { username, password, ...rest } = data as any delete rest.logo const logo_url = logo ? await this.uploaderService.uploadFile(logo, UploadedFileTypes.PARTNER_LOGO) : undefined const partner = await this.prisma.partner.create({ data: { ...rest, status: PartnerStatus.ACTIVE, ...(logo_url ? { logo_url } : {}), accounts: { create: { role: PartnerRole.OWNER, account: { create: { username, password: await PasswordUtil.hash(password), status: AccountStatus.ACTIVE, type: AccountType.PARTNER, }, }, }, }, }, select: this.defaultSelect, }) await this.cacheInvalidationService.invalidatePartnersList() await this.cacheInvalidationService.invalidatePartnerDetail(partner.id) return ResponseMapper.create(partner) } async update(id: string, data: UpdatePartnerDto, logo?: any) { const rest = { ...(data as any) } delete rest.logo const partner = await this.prisma.$transaction(async tx => { let logo_url = '' if (logo) { const uploadedUrl = await this.uploaderService.uploadFile( logo, UploadedFileTypes.PARTNER_LOGO, ) logo_url = uploadedUrl || '' } const prevLogo = await tx.partner.findUnique({ where: { id }, select: { logo_url: true }, }) if (logo_url && prevLogo?.logo_url) { this.uploaderService.deleteFile(prevLogo.logo_url, UploadedFileTypes.PARTNER_LOGO) } return tx.partner.update({ where: { id }, data: { ...rest, ...(logo_url ? { logo_url } : {}), }, select: this.defaultSelect, }) }) await this.cacheInvalidationService.invalidatePartnersList() await this.cacheInvalidationService.invalidatePartnerDetail(id) return ResponseMapper.update(partner) } async delete(id: string) { await this.prisma.partner.delete({ where: { id } }) await this.cacheInvalidationService.invalidatePartnersList() await this.cacheInvalidationService.invalidatePartnerDetail(id) await this.cacheInvalidationService.invalidatePartnerLicenses(id) return ResponseMapper.delete() } }