import { Injectable, Inject, BadRequestException } from "@nestjs/common"; import { Logger } from "nestjs-pino"; import { extractErrorMessage } from "@bff/core/utils/error.util.js"; import { FreebitClientService } from "./freebit-client.service.js"; import { FreebitMapperService } from "./freebit-mapper.service.js"; import { FreebitAuthService } from "./freebit-auth.service.js"; import type { FreebitAccountDetailsRequest, FreebitAccountDetailsResponse, SimDetails, } from "../interfaces/freebit.types.js"; /** * Service for Freebit account/SIM details operations. * Handles fetching SIM details and health checks. */ @Injectable() export class FreebitAccountService { constructor( private readonly client: FreebitClientService, private readonly mapper: FreebitMapperService, private readonly auth: FreebitAuthService, @Inject(Logger) private readonly logger: Logger ) {} /** * Get SIM account details with endpoint fallback */ async getSimDetails(account: string): Promise { try { const request: Omit = { version: "2", requestDatas: [{ kind: "MVNO", account }], }; const config = this.auth.getConfig(); const configured = config.detailsEndpoint || "/master/getAcnt/"; const candidates = Array.from( new Set([ configured, configured.replace(/\/$/, ""), "/master/getAcnt/", "/master/getAcnt", "/mvno/getAccountDetail/", "/mvno/getAccountDetail", "/mvno/getAcntDetail/", "/mvno/getAcntDetail", "/mvno/getAccountInfo/", "/mvno/getAccountInfo", "/mvno/getSubscriberInfo/", "/mvno/getSubscriberInfo", "/mvno/getInfo/", "/mvno/getInfo", "/master/getDetail/", "/master/getDetail", ]) ); let response: FreebitAccountDetailsResponse | undefined; let lastError: unknown; for (const ep of candidates) { try { if (ep !== candidates[0]) { this.logger.warn(`Retrying Freebit account details with alternative endpoint: ${ep}`); } response = await this.client.makeAuthenticatedRequest< FreebitAccountDetailsResponse, typeof request >(ep, request); break; } catch (err: unknown) { lastError = err; if (extractErrorMessage(err).includes("HTTP 404")) { continue; // try next endpoint for 404 (endpoint not found) } // For non-404 errors (auth failures, rate limits, 500s, etc.), fail fast break; } } if (!response) { if (lastError instanceof Error) { throw lastError; } throw new Error("Failed to get SIM details from any endpoint"); } return await this.mapper.mapToSimDetails(response); } catch (error) { const message = extractErrorMessage(error); this.logger.error(`Failed to get SIM details for account ${account}`, { account, error: message, }); throw new BadRequestException(`Failed to get SIM details: ${message}`); } } /** * Health check - test API connectivity */ async healthCheck(): Promise { try { // Try a simple endpoint first const simpleCheck = await this.client.makeSimpleRequest("/"); if (simpleCheck) { return true; } // If simple check fails, try authenticated request await this.auth.getAuthKey(); return true; } catch (error) { this.logger.debug("Freebit health check failed", { error: extractErrorMessage(error), }); return false; } } }