Assist_Design/apps/bff/src/vendors/whmcs/cache/whmcs-cache.service.ts

396 lines
12 KiB
TypeScript
Raw Normal View History

import { Injectable, Logger } from '@nestjs/common';
2025-08-21 15:24:40 +09:00
import { CacheService } from '../../../common/cache/cache.service';
import { Invoice, InvoiceList, Subscription, SubscriptionList, PaymentMethodList, PaymentGatewayList } from '@customer-portal/shared';
export interface CacheOptions {
ttl?: number;
tags?: string[];
}
export interface CacheKeyConfig {
prefix: string;
ttl: number;
tags: string[];
}
@Injectable()
export class WhmcsCacheService {
private readonly logger = new Logger(WhmcsCacheService.name);
// Cache configuration for different data types
private readonly cacheConfigs: Record<string, CacheKeyConfig> = {
invoices: {
prefix: 'whmcs:invoices',
ttl: 90, // 90 seconds - invoices change frequently
tags: ['invoices', 'billing'],
},
invoice: {
prefix: 'whmcs:invoice',
ttl: 300, // 5 minutes - individual invoices change less frequently
tags: ['invoice', 'billing'],
},
subscriptions: {
prefix: 'whmcs:subscriptions',
ttl: 300, // 5 minutes - subscriptions change less frequently
tags: ['subscriptions', 'services'],
},
subscription: {
prefix: 'whmcs:subscription',
ttl: 600, // 10 minutes - individual subscriptions rarely change
tags: ['subscription', 'services'],
},
client: {
prefix: 'whmcs:client',
ttl: 1800, // 30 minutes - client data rarely changes
tags: ['client', 'user'],
},
sso: {
prefix: 'whmcs:sso',
ttl: 3600, // 1 hour - SSO tokens have their own expiry
tags: ['sso', 'auth'],
},
paymentMethods: {
prefix: 'whmcs:paymentmethods',
ttl: 900, // 15 minutes - payment methods change occasionally
tags: ['paymentmethods', 'billing'],
},
paymentGateways: {
prefix: 'whmcs:paymentgateways',
ttl: 3600, // 1 hour - payment gateways rarely change
tags: ['paymentgateways', 'config'],
},
};
constructor(private readonly cacheService: CacheService) {}
/**
* Get cached invoices list for a user
*/
async getInvoicesList(
userId: string,
page: number,
limit: number,
status?: string
): Promise<InvoiceList | null> {
const key = this.buildInvoicesKey(userId, page, limit, status);
return this.get<InvoiceList>(key, 'invoices');
}
/**
* Cache invoices list for a user
*/
async setInvoicesList(
userId: string,
page: number,
limit: number,
status: string | undefined,
data: InvoiceList
): Promise<void> {
const key = this.buildInvoicesKey(userId, page, limit, status);
await this.set(key, data, 'invoices', [`user:${userId}`]);
}
/**
* Get cached individual invoice
*/
async getInvoice(userId: string, invoiceId: number): Promise<Invoice | null> {
const key = this.buildInvoiceKey(userId, invoiceId);
return this.get<Invoice>(key, 'invoice');
}
/**
* Cache individual invoice
*/
async setInvoice(userId: string, invoiceId: number, data: Invoice): Promise<void> {
const key = this.buildInvoiceKey(userId, invoiceId);
await this.set(key, data, 'invoice', [`user:${userId}`, `invoice:${invoiceId}`]);
}
/**
* Get cached subscriptions list for a user
*/
async getSubscriptionsList(userId: string): Promise<SubscriptionList | null> {
const key = this.buildSubscriptionsKey(userId);
return this.get<SubscriptionList>(key, 'subscriptions');
}
/**
* Cache subscriptions list for a user
*/
async setSubscriptionsList(userId: string, data: SubscriptionList): Promise<void> {
const key = this.buildSubscriptionsKey(userId);
await this.set(key, data, 'subscriptions', [`user:${userId}`]);
}
/**
* Get cached individual subscription
*/
async getSubscription(userId: string, subscriptionId: number): Promise<Subscription | null> {
const key = this.buildSubscriptionKey(userId, subscriptionId);
return this.get<Subscription>(key, 'subscription');
}
/**
* Cache individual subscription
*/
async setSubscription(userId: string, subscriptionId: number, data: Subscription): Promise<void> {
const key = this.buildSubscriptionKey(userId, subscriptionId);
await this.set(key, data, 'subscription', [`user:${userId}`, `subscription:${subscriptionId}`]);
}
/**
* Get cached client data
*/
async getClientData(clientId: number): Promise<any | null> {
const key = this.buildClientKey(clientId);
return this.get<any>(key, 'client');
}
/**
* Cache client data
*/
async setClientData(clientId: number, data: any): Promise<void> {
const key = this.buildClientKey(clientId);
await this.set(key, data, 'client', [`client:${clientId}`]);
}
/**
* Invalidate all cache for a specific user
*/
async invalidateUserCache(userId: string): Promise<void> {
try {
const patterns = [
`${this.cacheConfigs.invoices.prefix}:${userId}:*`,
`${this.cacheConfigs.invoice.prefix}:${userId}:*`,
`${this.cacheConfigs.subscriptions.prefix}:${userId}:*`,
`${this.cacheConfigs.subscription.prefix}:${userId}:*`,
];
await Promise.all(patterns.map(pattern => this.cacheService.delPattern(pattern)));
this.logger.log(`Invalidated all cache for user ${userId}`);
} catch (error) {
this.logger.error(`Failed to invalidate cache for user ${userId}`, error);
}
}
/**
* Invalidate cache by tags
*/
async invalidateByTag(tag: string): Promise<void> {
try {
// This would require a more sophisticated cache implementation with tag support
// For now, we'll use pattern-based invalidation
const patterns = Object.values(this.cacheConfigs)
.filter(config => config.tags.includes(tag))
.map(config => `${config.prefix}:*`);
await Promise.all(patterns.map(pattern => this.cacheService.delPattern(pattern)));
this.logger.log(`Invalidated cache by tag: ${tag}`);
} catch (error) {
this.logger.error(`Failed to invalidate cache by tag ${tag}`, error);
}
}
/**
* Invalidate specific invoice cache
*/
async invalidateInvoice(userId: string, invoiceId: number): Promise<void> {
try {
const specificKey = this.buildInvoiceKey(userId, invoiceId);
const listPattern = `${this.cacheConfigs.invoices.prefix}:${userId}:*`;
await Promise.all([
this.cacheService.del(specificKey),
this.cacheService.delPattern(listPattern),
]);
this.logger.log(`Invalidated invoice cache for user ${userId}, invoice ${invoiceId}`);
} catch (error) {
this.logger.error(`Failed to invalidate invoice cache for user ${userId}, invoice ${invoiceId}`, error);
}
}
/**
* Invalidate specific subscription cache
*/
async invalidateSubscription(userId: string, subscriptionId: number): Promise<void> {
try {
const specificKey = this.buildSubscriptionKey(userId, subscriptionId);
const listKey = this.buildSubscriptionsKey(userId);
await Promise.all([
this.cacheService.del(specificKey),
this.cacheService.del(listKey),
]);
this.logger.log(`Invalidated subscription cache for user ${userId}, subscription ${subscriptionId}`);
} catch (error) {
this.logger.error(`Failed to invalidate subscription cache for user ${userId}, subscription ${subscriptionId}`, error);
}
}
/**
* Get cached payment methods for a user
*/
async getPaymentMethods(userId: string): Promise<PaymentMethodList | null> {
const key = this.buildPaymentMethodsKey(userId);
return this.get<PaymentMethodList>(key, 'paymentMethods');
}
/**
* Set payment methods cache for a user
*/
async setPaymentMethods(userId: string, paymentMethods: PaymentMethodList): Promise<void> {
const key = this.buildPaymentMethodsKey(userId);
await this.set(key, paymentMethods, 'paymentMethods', [userId]);
}
/**
* Get cached payment gateways (global)
*/
async getPaymentGateways(): Promise<PaymentGatewayList | null> {
const key = 'whmcs:paymentgateways:global';
return this.get<PaymentGatewayList>(key, 'paymentGateways');
}
/**
* Set payment gateways cache (global)
*/
async setPaymentGateways(paymentGateways: PaymentGatewayList): Promise<void> {
const key = 'whmcs:paymentgateways:global';
await this.set(key, paymentGateways, 'paymentGateways');
}
/**
* Invalidate payment methods cache for a user
*/
async invalidatePaymentMethods(userId: string): Promise<void> {
try {
const key = this.buildPaymentMethodsKey(userId);
await this.cacheService.del(key);
this.logger.log(`Invalidated payment methods cache for user ${userId}`);
} catch (error) {
this.logger.error(`Failed to invalidate payment methods cache for user ${userId}`, error);
}
}
/**
* Invalidate payment gateways cache (global)
*/
async invalidatePaymentGateways(): Promise<void> {
try {
const key = 'whmcs:paymentgateways:global';
await this.cacheService.del(key);
this.logger.log('Invalidated payment gateways cache');
} catch (error) {
this.logger.error('Failed to invalidate payment gateways cache', error);
}
}
/**
* Generic get method with configuration
*/
private async get<T>(key: string, configKey: string): Promise<T | null> {
try {
const data = await this.cacheService.get<T>(key);
if (data) {
this.logger.debug(`Cache hit: ${key}`);
}
return data;
} catch (error) {
this.logger.error(`Cache get error for key ${key}`, error);
return null;
}
}
/**
* Generic set method with configuration
*/
private async set<T>(
key: string,
data: T,
configKey: string,
additionalTags: string[] = []
): Promise<void> {
try {
const config = this.cacheConfigs[configKey];
await this.cacheService.set(key, data, config.ttl);
this.logger.debug(`Cache set: ${key} (TTL: ${config.ttl}s)`);
} catch (error) {
this.logger.error(`Cache set error for key ${key}`, error);
}
}
/**
* Build cache key for invoices list
*/
private buildInvoicesKey(userId: string, page: number, limit: number, status?: string): string {
return `${this.cacheConfigs.invoices.prefix}:${userId}:${page}:${limit}:${status || 'all'}`;
}
/**
* Build cache key for individual invoice
*/
private buildInvoiceKey(userId: string, invoiceId: number): string {
return `${this.cacheConfigs.invoice.prefix}:${userId}:${invoiceId}`;
}
/**
* Build cache key for subscriptions list
*/
private buildSubscriptionsKey(userId: string): string {
return `${this.cacheConfigs.subscriptions.prefix}:${userId}`;
}
/**
* Build cache key for individual subscription
*/
private buildSubscriptionKey(userId: string, subscriptionId: number): string {
return `${this.cacheConfigs.subscription.prefix}:${userId}:${subscriptionId}`;
}
/**
* Build cache key for client data
*/
private buildClientKey(clientId: number): string {
return `${this.cacheConfigs.client.prefix}:${clientId}`;
}
/**
* Build cache key for payment methods
*/
private buildPaymentMethodsKey(userId: string): string {
return `${this.cacheConfigs.paymentMethods.prefix}:${userId}`;
}
/**
* Get cache statistics
*/
async getCacheStats(): Promise<{
totalKeys: number;
keysByType: Record<string, number>;
}> {
// This would require Redis SCAN or similar functionality
// For now, return a placeholder
return {
totalKeys: 0,
keysByType: {},
};
}
/**
* Clear all WHMCS cache
*/
async clearAllCache(): Promise<void> {
try {
const patterns = Object.values(this.cacheConfigs).map(config => `${config.prefix}:*`);
await Promise.all(patterns.map(pattern => this.cacheService.delPattern(pattern)));
this.logger.warn('Cleared all WHMCS cache');
} catch (error) {
this.logger.error('Failed to clear all WHMCS cache', error);
}
}
}