import { Request, Response } from 'express'; import { DyFM_Error, DyFM_Errors, DyFM_HttpCallType, DyFM_RelativeDate, DyFM_ServerStatus } from '@futdevpro/fsm-dynamo'; import { DyNTS_Errors_ControlService } from '../errors/errors.control-service'; import { DyNTS_Controller } from '../../../_services/route/controller.service'; import { DyNTS_Endpoint_Params } from '../../../_models/control-models/endpoint-params.control-model'; import { DyNTS_global_settings } from '../../../_collections/global-settings.const'; import { DyNTS_DbReadiness, DyNTS_ServerStatus_ControlService } from './server-status.control-service'; import { DyNTS_ServerStatusSnapshot_ControlService } from './server-status-snapshot.control-service'; import { DyNTS_EventLoopDiag } from '../../../_services/core/event-loop-diag.service'; import { DyNTS_MemoryGuard } from '../../../_services/core/memory-guard.service'; /** * Auth retrofit config a `DyNTS_ServerStatus_Controller`-hez. Opt-in — ha az * `authPreProcess` nincs megadva, a controller a regi (auth nelkuli) viselkedest * tartja. Ugyanaz a mintazat, mint a `DyNTS_Errors_Controller`-ben, DE biztonsagosabb * default-tal: a liveness/readiness/client-version **probak SOHA nem gate-eltek** * (orchestrator/LB auth nelkul hivja oket), es a default `protectedEndpoints` csak az * admin-adat vegpontokat fedi (NEM a probakat). * * **Subclass-szintu konfiguracio:** az `authConfig` static, igy az abstract osztaly * minden subclass-a kozott OSZTOTT (TypeScript a static field-eket per-class hatarozza meg). */ export interface DyNTS_ServerStatusController_AuthConfig { /** * Pre-process fuggveny ami minden vedett endpoint elott fut. Ha hianyzik vagy * `undefined`, a controller NEM ad hozza auth-ot semelyik endpoint-hoz (regi * viselkedes, opt-in safety). */ authPreProcess?: (req: Request, res: Response) => Promise; /** * Vedett endpoint nevek listaja. Ha hianyzik, a default = csak az admin-adat * vegpontok (`getServerStatus`, `getErrorStatistics`) — a probak NEM. A * `PUBLIC_PROBE_ENDPOINTS` (`getServerHealth`/`getServerReadiness`/ * `getServerStatusForClient`) MEG explicit listazva sem gate-elheto. */ protectedEndpoints?: string[]; } /** * Public probak — orchestrator/LB ÉS a kliens server-status-indikator hivja auth * nelkul, ezert SOHA nem gate-eljuk (meg ha a host explicit listazza is a * `protectedEndpoints`-ban). * * FONTOS: `getServerStatus` (/status) IS itt van — a base controller-ben mindharom * (`getServerStatus`, `getServerHealth`, `getServerStatusForClient`) UGYANAZT hivja: * `server_CS.getServerStatus(issuer)`. Mivel a /health es /status/:version amugy is * publikus, a /status gate-elese 0 biztonsagi hasznot adna, viszont eltori a kliens * `DyNX` server-status-pollot (401 minden oldalbetolteskor). Csak a `getErrorStatistics` * valodi admin-adat. */ const PUBLIC_PROBE_ENDPOINTS: string[] = [ 'getServerStatus', 'getServerHealth', 'getServerReadiness', 'getServerStatusForClient', ]; /** * Default vedett vegpontok (ha a host nem ad `protectedEndpoints`-t): a valodi admin-adat * vegpontok. A status-probak publikusak (lasd `PUBLIC_PROBE_ENDPOINTS` fent). * * `getServerDiag` (2026-07-22): a `/diag` **EGYIK listaban sem szerepelt** — sem a publikus * probak kozott, sem itt —, ezert **minden FDP-szolgaltatason NYITOTT volt**: elesben 200-at adott * event-loop lag-history-val, aktiv handle-szammal, memoria-nyomassal es uptime-mal. Ez nem szandek * volt, hanem kimaradas: az endpoint a BFR-OVERSEER-007-tel keszult, es a ket lista egyiket sem * frissitettek hozza. A sajat doc-blockja "belso jel"-nek nevezi (amit a kivulrol-nezo health NEM lat), * es `lazyPreProcessesFor`-ral kotott — vagyis SZANDEKOSAN gate-elheto, ellentetben a hard-publikus * probakkal. Ezert a biztonsagos default: VEDETT. * * Kockazat: az operativ telemetria (mikor van a szolgaltatas nyomas alatt) tamado szamara * idozites-informacio. Merve: a flottaban SEMMI nem fogyasztja (`fdp` CLI, webhook, compose-healthcheck, * e2e-suite-ok — mind ures), tehat a gate-eles nem tor el semmit. * * ⚠️ A host `protectedEndpoints`-ja FELULIRJA ezt a listat — az explicit listat hasznalo appokban * kulon fel kell venni a nevet (l. a projekt-szintu kovetes). */ const DEFAULT_PROTECTED_ENDPOINTS: string[] = [ 'getErrorStatistics', 'getServerDiag', ]; /** * Endpoints: * * getServerStatus * GET /status * * getServerStatusForClient * GET /status/:version * * getErrorStatistics * GET /statistics/error/:range * */ export abstract class DyNTS_ServerStatus_Controller< T_ServerStatus extends DyFM_ServerStatus, T_Error extends DyFM_Error, T_Errors extends DyFM_Errors, T_Errors_ControlService extends DyNTS_Errors_ControlService, T_ServerStatusSnapshot extends DyFM_ServerStatus, T_ServerStatusSnapshot_ControlService extends DyNTS_ServerStatusSnapshot_ControlService, T_ServerStatus_ControlService extends DyNTS_ServerStatus_ControlService< T_ServerStatus, T_Error, T_Errors, T_Errors_ControlService, T_ServerStatusSnapshot, T_ServerStatusSnapshot_ControlService > > extends DyNTS_Controller { /* static getInstance(): DyNTS_ServerStatus_Controller { return DyNTS_ServerStatus_Controller.getSingletonInstance(); } */ protected abstract readonly server_CS: T_ServerStatus_ControlService; /* protected abstract getServerService(): T_ServerStatusService; */ protected readonly additionalEndpoints: DyNTS_Endpoint_Params[] = []; /** * Static auth config — opt-in retrofit. Default ures objektum → NO auth * (a meglevo integraciok valtoznatlan viselkedessel folytatodnak). */ protected static authConfig: DyNTS_ServerStatusController_AuthConfig = {}; /** * Static config setter. Hivhato a szerver startup-jan az endpoint registration ELOTT. * * Use case (host app): * ```ts * ServerStatus_Controller.configure({ * authPreProcess: async (req, res) => auth.authenticate_tokenAndPermission(req, res, FDP_Permission.X), * }); * ``` * A probak (`getServerHealth`/`getServerReadiness`/`getServerStatusForClient`) ettol * fuggetlenul NYITVA maradnak. */ static configure(config: DyNTS_ServerStatusController_AuthConfig): void { DyNTS_ServerStatus_Controller.authConfig = config; } /** * Aktualis auth config olvasasa (test/diagnosztika celokra). */ static getAuthConfig(): DyNTS_ServerStatusController_AuthConfig { return DyNTS_ServerStatus_Controller.authConfig; } /** * Test-only: visszaallitja az auth config-ot ures objektumra. Production code NE hivja. */ static _resetAuthConfigForTesting(): void { DyNTS_ServerStatus_Controller.authConfig = {}; } /** * Vissza-adja az adott endpoint-hoz tartozo `preProcesses` array-t. * - Ha az auth NINCS configurolva → ures (regi viselkedes). * - A public probak SOHA nem gate-eltek. * - Egyebkent az endpoint a `protectedEndpoints` (default = admin-only) listan van-e. */ protected getPreProcessesFor(endpointName: string): ((req: Request, res: Response) => Promise)[] { const cfg: DyNTS_ServerStatusController_AuthConfig = DyNTS_ServerStatus_Controller.authConfig; if (!cfg.authPreProcess) { return []; } if (PUBLIC_PROBE_ENDPOINTS.includes(endpointName)) { return []; } const protectedNames: string[] = cfg.protectedEndpoints ?? DEFAULT_PROTECTED_ENDPOINTS; return protectedNames.includes(endpointName) ? [cfg.authPreProcess] : []; } /** * FR-263 (2026-06-26) — LAZY, REQUEST-IDŐBEN kiértékelt guard-wrapper (lásd a DyNTS_Errors_Controller * azonos fixét). A `setupEndpoints()` a host-app `configure()`-ja ELŐTT futhat (korai singleton- * konstrukció) → a guard ürès configgal kötődött → a `getServerStatus`/`getErrorStatistics` admin-adat * token nélkül 200-at adott (info-disclosure). A wrapper a configot KÉRÉS-IDŐBEN olvassa → sorrend- * független. A 401-et a valódi guard küldi; `headersSent`-re leállunk; nem-védett endpointnál no-op. */ protected lazyPreProcessesFor(endpointName: string): ((req: Request, res: Response) => Promise)[] { return [ async (req: Request, res: Response): Promise => { const guards: ((req: Request, res: Response) => Promise)[] = this.getPreProcessesFor(endpointName); for (const guard of guards) { if (res.headersSent) { return; } await guard(req, res); } }, ]; } setupEndpoints(): void { /* if (!this.getServerService) { throw new DyFM_Error({ message: 'getServerService() method not implemented in ServerStatus_Controller.', errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSC-001`, }); } */ if (!this.additionalEndpoints) { throw new DyFM_Error({ message: 'additionalEndpoints must be defined in ServerStatus_Controller.', errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSC-002`, }); } /* this.serverService = this.getServerService(); */ this.endpoints = [ new DyNTS_Endpoint_Params({ name: 'getServerStatus', type: DyFM_HttpCallType.get, endpoint: '/status', preProcesses: this.lazyPreProcessesFor('getServerStatus'), tasks: [ async (req: Request, res: Response, issuer: string): Promise => { res.send( await this.server_CS.getServerStatus(issuer) ); }, ], }), new DyNTS_Endpoint_Params({ name: 'getServerHealth', type: DyFM_HttpCallType.get, endpoint: '/health', preProcesses: this.lazyPreProcessesFor('getServerHealth'), tasks: [ async (req: Request, res: Response, issuer: string): Promise => { res.send( await this.server_CS.getServerStatus(issuer) ); }, ], }), // Diag-endpoint (BFR-OVERSEER-007): event-loop-lag + aktív-handle + memória-nyomás EGY helyen — // a „process él, HTTP néma" wedge triage-ához (belső jel, amit a kívülről-néző health nem lát). new DyNTS_Endpoint_Params({ name: 'getServerDiag', type: DyFM_HttpCallType.get, endpoint: '/diag', preProcesses: this.lazyPreProcessesFor('getServerDiag'), tasks: [ async (req: Request, res: Response): Promise => { res.send({ eventLoop: DyNTS_EventLoopDiag.getInstance().getSnapshot(), memory: DyNTS_MemoryGuard.getInstance().pressure(), uptimeSec: Math.round(process.uptime()), }); }, ], }), // Readiness-probe: a /health-tel (liveness) szemben a DB-elérhetőséget is ellenőrzi. // Public (mint a /health) — orchestrátor/LB hívja; 503 ha a DB nem ready → no traffic. new DyNTS_Endpoint_Params({ name: 'getServerReadiness', type: DyFM_HttpCallType.get, endpoint: '/readiness', preProcesses: this.lazyPreProcessesFor('getServerReadiness'), tasks: [ async (req: Request, res: Response, issuer: string): Promise => { const readiness: DyNTS_DbReadiness = await this.server_CS.checkDbReadiness(); res.status(readiness.ready ? 200 : 503).send(readiness); }, ], }), new DyNTS_Endpoint_Params({ name: 'getServerStatusForClient', type: DyFM_HttpCallType.get, endpoint: '/status/:version', preProcesses: this.lazyPreProcessesFor('getServerStatusForClient'), tasks: [ async (req: Request, res: Response, issuer: string): Promise => { res.send( await this.server_CS.getServerStatus(issuer, req.params.version) ); }, ], }), new DyNTS_Endpoint_Params({ name: 'getErrorStatistics', type: DyFM_HttpCallType.get, endpoint: '/statistics/error/:range', preProcesses: this.lazyPreProcessesFor('getErrorStatistics'), tasks: [ async (req: Request, res: Response, issuer: string) : Promise => { res.send( await this.server_CS.getErrorStatistics( req.params.range as DyFM_RelativeDate, issuer ) ); }, ], }), ...this.additionalEndpoints, ]; } }