1
0
forked from Yara724/api

lookups update per client

This commit is contained in:
2026-06-23 18:23:09 +03:30
parent 16cdf2e7b0
commit c7fb6174a0
8 changed files with 469 additions and 32 deletions

View File

@@ -0,0 +1,241 @@
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { HttpService } from "@nestjs/axios";
import {
BadGatewayException,
Injectable,
Logger,
NotFoundException,
} from "@nestjs/common";
import { firstValueFrom } from "rxjs";
import { isAxiosError } from "axios";
import {
getFanavaranClientProfile,
type FanavaranClientKey,
} from "src/core/config/fanavaran-client.config";
import {
fanavaranLookupCacheDir,
tejaratStaticAccidentFilePath,
} from "./fanavaran-lookup.config";
@Injectable()
export class FanavaranLookupService {
private readonly logger = new Logger(FanavaranLookupService.name);
private readonly getAppTokenUrl =
"https://apimanager.iraneit.com/BimeApiManager/api/EITAuthentication/GetAppToken";
private readonly loginUrl =
"https://apimanager.iraneit.com/BimeApiManager/api/EITAuthentication/Login";
constructor(private readonly httpService: HttpService) {}
private cacheFilePath(clientKey: FanavaranClientKey, fileName: string): string {
return join(fanavaranLookupCacheDir(clientKey), fileName);
}
async readCacheFile<T>(
clientKey: FanavaranClientKey,
fileName: string,
): Promise<T | null> {
try {
const content = await readFile(
this.cacheFilePath(clientKey, fileName),
"utf-8",
);
return JSON.parse(content) as T;
} catch {
return null;
}
}
async writeCacheFile(
clientKey: FanavaranClientKey,
fileName: string,
data: unknown,
): Promise<void> {
const dir = fanavaranLookupCacheDir(clientKey);
await mkdir(dir, { recursive: true });
await writeFile(
join(dir, fileName),
`${JSON.stringify(data, null, 2)}\n`,
"utf-8",
);
this.logger.log(
`Cached Fanavaran lookup ${fileName} for client ${clientKey}`,
);
}
async readTejaratStaticAccidentFile<T>(fileName: string): Promise<T> {
const cached = await readFile(tejaratStaticAccidentFilePath(fileName), "utf-8");
return JSON.parse(cached) as T;
}
private async getAppToken(config: {
appName: string;
secret: string;
}): Promise<string> {
const response = await firstValueFrom(
this.httpService.post(this.getAppTokenUrl, "", {
headers: {
appname: config.appName,
secret: config.secret,
"Content-Length": "0",
},
transformRequest: [
(_data, headers) => {
if (headers) {
delete headers["Content-Type"];
delete headers["content-type"];
}
return _data;
},
],
}),
);
const appToken =
response.headers.apptoken ||
response.headers.appToken ||
response.headers["apptoken"] ||
response.headers["appToken"];
if (!appToken) {
throw new BadGatewayException("Failed to get Fanavaran appToken");
}
return appToken;
}
private async login(
appToken: string,
config: { username: string; password: string },
): Promise<string> {
const response = await firstValueFrom(
this.httpService.post(this.loginUrl, "", {
headers: {
appToken,
userName: config.username,
password: config.password,
"Content-Length": "0",
},
transformRequest: [
(_data, headers) => {
if (headers) {
delete headers["Content-Type"];
delete headers["content-type"];
}
return _data;
},
],
}),
);
const authenticationToken =
response.headers.authenticationtoken ||
response.headers.authenticationToken ||
response.headers["authenticationtoken"] ||
response.headers["authenticationToken"] ||
response.data?.authenticationtoken ||
response.data?.authenticationToken ||
response.data?.authentication_token;
if (!authenticationToken) {
throw new BadGatewayException("Failed to get Fanavaran authenticationToken");
}
return authenticationToken;
}
async fetchFromFanavaran(
clientKey: FanavaranClientKey,
url: string,
): Promise<unknown> {
const profile = getFanavaranClientProfile(clientKey);
try {
const appToken = await this.getAppToken(profile.auth);
const authenticationToken = await this.login(appToken, profile.auth);
const response = await firstValueFrom(
this.httpService.get(url, {
headers: {
authenticationToken,
CorpId: profile.auth.corpId,
ContractId: profile.auth.contractId,
Location: profile.auth.location,
"Content-Type": "application/json",
},
timeout: 20000,
}),
);
return response.data;
} catch (error) {
const message = isAxiosError(error)
? error.response?.data?.Message ||
error.response?.data?.message ||
error.message
: error instanceof Error
? error.message
: "Fanavaran lookup request failed";
this.logger.error(
`Fanavaran lookup fetch failed for ${clientKey} (${url}): ${message}`,
);
throw new BadGatewayException(String(message));
}
}
async getRemoteLookup(
clientKey: FanavaranClientKey,
url: string,
cacheFile: string,
fallback?: () => Promise<unknown>,
): Promise<unknown> {
const cached = await this.readCacheFile(clientKey, cacheFile);
if (cached !== null) {
return cached;
}
try {
const data = await this.fetchFromFanavaran(clientKey, url);
await this.writeCacheFile(clientKey, cacheFile, data);
return data;
} catch (error) {
if (fallback) {
this.logger.warn(
`Fanavaran lookup fetch failed for ${clientKey}/${cacheFile}; using fallback`,
);
const data = await fallback();
await this.writeCacheFile(clientKey, cacheFile, data);
return data;
}
throw error;
}
}
mapFanavaranAccidentCausesToReasonOptions(
causes: unknown,
): { id: number; label: string; fanavaran: number }[] {
if (!Array.isArray(causes)) {
throw new NotFoundException("Fanavaran accident-causes response is invalid");
}
return causes
.filter(
(item) =>
item &&
typeof item === "object" &&
(item as { IsActive?: number }).IsActive === 1 &&
typeof (item as { Id?: unknown }).Id === "number",
)
.map((item) => {
const row = item as { Id: number; Caption?: string };
return {
id: row.Id,
label: row.Caption ?? String(row.Id),
fanavaran: row.Id,
};
});
}
}