tema 675f7d5cfd Remove cached profile fields migration and update CSRF middleware for new public auth endpoints
- 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.
2025-11-21 17:12:34 +09:00

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);
}
}