- Deleted migration file that removed cached profile fields from the users table, centralizing profile data retrieval from WHMCS. - Updated CsrfMiddleware to include new public authentication endpoints for password reset, setting password, and WHMCS account linking. - Enhanced error handling in password and WHMCS linking workflows to provide clearer feedback on missing mappings and improve user experience. - Adjusted user creation and update methods in UsersFacade to handle cases where WHMCS mappings are not yet available, ensuring smoother account setup.
173 lines
5.4 KiB
TypeScript
173 lines
5.4 KiB
TypeScript
import { Injectable, Inject } from "@nestjs/common";
|
|
import { Logger } from "nestjs-pino";
|
|
import { SimDetailsService } from "./sim-details.service";
|
|
import { SimUsageService } from "./sim-usage.service";
|
|
import { SimTopUpService } from "./sim-topup.service";
|
|
import { SimPlanService } from "./sim-plan.service";
|
|
import { SimCancellationService } from "./sim-cancellation.service";
|
|
import { EsimManagementService } from "./esim-management.service";
|
|
import { SimValidationService } from "./sim-validation.service";
|
|
import { getErrorMessage } from "@bff/core/utils/error.util";
|
|
import type {
|
|
SimDetails,
|
|
SimUsage,
|
|
SimTopUpHistory,
|
|
} from "@bff/integrations/freebit/interfaces/freebit.types";
|
|
import type {
|
|
SimTopUpRequest,
|
|
SimPlanChangeRequest,
|
|
SimCancelRequest,
|
|
SimTopUpHistoryRequest,
|
|
SimFeaturesUpdateRequest,
|
|
} from "../types/sim-requests.types";
|
|
|
|
@Injectable()
|
|
export class SimOrchestratorService {
|
|
constructor(
|
|
private readonly simDetails: SimDetailsService,
|
|
private readonly simUsage: SimUsageService,
|
|
private readonly simTopUp: SimTopUpService,
|
|
private readonly simPlan: SimPlanService,
|
|
private readonly simCancellation: SimCancellationService,
|
|
private readonly esimManagement: EsimManagementService,
|
|
private readonly simValidation: SimValidationService,
|
|
@Inject(Logger) private readonly logger: Logger
|
|
) {}
|
|
|
|
/**
|
|
* Get SIM details for a subscription
|
|
*/
|
|
async getSimDetails(userId: string, subscriptionId: number): Promise<SimDetails> {
|
|
return this.simDetails.getSimDetails(userId, subscriptionId);
|
|
}
|
|
|
|
/**
|
|
* Get SIM data usage for a subscription
|
|
*/
|
|
async getSimUsage(userId: string, subscriptionId: number): Promise<SimUsage> {
|
|
return this.simUsage.getSimUsage(userId, subscriptionId);
|
|
}
|
|
|
|
/**
|
|
* Top up SIM data quota with payment processing
|
|
*/
|
|
async topUpSim(userId: string, subscriptionId: number, request: SimTopUpRequest): Promise<void> {
|
|
return this.simTopUp.topUpSim(userId, subscriptionId, request);
|
|
}
|
|
|
|
/**
|
|
* Get SIM top-up history
|
|
*/
|
|
async getSimTopUpHistory(
|
|
userId: string,
|
|
subscriptionId: number,
|
|
request: SimTopUpHistoryRequest
|
|
): Promise<SimTopUpHistory> {
|
|
return this.simUsage.getSimTopUpHistory(userId, subscriptionId, request);
|
|
}
|
|
|
|
/**
|
|
* Change SIM plan
|
|
*/
|
|
async changeSimPlan(
|
|
userId: string,
|
|
subscriptionId: number,
|
|
request: SimPlanChangeRequest
|
|
): Promise<{ ipv4?: string; ipv6?: string }> {
|
|
return this.simPlan.changeSimPlan(userId, subscriptionId, request);
|
|
}
|
|
|
|
/**
|
|
* Update SIM features (voicemail, call waiting, roaming, network type)
|
|
*/
|
|
async updateSimFeatures(
|
|
userId: string,
|
|
subscriptionId: number,
|
|
request: SimFeaturesUpdateRequest
|
|
): Promise<void> {
|
|
return this.simPlan.updateSimFeatures(userId, subscriptionId, request);
|
|
}
|
|
|
|
/**
|
|
* Cancel SIM service
|
|
*/
|
|
async cancelSim(
|
|
userId: string,
|
|
subscriptionId: number,
|
|
request: SimCancelRequest = {}
|
|
): Promise<void> {
|
|
return this.simCancellation.cancelSim(userId, subscriptionId, request);
|
|
}
|
|
|
|
/**
|
|
* Reissue eSIM profile
|
|
*/
|
|
async reissueEsimProfile(userId: string, subscriptionId: number, newEid?: string): Promise<void> {
|
|
return this.esimManagement.reissueEsimProfile(userId, subscriptionId, newEid);
|
|
}
|
|
|
|
/**
|
|
* Get comprehensive SIM information (details + usage combined)
|
|
*/
|
|
async getSimInfo(
|
|
userId: string,
|
|
subscriptionId: number
|
|
): Promise<{
|
|
details: SimDetails;
|
|
usage: SimUsage;
|
|
}> {
|
|
try {
|
|
const [details, usage] = await Promise.all([
|
|
this.getSimDetails(userId, subscriptionId),
|
|
this.getSimUsage(userId, subscriptionId),
|
|
]);
|
|
|
|
// If Freebit doesn't return remaining quota, derive it from plan code (e.g., PASI_50G)
|
|
// by subtracting measured usage (today + recentDays) from the plan cap.
|
|
const normalizeNumber = (n: number) => (isFinite(n) && n > 0 ? n : 0);
|
|
const usedMb =
|
|
normalizeNumber(usage.todayUsageMb) +
|
|
(usage.recentDaysUsage || []).reduce((sum, d) => sum + normalizeNumber(d.usageMb), 0);
|
|
|
|
const planCapMatch = (details.planCode || "").match(/(\d+)\s*G/i);
|
|
if ((details.remainingQuotaMb === 0 || details.remainingQuotaMb == null) && planCapMatch) {
|
|
const capGb = parseInt(planCapMatch[1], 10);
|
|
if (!isNaN(capGb) && capGb > 0) {
|
|
const capMb = capGb * 1000;
|
|
const remainingMb = Math.max(capMb - usedMb, 0);
|
|
details.remainingQuotaMb = Math.round(remainingMb * 100) / 100;
|
|
details.remainingQuotaKb = Math.round(details.remainingQuotaMb * 1000);
|
|
}
|
|
}
|
|
|
|
return { details, usage };
|
|
} catch (error) {
|
|
const sanitizedError = getErrorMessage(error);
|
|
this.logger.error(`Failed to get comprehensive SIM info for subscription ${subscriptionId}`, {
|
|
error: sanitizedError,
|
|
userId,
|
|
subscriptionId,
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Debug method to check subscription data for SIM services
|
|
*/
|
|
async debugSimSubscription(
|
|
userId: string,
|
|
subscriptionId: number
|
|
): Promise<Record<string, unknown>> {
|
|
return this.simValidation.debugSimSubscription(userId, subscriptionId);
|
|
}
|
|
|
|
/**
|
|
* Debug method to query Freebit directly for any account's details
|
|
* Bypasses subscription validation and queries Freebit directly
|
|
*/
|
|
async getSimDetailsDirectly(account: string): Promise<SimDetails> {
|
|
return this.simDetails.getSimDetailsDirectly(account);
|
|
}
|
|
}
|