183 lines
5.9 KiB
TypeScript
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}`
|
|
);
|
|
}
|
|
}
|