lookups api is offline

This commit is contained in:
2026-04-18 13:26:37 +03:30
parent 131df63a3f
commit e8a3873851

View File

@@ -1,401 +1,45 @@
import { import { Injectable, Logger, NotFoundException } from "@nestjs/common";
BadGatewayException,
Injectable,
Logger,
} from "@nestjs/common";
import axios from "axios";
import { LookupDbService } from "./entities/db-service/lookup.db.service"; import { LookupDbService } from "./entities/db-service/lookup.db.service";
@Injectable() @Injectable()
export class LookupsService { export class LookupsService {
private readonly logger = new Logger(LookupsService.name); private readonly logger = new Logger(LookupsService.name);
private readonly GET_APP_TOKEN_URL =
"https://insapm.ir/bimeapimanager/api/EITAuthentication/GetAppToken";
private readonly LOGIN_URL =
"https://insapm.ir/bimeapimanager/api/EITAuthentication/Login";
private readonly BASE_API_URL =
"https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/car";
private readonly BASE_INFO_PATH = "base-info";
private readonly CODE_LIST_PATH = "code-list";
// Authentication credentials constructor(private readonly lookupDbService: LookupDbService) {}
private readonly APP_NAME = "fanhab";
private readonly SECRET = "5Fa@N#A2B";
private readonly USERNAME = "fanhabUser";
private readonly PASSWORD = "Fan#@2U$3er";
// API headers
private readonly CORP_ID = "3539";
private readonly CONTRACT_ID = "263";
private readonly LOCATION = "100";
constructor(
private readonly lookupDbService: LookupDbService,
) {}
/** /**
* Step 1: Get appToken from GetAppToken API * Returns stored `response` for a lookup document by `name` in the lookups collection.
* The token is returned in the response header 'appToken'
*/ */
private async getAppToken(): Promise<string> { async getLookup(lookupName: string): Promise<any> {
try { const doc = await this.lookupDbService.findOne({ name: lookupName });
const requestHeaders: any = { if (!doc) {
appname: this.APP_NAME, this.logger.warn(`Lookup not found in database: ${lookupName}`);
secret: this.SECRET, throw new NotFoundException(`Lookup "${lookupName}" not found`);
"Content-Length": "0",
};
// Remove Content-Type header when Content-Length is 0
delete requestHeaders["Content-Type"];
const response = await axios({
method: "POST",
url: this.GET_APP_TOKEN_URL,
data: "", // Empty string body
headers: requestHeaders,
transformRequest: [
(data, headers) => {
// Remove Content-Type header completely for empty body
if (headers) {
delete headers["Content-Type"];
delete headers["content-type"];
}
return data;
},
],
});
// Axios lowercases headers, so check both cases
const appToken =
response.headers.apptoken ||
response.headers.appToken ||
response.headers["apptoken"] ||
response.headers["appToken"];
if (!appToken) {
this.logger.error("Failed to get appToken from response headers");
this.logger.error("Response status:", response.status);
this.logger.error("Response headers:", JSON.stringify(response.headers, null, 2));
this.logger.error("Available header keys:", Object.keys(response.headers));
this.logger.error("Response data:", response.data);
// Extract error message from response if available
const errorMessage = response.data?.Message ||
response.data?.message ||
response.data ||
"Failed to get appToken from response headers";
throw new BadGatewayException(errorMessage);
}
this.logger.log(`Successfully obtained appToken: ${appToken}`);
return appToken;
} catch (error) {
this.logger.error("Failed to get appToken");
if (axios.isAxiosError(error)) {
this.logger.error("Error status:", error.response?.status);
this.logger.error("Error status text:", error.response?.statusText);
this.logger.error("Error response data:", error.response?.data);
this.logger.error("Error response headers:", error.response?.headers);
this.logger.error("Request URL:", error.config?.url);
this.logger.error("Request headers:", error.config?.headers);
// Extract error message from response
const errorMessage = error.response?.data?.Message ||
error.response?.data?.message ||
error.response?.data ||
error.message ||
"Failed to get appToken from external API";
throw new BadGatewayException(errorMessage);
} else {
this.logger.error("Error details:", error);
throw new BadGatewayException("Failed to get appToken from external API");
}
} }
return doc.response;
} }
/**
* Step 2: Login to get authenticationToken
* The token is returned in the response body field 'authenticationtoken'
*/
private async login(appToken: string): Promise<string> {
try {
this.logger.log(`Attempting login with appToken: ${appToken}`);
this.logger.log(`Login URL: ${this.LOGIN_URL}`);
this.logger.log(`Username: ${this.USERNAME}`);
const requestHeaders: any = {
appToken: appToken,
userName: this.USERNAME,
password: this.PASSWORD,
"Content-Length": "0",
};
// Remove Content-Type header when Content-Length is 0
// Axios adds it automatically, but server doesn't expect it for empty body
delete requestHeaders["Content-Type"];
this.logger.log("Request headers (without password):", {
appToken: appToken,
userName: this.USERNAME,
password: "***",
"Content-Length": "0",
"Content-Type": "removed",
});
const response = await axios({
method: "POST",
url: this.LOGIN_URL,
data: "", // Empty string body
headers: requestHeaders,
transformRequest: [
(data, headers) => {
// Remove Content-Type header completely for empty body
if (headers) {
delete headers["Content-Type"];
delete headers["content-type"];
}
return data;
},
],
});
this.logger.log("Login response status:", response.status);
this.logger.log("Login response data:", JSON.stringify(response.data, null, 2));
this.logger.log("Login response headers:", JSON.stringify(response.headers, null, 2));
// Check response headers first (authenticationToken is in headers, not body)
const authenticationToken =
response.headers.authenticationtoken ||
response.headers.authenticationToken ||
response.headers["authenticationtoken"] ||
response.headers["authenticationToken"] ||
// Fallback to body if not in headers
response.data?.authenticationtoken ||
response.data?.authenticationToken ||
response.data?.authentication_token;
this.logger.log(`Extracted authenticationToken: ${authenticationToken ? 'Found' : 'Not found'}`);
if (!authenticationToken) {
this.logger.error("Failed to get authenticationToken from response");
this.logger.error("Response status:", response.status);
this.logger.error("Response headers:", JSON.stringify(response.headers, null, 2));
this.logger.error("Response headers keys:", Object.keys(response.headers || {}));
this.logger.error("Response data:", JSON.stringify(response.data, null, 2));
this.logger.error("Response data keys:", Object.keys(response.data || {}));
// Extract error message from response if available
const errorMessage = response.data?.Message ||
response.data?.message ||
"Failed to get authenticationToken from response";
throw new BadGatewayException(errorMessage);
}
this.logger.log(`Successfully obtained authenticationToken: ${authenticationToken}`);
return authenticationToken;
} catch (error) {
this.logger.error("Failed to login");
this.logger.error("AppToken used:", appToken);
this.logger.error("Login URL:", this.LOGIN_URL);
this.logger.error("Username:", this.USERNAME);
if (axios.isAxiosError(error)) {
this.logger.error("Error status:", error.response?.status);
this.logger.error("Error status text:", error.response?.statusText);
this.logger.error("Error response data:", JSON.stringify(error.response?.data, null, 2));
this.logger.error("Error response headers:", JSON.stringify(error.response?.headers, null, 2));
this.logger.error("Request URL:", error.config?.url);
this.logger.error("Request method:", error.config?.method);
this.logger.error("Request headers:", JSON.stringify({
...error.config?.headers,
password: "***",
}, null, 2));
// Extract error message from response
const errorMessage = error.response?.data?.Message ||
error.response?.data?.message ||
error.response?.data ||
error.message ||
"Failed to login to external API";
throw new BadGatewayException(errorMessage);
} else {
this.logger.error("Error details:", error);
throw new BadGatewayException("Failed to login to external API");
}
}
}
/**
* Step 3: Call the lookup API with authenticationToken
*/
private async callLookupApi(
lookupName: string,
authenticationToken: string,
pathType: "base-info" | "code-list" = "base-info",
): Promise<any> {
const url = `${this.BASE_API_URL}/${pathType}/${lookupName}`;
try {
const response = await axios.get(url, {
headers: {
authenticationToken: authenticationToken,
CorpId: this.CORP_ID,
ContractId: this.CONTRACT_ID,
Location: this.LOCATION,
"Content-Type": "application/json",
},
});
this.logger.log(`Successfully fetched data for lookup: ${lookupName}`);
return response.data;
} catch (error) {
this.logger.error(`Failed to fetch lookup data for ${lookupName}`);
this.logger.error("URL:", url);
this.logger.error("AuthenticationToken used:", authenticationToken);
if (axios.isAxiosError(error)) {
this.logger.error("Error status:", error.response?.status);
this.logger.error("Error status text:", error.response?.statusText);
this.logger.error("Error response data:", JSON.stringify(error.response?.data, null, 2));
this.logger.error("Error response headers:", JSON.stringify(error.response?.headers, null, 2));
this.logger.error("Request URL:", error.config?.url);
this.logger.error("Request headers:", JSON.stringify(error.config?.headers, null, 2));
// Extract error message from response
const errorMessage = error.response?.data?.Message ||
error.response?.data?.message ||
error.response?.data ||
error.message ||
`Failed to fetch lookup data for ${lookupName}`;
throw new BadGatewayException(errorMessage);
} else {
this.logger.error("Error details:", error);
throw new BadGatewayException(
`Failed to fetch lookup data for ${lookupName}`,
);
}
}
}
/**
* Complete authentication flow and fetch lookup data
*/
private async fetchLookupDataFromExternalApi(
lookupName: string,
pathType: "base-info" | "code-list" = "base-info",
): Promise<{ authenticationToken: string; response: any; url: string }> {
// Step 1: Get appToken
const appToken = await this.getAppToken();
// Step 2: Login to get authenticationToken
const authenticationToken = await this.login(appToken);
// Step 3: Call the lookup API
const url = `${this.BASE_API_URL}/${pathType}/${lookupName}`;
const response = await this.callLookupApi(
lookupName,
authenticationToken,
pathType,
);
return { authenticationToken, response, url };
}
/**
* Check if lookup data needs to be updated (once a month)
*/
private shouldUpdateLookup(updatedAt: Date | null): boolean {
if (!updatedAt) {
return true; // Never updated, need to fetch
}
const oneMonthAgo = new Date();
oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);
return updatedAt < oneMonthAgo;
}
/**
* Get lookup data by name
* Returns cached data if available and not expired, otherwise fetches from external API
*/
async getLookup(
lookupName: string,
pathType: "base-info" | "code-list" = "base-info",
): Promise<any> {
// Check if we have cached data
const existingLookup = await this.lookupDbService.findOne({
name: lookupName,
});
if (existingLookup && !this.shouldUpdateLookup(existingLookup.updatedAt)) {
this.logger.log(
`Returning cached data for lookup: ${lookupName} (last updated: ${existingLookup.updatedAt})`,
);
return existingLookup.response;
}
// Need to fetch from external API
this.logger.log(`Fetching fresh data for lookup: ${lookupName}`);
const { authenticationToken, response, url } =
await this.fetchLookupDataFromExternalApi(lookupName, pathType);
// Save or update in database
await this.lookupDbService.findOneAndUpdate(
{ name: lookupName },
{
url: url,
authenticationToken: authenticationToken,
name: lookupName,
response: response,
updatedAt: new Date(),
},
);
this.logger.log(`Successfully saved/updated lookup data for: ${lookupName}`);
return response;
}
/**
* Get accident-causes lookup data
*/
async getAccidentCauses(): Promise<any> { async getAccidentCauses(): Promise<any> {
return await this.getLookup("accident-causes", "base-info"); return await this.getLookup("accident-causes");
} }
/**
* Get accident-report-type lookup data
*/
async getAccidentReportType(): Promise<any> { async getAccidentReportType(): Promise<any> {
return await this.getLookup("accident-report-type", "code-list"); return await this.getLookup("accident-report-type");
} }
/**
* Get vehicle-use-types lookup data
*/
async getVehicleUseTypes(): Promise<any> { async getVehicleUseTypes(): Promise<any> {
return await this.getLookup("vehicle-use-types", "base-info"); return await this.getLookup("vehicle-use-types");
} }
/**
* Get dmg-pay-method lookup data
*/
async getDmgPayMethod(): Promise<any> { async getDmgPayMethod(): Promise<any> {
return await this.getLookup("dmg-pay-method", "code-list"); return await this.getLookup("dmg-pay-method");
} }
/**
* Get driving-licence-types lookup data
*/
async getDrivingLicenceTypes(): Promise<any> { async getDrivingLicenceTypes(): Promise<any> {
return await this.getLookup("driving-licence-types", "base-info"); return await this.getLookup("driving-licence-types");
} }
/**
* Get accident-culprit-type lookup data
*/
async getAccidentCulpritType(): Promise<any> { async getAccidentCulpritType(): Promise<any> {
return await this.getLookup("accident-culprit-type", "code-list"); return await this.getLookup("accident-culprit-type");
} }
} }