import { DyFM_AnyError, DyFM_Array, DyFM_DataModel_Params, DyFM_Error, DyFM_Error_Settings, DyFM_global_settings, DyFM_Log, DyFM_Metadata } from '@futdevpro/fsm-dynamo'; import { DyNTS_App_Params } from '../../_models/control-models/app-params.control-model'; import { DyNTS_GlobalErrorHandlerFn, DyNTS_GlobalService_Settings } from '../../_models/interfaces/global-service-settings.interface'; import { DyNTS_AuthService } from './auth.service'; /* import { DyNTS_DBServiceCollection } from './db-service-collection.service'; */ import { DyNTS_DBService } from '../base/db.service'; import { DyNTS_EmailService } from './email.service'; import { DyNTS_Service_Collection } from './service-collection.service'; /* import { DyNTS_EmailServiceCollection } from './email-service-collection.service'; */ import { DyNTS_global_settings } from '../../_collections/global-settings.const'; import { DyNTS_SingletonService } from '../base/singleton.service'; /* import { DyNTS_archiveFlag } from '../../_collections/archive-flag.const'; */ import { DyNTS_getArchivedDBName } from '../../_collections/archive.util'; /** * This is the main Global/Core Service Collection used by DynamoNTS, * you also can use this to access main services as * dbServices, emailServices, or authService */ export class DyNTS_GlobalService extends DyNTS_SingletonService { static getInstance(): DyNTS_GlobalService { return DyNTS_GlobalService.getSingletonInstance(); } private static _params: DyNTS_App_Params; static get params(): DyNTS_App_Params { return this._params; } private static readonly _upSince: Date = new Date(); static get upSince(): Date { return this._upSince; } static get upTime(): number { return +new Date() - +this._upSince; } /** * FR-258 / SR-2 — enumerate every registered DB service (one per data-model, including the * auto-created `_archived` siblings). Used by the DyNTS_App startup TTL-installer to find the * models that declared `retention` and create their MongoDB TTL index. Returns an empty array * if services aren't set up yet (safe to call early). */ static getAllDBServices(): DyNTS_DBService[] { const collection: DyNTS_Service_Collection> | undefined = this.instance?.dbServiceCollection; return collection ? Object.values(collection) : []; } authService: DyNTS_AuthService; dbServiceCollection: DyNTS_Service_Collection>; /* dataServiceCollection: DyNTS_Service_Collection; */ emailServiceCollection: DyNTS_Service_Collection; static readonly defaultErrorUserMsg: string = `We encountered an unhandled Control Service Error, ` + `\nplease contact the responsible development team.`; static globalErrorHandler: DyNTS_GlobalErrorHandlerFn = async (error: DyFM_AnyError): Promise => { DyFM_Log.error(`Global Error Handler not set!\nError:`, error); } /** * You need to setup global Services through this function */ static async setServices(set: DyNTS_GlobalService_Settings): Promise { this.getInstance(); try { await this.setDBServices(set?.dbModels); await this.setAuthService(set?.authService); await this.setEmailServices(set?.emailServiceCollection); await this.setErrorHandler(set?.errorHandler); } catch (error) { throw new DyFM_Error({ ...this.getDefaultErrorSettings('setServices', error, 'DyNTS_GlobalService'), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-GS0-SS0`, }); } } private static async setDBServices(dbModels?: DyFM_DataModel_Params[]): Promise { try { this.instance.dbServiceCollection = {}; dbModels?.forEach((dbModel: DyFM_DataModel_Params): void => { if (!dbModel) { throw new DyFM_Error({ ...this.getDefaultErrorSettings( 'setDBServices', new Error(`given dbModel is empty!`), 'DyNTS_GlobalService' ), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-GS0-SDBS2`, additionalContent: { dbModels: dbModels, dbModelNames: dbModels.map((dbModel: DyFM_DataModel_Params) => dbModel.dataName), }, }); } if (dbModel.addArchive) { const archiveModel: DyFM_DataModel_Params = new DyFM_DataModel_Params({ ...dbModel, dataName: DyNTS_getArchivedDBName(dbModel.dataName), properties: { ...dbModel.properties, }, }); dbModels.push(archiveModel); } }); dbModels?.forEach((dbModel: DyFM_DataModel_Params): void => { this.createDBService(dbModel); }); if (DyFM_global_settings.log_settings.dbSetup) { DyFM_Log.info( `DB Services setup complete. DB Models:`, `\n ${Object.keys(this.instance.dbServiceCollection).join('\n ')}` ); } } catch (error) { throw new DyFM_Error({ ...this.getDefaultErrorSettings('setDBServices', error, 'DyNTS_GlobalService'), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-GS0-SDBS0`, }); } } static createDBService(dbModel: DyFM_DataModel_Params): void { try { if (!dbModel.constructed) { throw new DyFM_Error({ ...this.getDefaultErrorSettings( 'setDBServices', new Error( `"${dbModel.dataName}" dbModel is not constructed!` + `\n use new DyFM_DataModel_Params() to properly construct it.` ), 'DyNTS_GlobalService' ), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-GS0-CDBS1`, additionalContent: { dbModel: dbModel, }, }); } if (this.instance.dbServiceCollection[dbModel.dataName]) { return; } try { this.instance.dbServiceCollection[dbModel.dataName] = new DyNTS_DBService(dbModel); } catch (error) { if (error instanceof DyFM_Error && !DyFM_global_settings.log_settings.highDetailedLogs) { error.logSimple(`Failed to create DyNTS_DBService (${dbModel.dataName}).`); } else { DyFM_Log.error(`Failed to create DyNTS_DBService (${dbModel.dataName}).`, error); } throw new DyFM_Error({ ...this.getDefaultErrorSettings('setDBServices', error, 'DyNTS_GlobalService'), additionalContent: { dbModel: dbModel, }, }); } } catch (error) { throw new DyFM_Error({ ...this.getDefaultErrorSettings('createDBService', error, 'DyNTS_GlobalService'), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-GS0-SDBS0`, }); } } static unmountDBService(dbModel: DyFM_DataModel_Params): void { try { if (!this.instance.dbServiceCollection[dbModel.dataName]) { return; } delete this.instance.dbServiceCollection[dbModel.dataName]; } catch (error) { throw new DyFM_Error({ ...this.getDefaultErrorSettings('unmountDBService', error, 'DyNTS_GlobalService'), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-GS0-UDBS0`, }); } } private static async setAuthService(authService?: DyNTS_AuthService): Promise { try { if (!authService) { // Opcionális auth-service esetén ez VÁRT (public / no-auth szerver) — nincs warn. if (!DyNTS_global_settings.auth_settings?.optional) { DyFM_Log.warn(`Authentication Service missing!`); } } else { this.instance.authService = authService; } } catch (error) { throw new DyFM_Error({ ...this.getDefaultErrorSettings('setAuthService', error, 'DyNTS_GlobalService'), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-GS0-SAS0`, }); } } private static async setEmailServices( emailServiceCollection?: DyNTS_Service_Collection ): Promise { try { this.instance.emailServiceCollection = emailServiceCollection ?? {}; await DyFM_Array.asyncForEach( Object.keys(this.instance.emailServiceCollection), async (key: string): Promise => { await ( this.instance.emailServiceCollection[key] as DyNTS_EmailService ).asyncPostConstruct(); } ); } catch (error) { throw new DyFM_Error({ ...this.getDefaultErrorSettings('setEmailServices', error, 'DyNTS_GlobalService'), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-GS0-SS4`, }); } } private static async setErrorHandler(errorHandler?: DyNTS_GlobalErrorHandlerFn): Promise { try { DyNTS_GlobalService.globalErrorHandler = (async (error: any): Promise => { try { if (errorHandler) { await errorHandler(error); return; } DyFM_Log.warn( `globalErrorHandler not set! (set in app.getGlobalServiceCollection)`, '\nwill use default-logging', '\n(If you set the globalErrorHandler, ' + 'please check if it is using the same node_modules as the app)' ); DyFM_Error.logSimple(`Error caught by globalErrorHandler.`, error); } catch (error) { if (error instanceof DyFM_Error) { error.logSimple( `NTS GLOBAL MULTILEVEL ERROR: ` + `\nFailed to handle error on globalErrorHandler.`, ); } else { DyFM_Log.error( `NTS GLOBAL MULTILEVEL ERROR: ` + `\nFailed to handle error on globalErrorHandler.`, (error as DyFM_Error)?.getErrorSimplified?.() ?? error ); } } }); } catch (error) { throw new DyFM_Error({ ...this.getDefaultErrorSettings('setErrorHandler', error, 'DyNTS_GlobalService'), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-GS0-SEH0`, }); } } static setParams(params: DyNTS_App_Params): void { this._params = params; } /** * * @returns */ static getAuthService(): DyNTS_AuthService { if (!this.instance?.authService) { // Ha az app explicit opcionálisnak jelölte az auth-service-t // (DyNTS_global_settings.auth_settings.optional), NEM dobunk: undefined-ot // adunk vissza. A hívók (pl. a request-path issuer-kinyerés) optional- // chaining-gel kezelik. Public / no-auth szervereknek — így a // "Unique Authentication Service missing!" hiba suppress-elhető beállításból. if (DyNTS_global_settings.auth_settings?.optional) { return undefined; } throw new Error( `\n Unique Authentication Service missing!` + `\n Please create a Unique Authentication Service extending DyNTS_AuthService, ` + `\n and Setup with DyNTS_GlobalServiceC.setServices(...)` + '\n (If you set the globalErrorHandler, ' + 'please check if it is using the same node_modules as the app)' + '\n (To run WITHOUT an auth service, set ' + 'DyNTS_global_settings.auth_settings.optional = true)', ); } return this.instance.authService; } /** * * @returns */ static getDBServiceCollection(): DyNTS_Service_Collection> { if (!(this.instance as DyNTS_GlobalService)?.dbServiceCollection) { throw new Error( `\n Unique DataBase Service Collection missing!` + `\n Please create a Unique DataBase Service Collection extending ` + `DyNTS_DBServiceCollection, ` + `\n and Setup with DyNTS_GlobalServiceC.setServices(...)` + '\n (If you set the globalErrorHandler, ' + 'please check if it is using the same node_modules as the app)\n', ); } return this.instance.dbServiceCollection; } /** * * @param dataParams * @returns */ static getDBService( dataParams: DyFM_DataModel_Params ): DyNTS_DBService { return this.getDBServiceByKey(dataParams.dataName); } /** * * @param key * @returns */ static getDBServiceByKey(key: string): DyNTS_DBService { const collection = this.getDBServiceCollection(); if (collection[key]) { return collection[key] as DyNTS_DBService; } else { throw new Error( `\n DyNTS_GlobalService.getDBService failed, ERROR: ` + `\n no dbService on collection with this key: "${key}"!` + '\n (If you set the globalErrorHandler, ' + 'please check if it is using the same node_modules as the app)' + `\n The available dbServices on collection:` + `\n ${Object.keys(collection).join(', ')}\n` ); } } /** * * @returns */ static getEmailServiceCollection(): DyNTS_Service_Collection { if (!this.instance.emailServiceCollection) { throw new Error( `\n Unique Email Service Collection missing!` + `\n Please create a Unique Email Service Collection extending ` + `DyNTS_EmailServiceCollection, ` + '\n (If you set the globalErrorHandler, ' + 'please check if it is using the same node_modules as the app)' + `\n and Setup with DyNTS_GlobalServiceC.setServices(...)\n` + `\n available emailServiceCollection: ` + `${Object().keys(this.instance.emailServiceCollection).join(', ')}\n` ); } return this.instance.emailServiceCollection; } /** * * @returns */ /* static getSocketServiceCollection(): DyNTS_SocketServiceCollection { if (!this.instance?.socketServiceCollection) { throw new Error( `\nUnique Socket Service Collection missing!` + `\nSocket Service Collection will only be created if there are Socket Services properly setted up ` + `in the setupModules in the App using DyNTS_AppExtended, ` + `and Setup with "DyNTS_GlobalServiceC.setServices(...)"`); } return this.instance.socketServiceCollection; } */ /** * * @param name * @returns */ /* static getSocketServiceByName(name: string): DyNTS_SocketService { const collection: DyNTS_SocketServiceCollection = this.getSocketServiceCollection(); if (collection[name]) { return collection[name]; } else { let names: string = `${Object.keys(collection)}`; names = names.replace(/,/g, '\n '); throw new Error( `\nDyNTS_GlobalService.getSocketServiceByName failed, ERROR: no socketService on collection with this name: ${name}! The available socketServices on collection: ${names}\n`); } } static attachNewSocketService(service: DyNTS_SocketService): void { const collection: DyNTS_SocketServiceCollection = this.getSocketServiceCollection(); if (!collection[service.name]) { collection[service.name] = service; } else { let names: string = `${Object.keys(collection)}`; names = names.replace(/,/g, '\n '); throw new Error( `\nDyNTS_GlobalService.attachNewSocketService failed, ERROR: socketService name is already taken on collection with this name: ${service.name}! The available socketServices on collection: ${names}\n`); } } */ protected static getDefaultErrorSettings( fnName: string, error: DyFM_AnyError, issuer: string, /** @deprecated we wont support the separate user message in the future, use the message instead */ useMessageAsUserMessage: boolean = true, ): DyFM_Error_Settings { return { status: (error as DyFM_Error)?.___status ?? 500, message: (error as Error)?.message ?? (error as DyFM_Error)?._message ?? `${fnName} was UNSUCCESSFUL (${DyNTS_global_settings.systemShortCodeName})`, addECToUserMsg: !(error as DyFM_Error)?.__userMessage, userMessage: (error as DyFM_Error)?.__userMessage ?? (useMessageAsUserMessage ? (error as Error)?.message : this.defaultErrorUserMsg), issuer: issuer, issuerService: this.constructor?.name, error: error, }; } }