diff --git a/src/lookups/lookups.service.ts b/src/lookups/lookups.service.ts index cadfea2..a080066 100644 --- a/src/lookups/lookups.service.ts +++ b/src/lookups/lookups.service.ts @@ -1,401 +1,45 @@ -import { - BadGatewayException, - Injectable, - Logger, -} from "@nestjs/common"; -import axios from "axios"; +import { Injectable, Logger, NotFoundException } from "@nestjs/common"; 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, - ) {} + constructor(private readonly lookupDbService: LookupDbService) {} /** - * Step 1: Get appToken from GetAppToken API - * The token is returned in the response header 'appToken' + * Returns stored `response` for a lookup document by `name` in the lookups collection. */ - private async getAppToken(): Promise { - 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"); - } + async getLookup(lookupName: string): Promise { + const doc = await this.lookupDbService.findOne({ name: lookupName }); + if (!doc) { + this.logger.warn(`Lookup not found in database: ${lookupName}`); + throw new NotFoundException(`Lookup "${lookupName}" not found`); } + 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 { - 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 { - 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 { - // 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 { - return await this.getLookup("accident-causes", "base-info"); + return await this.getLookup("accident-causes"); } - /** - * Get accident-report-type lookup data - */ async getAccidentReportType(): Promise { - 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 { - 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 { - 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 { - 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 { - return await this.getLookup("accident-culprit-type", "code-list"); + return await this.getLookup("accident-culprit-type"); } } -