Assist_Design/apps/bff/src/integrations/whmcs/services/whmcs-subscription.service.ts

183 lines
5.9 KiB
TypeScript

import { getErrorMessage } from "@bff/core/utils/error.util";
import { Logger } from "nestjs-pino";
import { Injectable, NotFoundException, Inject } from "@nestjs/common";
import { Subscription, SubscriptionList } from "@customer-portal/domain";
import { WhmcsConnectionOrchestratorService } from "../connection/services/whmcs-connection-orchestrator.service";
import { SubscriptionTransformerService } from "../transformers/services/subscription-transformer.service";
import { WhmcsCacheService } from "../cache/whmcs-cache.service";
import { WhmcsGetClientsProductsParams } from "../types/whmcs-api.types";
export interface SubscriptionFilters {
status?: string;
}
@Injectable()
export class WhmcsSubscriptionService {
constructor(
@Inject(Logger) private readonly logger: Logger,
private readonly connectionService: WhmcsConnectionOrchestratorService,
private readonly subscriptionTransformer: SubscriptionTransformerService,
private readonly cacheService: WhmcsCacheService
) {}
/**
* Get client subscriptions/services with caching
*/
async getSubscriptions(
clientId: number,
userId: string,
filters: SubscriptionFilters = {}
): Promise<SubscriptionList> {
try {
// Try cache first
const cached = await this.cacheService.getSubscriptionsList(userId);
if (cached) {
this.logger.debug(`Cache hit for subscriptions: user ${userId}`);
// Apply status filter if needed
if (filters.status) {
const statusFilter = filters.status.toLowerCase();
const filtered = cached.subscriptions.filter(
(sub: Subscription) => sub.status.toLowerCase() === statusFilter
);
return {
subscriptions: filtered,
totalCount: filtered.length,
};
}
return cached;
}
// Fetch from WHMCS API
const params: WhmcsGetClientsProductsParams = {
clientid: clientId,
orderby: "regdate",
order: "DESC",
};
const response = await this.connectionService.getClientsProducts(params);
// Debug logging to understand the response structure
const productContainer = response.products?.product;
const products = Array.isArray(productContainer)
? productContainer
: productContainer
? [productContainer]
: [];
this.logger.debug(`WHMCS GetClientsProducts response structure for client ${clientId}`, {
hasResponse: Boolean(response),
responseKeys: response ? Object.keys(response) : [],
hasProducts: Boolean(response.products),
productType: productContainer ? typeof productContainer : "undefined",
productCount: products.length,
});
if (products.length === 0) {
this.logger.warn(`No products found for client ${clientId}`, {
responseStructure: response ? Object.keys(response) : "null response",
});
return {
subscriptions: [],
totalCount: 0,
};
}
const subscriptions = products
.map(whmcsProduct => {
try {
return this.subscriptionTransformer.transformSubscription(whmcsProduct);
} catch (error) {
this.logger.error(`Failed to transform subscription ${whmcsProduct.id}`, {
error: getErrorMessage(error),
});
return null;
}
})
.filter((subscription): subscription is Subscription => subscription !== null);
const result: SubscriptionList = {
subscriptions,
totalCount: subscriptions.length,
};
// Cache the result
await this.cacheService.setSubscriptionsList(userId, result);
this.logger.log(`Fetched ${subscriptions.length} subscriptions for client ${clientId}`);
// Apply status filter if needed
if (filters.status) {
const statusFilter = filters.status.toLowerCase();
const filtered = result.subscriptions.filter(
(sub: Subscription) => sub.status.toLowerCase() === statusFilter
);
return {
subscriptions: filtered,
totalCount: filtered.length,
};
}
return result;
} catch (error) {
this.logger.error(`Failed to fetch subscriptions for client ${clientId}`, {
error: getErrorMessage(error),
filters,
});
throw error;
}
}
/**
* Get individual subscription by ID
*/
async getSubscriptionById(
clientId: number,
userId: string,
subscriptionId: number
): Promise<Subscription> {
try {
// Try cache first
const cached = await this.cacheService.getSubscription(userId, subscriptionId);
if (cached) {
this.logger.debug(
`Cache hit for subscription: user ${userId}, subscription ${subscriptionId}`
);
return cached;
}
// Get all subscriptions and find the specific one
const subscriptionList = await this.getSubscriptions(clientId, userId);
const subscription = subscriptionList.subscriptions.find(
(s: Subscription) => s.id === subscriptionId
);
if (!subscription) {
throw new NotFoundException(`Subscription ${subscriptionId} not found`);
}
// Cache the individual subscription
await this.cacheService.setSubscription(userId, subscriptionId, subscription);
this.logger.log(`Fetched subscription ${subscriptionId} for client ${clientId}`);
return subscription;
} catch (error) {
this.logger.error(`Failed to fetch subscription ${subscriptionId} for client ${clientId}`, {
error: getErrorMessage(error),
});
throw error;
}
}
/**
* Invalidate cache for a specific subscription
*/
async invalidateSubscriptionCache(userId: string, subscriptionId: number): Promise<void> {
await this.cacheService.invalidateSubscription(userId, subscriptionId);
this.logger.log(
`Invalidated subscription cache for user ${userId}, subscription ${subscriptionId}`
);
}
}