Assist_Design/apps/bff/src/integrations/freebit/services/freebit-account.service.ts

122 lines
3.7 KiB
TypeScript
Raw Normal View History

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<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;
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<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", {
error: extractErrorMessage(error),
});
return false;
}
}
}