import "dotenv/config"; import { configureLogger } from "../reporting-service/logger.js"; import { SQLServerConfiguration } from "../sql-server-erp-service/configuration.js"; import { getEnvBoolean, getEnvNumber, getEnvString } from "../../utils/env.js"; /** * Resolves MM REST API base URL for {@link CoreConfiguration} only — not exported from the SDK. * Requires non-empty `MM_MAPPING_AUTH_SERVICE_URL` (trimmed, no trailing slash). */ function resolveMmApiBaseUrl(): string { const fromEnv = getEnvString("MM_MAPPING_AUTH_SERVICE_URL", ""); if (fromEnv.length === 0) { throw new Error( "MM_MAPPING_AUTH_SERVICE_URL is required and must be set to the MM REST API base URL (e.g. https://api.machinemetrics.com)" ); } return fromEnv.replace(/\/$/, ""); } export class CoreConfiguration { private static instance: CoreConfiguration; // General Configuration public readonly logLevel: string; public readonly nodeEnv: string; // MM API (aka "Mapping") Service public readonly mmERPSvcApiBaseUrl: string; public readonly mmApiBaseUrl: string; public readonly mmApiAuthToken: string; public readonly mmApiRetryAttempts: number; public readonly mmApiTimeoutSeconds: number; public readonly mmApiWriteChunkSize: number; // Caching (optionally used for interacting with the MM API) public readonly cacheTTL: number; // ERP API Service public readonly erpApiPagingLimit: number; //Pagination limit for ERP API public readonly erpApiTimeoutSeconds: number; public readonly erpApiRetryAttempts: number; // Job timing Intervals public readonly fromErpInterval: string; public readonly toErpInterval: string; public readonly retryLaborTicketsInterval: string; public readonly cacheExpirationCheckInterval: string; public readonly reconcileWorkOrdersInterval: string; private constructor() { this.logLevel = getEnvString("LOG_LEVEL", "info"); this.nodeEnv = getEnvString("NODE_ENV", "development"); //#region MM API (aka "Mapping") Service /** * MM ERP Service REST API URL (typically https://erp-api.svc.machinemetrics.com) */ this.mmERPSvcApiBaseUrl = getEnvString("MM_MAPPING_SERVICE_URL", ""); /** * MM REST API URL (typically https://api.machinemetrics.com). * Set via required `MM_MAPPING_AUTH_SERVICE_URL` — see {@link resolveMmApiBaseUrl}. */ this.mmApiBaseUrl = resolveMmApiBaseUrl(); /** * Company Auth Token */ this.mmApiAuthToken = getEnvString("MM_MAPPING_SERVICE_TOKEN", ""); /** * Number of retry attempts for MM API calls */ // Prefer the MM-specific alias for consistent naming. Fall back to legacy `RETRY_ATTEMPTS`. this.mmApiRetryAttempts = getEnvNumber( "MM_API_RETRY_ATTEMPTS", getEnvNumber("RETRY_ATTEMPTS", 0) ); /** * Timeout for MM API calls (seconds) * * Note: axios expects milliseconds; we convert at call sites. */ this.mmApiTimeoutSeconds = getEnvNumber("MM_API_TIMEOUT_SEC", 30); /** * Maximum number of records per MM API write request. When set (> 0), * larger record sets are split into sequential requests of this size so * each call stays within the MM API timeout. * * Disabled by default (0): many connectors chunk at their own layer, and * SDK-side sub-chunking would alter the error semantics they rely on * (aggregated error messages; a structured 500 surfacing as 207 when an * earlier chunk succeeded). Opt in per deployment via this env var, or * per call via the `chunkSize` option on writeEntitiesToMM. */ this.mmApiWriteChunkSize = getEnvNumber("MM_API_WRITE_CHUNK_SIZE", 0); //#endregion MM API (aka "Mapping") Service /** * Default pagination limit for ERP API */ this.erpApiPagingLimit = getEnvNumber("ERP_PAGINATION_LIMIT", 0); /** * Timeout for ERP API calls (seconds) */ this.erpApiTimeoutSeconds = getEnvNumber("ERP_API_TIMEOUT_SEC", 30); /** * Default retry attempts for ERP API calls. */ // Keep default consistent with `getErpApiConnectionParams()` when env is unset. this.erpApiRetryAttempts = getEnvNumber("ERP_API_RETRY_ATTEMPTS", 3); //#endregion ERP API Service /** * For how to define the intervals, see Bree's documentation: https://github.com/breejs/bree */ const pollInterval = getEnvString("POLL_INTERVAL", ""); this.fromErpInterval = getEnvString( "FROM_ERP_INTERVAL", pollInterval || "5 min" ); this.toErpInterval = getEnvString("TO_ERP_INTERVAL", "5 min"); this.retryLaborTicketsInterval = getEnvString( "RETRY_LABOR_TICKETS_INTERVAL", "30 min" ); this.cacheExpirationCheckInterval = getEnvString( "CACHE_EXPIRATION_CHECK_INTERVAL", "60 min" ); // The job runs for every connector; it no-ops unless the connector implements reconcileWorkOrders(). this.reconcileWorkOrdersInterval = getEnvString( "RECONCILE_WORK_ORDERS_INTERVAL", "24 hours" ); /** * Cache TTL (in seconds) */ const cacheTTLDef = 7 * 24 * 60 * 60; // 7 days this.cacheTTL = getEnvNumber("CACHE_TTL", cacheTTLDef); // Configure the logger with our settings configureLogger(this.logLevel, this.nodeEnv); } public static inst(): CoreConfiguration { if (!CoreConfiguration.instance) { CoreConfiguration.instance = new CoreConfiguration(); } return CoreConfiguration.instance; } /** * Returns a sanitized version of the configuration for safe logging. * Masks sensitive fields like authentication tokens. */ public toSafeLogObject(): Record { const maskSensitiveValue = (value: string): string => { if (!value || value.length === 0) { return ""; } if (value.length < 6) { return "********"; } // Show first 3 and last 3 characters, mask the middle return value.substring(0, 3) + "********" + value.substring(value.length - 3); }; return { logLevel: this.logLevel, nodeEnv: this.nodeEnv, mmERPSvcApiBaseUrl: this.mmERPSvcApiBaseUrl, mmApiBaseUrl: this.mmApiBaseUrl, mmApiAuthToken: maskSensitiveValue(this.mmApiAuthToken), mmApiRetryAttempts: this.mmApiRetryAttempts, mmApiTimeoutSeconds: this.mmApiTimeoutSeconds, mmApiWriteChunkSize: this.mmApiWriteChunkSize, cacheTTL: this.cacheTTL, erpApiPagingLimit: this.erpApiPagingLimit, erpApiTimeoutSeconds: this.erpApiTimeoutSeconds, erpApiRetryAttempts: this.erpApiRetryAttempts, fromErpInterval: this.fromErpInterval, toErpInterval: this.toErpInterval, retryLaborTicketsInterval: this.retryLaborTicketsInterval, cacheExpirationCheckInterval: this.cacheExpirationCheckInterval, reconcileWorkOrdersInterval: this.reconcileWorkOrdersInterval, }; } } /** * Helper function to get the SQL Server Configuration for collectors that use SQL Server to interact with the ERP */ export const getSQLServerConfiguration = (): SQLServerConfiguration => { return { username: getEnvString("ERP_SQLSERVER_USERNAME", ""), password: getEnvString("ERP_SQLSERVER_PASSWORD", ""), database: getEnvString("ERP_SQLSERVER_DATABASE", ""), host: getEnvString( "ERP_SQLSERVER_HOST", getEnvString("ERP_SQLSERVER_SERVER", "") ), port: getEnvString("ERP_SQLSERVER_PORT", "1433"), connectionTimeout: getEnvString("ERP_SQLSERVER_CONNECTION_TIMEOUT", "30000"), requestTimeout: getEnvString("ERP_SQLSERVER_REQUEST_TIMEOUT", "60000"), poolMax: getEnvString("ERP_SQLSERVER_MAX", "10"), poolMin: getEnvString("ERP_SQLSERVER_MIN", "0"), idleTimeoutMillis: getEnvString( "ERP_SQLSERVER_IDLE_TIMEOUT_MMILLIS", "30000" ), encrypt: getEnvBoolean("ERP_SQLSERVER_ENCRYPT", false), trustServer: getEnvBoolean("ERP_SQLSERVER_TRUST_SERVER", false), }; }; /** * Parameters required to connect to an ERP system via its API. * Contains all the necessary settings to establish a connection and authenticate with an ERP system's API. */ export class ErpApiConnectionParams { constructor( public readonly erpApiUrl: string, // Base url of ERP public readonly erpApiClientId: string, // Client ID to authenticate with ERP public readonly erpApiClientSecret: string, // Client Secret to authenticate with ERP public readonly erpApiOrganizationId: string, // Organization / tenant Id public readonly erpAuthBaseUrl: string, // Auth base url public readonly retryAttempts: number = 3 // Number of retry attempts for API calls ) {} } /** * Helper function to get the ERP API Connection Parameters * Not all connectors use these, but keeping these commonly values in one place may * make it easier to set and understand env var names set in App. */ export const getErpApiConnectionParams = (): ErpApiConnectionParams => { return new ErpApiConnectionParams( getEnvString("ERP_API_URL", ""), getEnvString("ERP_API_CLIENT_ID", ""), getEnvString("ERP_API_CLIENT_SECRET", ""), getEnvString("ERP_API_ORGANIZATION_ID", ""), getEnvString("ERP_AUTH_BASE_URL", ""), getEnvNumber("ERP_API_RETRY_ATTEMPTS", 3) ); };