2025-12-26 18:17:37 +09:00
|
|
|
import { Injectable, Inject, BadRequestException } from "@nestjs/common";
|
|
|
|
|
import { Logger } from "nestjs-pino";
|
2025-12-29 15:07:11 +09:00
|
|
|
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
|
2025-12-26 18:17:37 +09:00
|
|
|
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<SimDetails> {
|
|
|
|
|
try {
|
|
|
|
|
const request: Omit<FreebitAccountDetailsRequest, "authKey"> = {
|
|
|
|
|
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;
|
2025-12-29 15:07:11 +09:00
|
|
|
if (extractErrorMessage(err).includes("HTTP 404")) {
|
2025-12-26 18:17:37 +09:00
|
|
|
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) {
|
2025-12-29 15:07:11 +09:00
|
|
|
const message = extractErrorMessage(error);
|
2025-12-26 18:17:37 +09:00
|
|
|
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<boolean> {
|
|
|
|
|
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", {
|
2025-12-29 15:07:11 +09:00
|
|
|
error: extractErrorMessage(error),
|
2025-12-26 18:17:37 +09:00
|
|
|
});
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|