forked from Yara724/api
Initial commit after migration to gitea
This commit is contained in:
401
src/lookups/lookups.service.ts
Normal file
401
src/lookups/lookups.service.ts
Normal file
@@ -0,0 +1,401 @@
|
||||
import {
|
||||
BadGatewayException,
|
||||
Injectable,
|
||||
Logger,
|
||||
} from "@nestjs/common";
|
||||
import axios from "axios";
|
||||
import { LookupDbService } from "./entities/db-service/lookup.db.service";
|
||||
|
||||
@Injectable()
|
||||
export class LookupsService {
|
||||
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
|
||||
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
|
||||
* The token is returned in the response header 'appToken'
|
||||
*/
|
||||
private async getAppToken(): Promise<string> {
|
||||
try {
|
||||
const requestHeaders: any = {
|
||||
appname: this.APP_NAME,
|
||||
secret: this.SECRET,
|
||||
"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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
return await this.getLookup("accident-causes", "base-info");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get accident-report-type lookup data
|
||||
*/
|
||||
async getAccidentReportType(): Promise<any> {
|
||||
return await this.getLookup("accident-report-type", "code-list");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get vehicle-use-types lookup data
|
||||
*/
|
||||
async getVehicleUseTypes(): Promise<any> {
|
||||
return await this.getLookup("vehicle-use-types", "base-info");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dmg-pay-method lookup data
|
||||
*/
|
||||
async getDmgPayMethod(): Promise<any> {
|
||||
return await this.getLookup("dmg-pay-method", "code-list");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get driving-licence-types lookup data
|
||||
*/
|
||||
async getDrivingLicenceTypes(): Promise<any> {
|
||||
return await this.getLookup("driving-licence-types", "base-info");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get accident-culprit-type lookup data
|
||||
*/
|
||||
async getAccidentCulpritType(): Promise<any> {
|
||||
return await this.getLookup("accident-culprit-type", "code-list");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user