diff --git a/bizmatch-server/package.json b/bizmatch-server/package.json index b0be50e..e91030f 100644 --- a/bizmatch-server/package.json +++ b/bizmatch-server/package.json @@ -36,6 +36,7 @@ "@nestjs/serve-static": "^4.0.1", "@types/stripe": "^8.0.417", "body-parser": "^1.20.2", + "cls-hooked": "^4.2.2", "cors": "^2.8.5", "dotenv": "^16.4.5", "dotenv-flow": "^4.1.0", @@ -48,6 +49,7 @@ "jwks-rsa": "^3.1.0", "ky": "^1.4.0", "nest-winston": "^1.9.4", + "nestjs-cls": "^4.4.1", "nodemailer": "^6.9.10", "nodemailer-smtp-transport": "^2.7.4", "openai": "^4.52.6", @@ -122,4 +124,4 @@ "coverageDirectory": "../coverage", "testEnvironment": "node" } -} \ No newline at end of file +} diff --git a/bizmatch-server/src/app.module.ts b/bizmatch-server/src/app.module.ts index 070f161..8440260 100644 --- a/bizmatch-server/src/app.module.ts +++ b/bizmatch-server/src/app.module.ts @@ -1,4 +1,4 @@ -import { MiddlewareConsumer, Module } from '@nestjs/common'; +import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { PassportModule } from '@nestjs/passport'; import { utilities as nestWinstonModuleUtilities, WinstonModule } from 'nest-winston'; @@ -19,7 +19,7 @@ import { EventModule } from './event/event.module'; import { JwtStrategy } from './jwt.strategy'; import { MailModule } from './mail/mail.module'; -import { ContextService } from './context/context.service'; +import { ClsMiddleware, ClsModule } from 'nestjs-cls'; import { PaymentModule } from './payment/payment.module'; import { RequestDurationMiddleware } from './request-duration/request-duration.middleware'; import { SelectOptionsModule } from './select-options/select-options.module'; @@ -31,6 +31,13 @@ console.log('Loaded environment variables:'); console.log(JSON.stringify(process.env, null, 2)); @Module({ imports: [ + ClsModule.forRoot({ + global: true, // Macht den ClsService global verfügbar + middleware: { mount: true }, // Registriert automatisch die ClsMiddleware + // setup: clsService => { + // // Optional: zusätzliche Setup-Logik + // }, + }), ConfigModule.forRoot({ isGlobal: true }), MailModule, AuthModule, @@ -64,10 +71,11 @@ console.log(JSON.stringify(process.env, null, 2)); EventModule, ], controllers: [AppController, LogController], - providers: [AppService, FileService, JwtStrategy, ContextService, ContextService], + providers: [AppService, FileService, JwtStrategy], }) -export class AppModule { +export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { + consumer.apply(ClsMiddleware).forRoutes('*'); consumer.apply(RequestDurationMiddleware).forRoutes('*'); } } diff --git a/bizmatch-server/src/app.service.ts b/bizmatch-server/src/app.service.ts index 3bd289f..927d7cc 100644 --- a/bizmatch-server/src/app.service.ts +++ b/bizmatch-server/src/app.service.ts @@ -1,16 +1,5 @@ -import { createParamDecorator, ExecutionContext, Injectable } from '@nestjs/common'; -import { RealIpInfo } from './models/main.model'; -import { getRealIpInfo } from './utils/ip.util'; -// export const RealIp = createParamDecorator((data: unknown, ctx: ExecutionContext): RealIpInfo => { -// const request = ctx.switchToHttp().getRequest(); -// const ip = request.headers['cf-connecting-ip'] || request.headers['x-real-ip'] || request.headers['x-forwarded-for']?.split(',')[0] || request.connection.remoteAddress; -// const countryCode = request.headers['cf-ipcountry']; -// return { ip, countryCode }; -// }); -export const RealIp = createParamDecorator((data: unknown, ctx: ExecutionContext): RealIpInfo => { - const request = ctx.switchToHttp().getRequest(); - return getRealIpInfo(request); -}); +import { Injectable } from '@nestjs/common'; + @Injectable() export class AppService { getHello(): string { diff --git a/bizmatch-server/src/context/context.service.ts b/bizmatch-server/src/context/context.service.ts deleted file mode 100644 index 07dbf93..0000000 --- a/bizmatch-server/src/context/context.service.ts +++ /dev/null @@ -1,21 +0,0 @@ -// src/context/context.service.ts -import { Injectable } from '@nestjs/common'; -import { AsyncLocalStorage } from 'async_hooks'; - -export interface RequestContext { - ip: string; - countryCode?: string; -} - -@Injectable() -export class ContextService { - private readonly asyncLocalStorage = new AsyncLocalStorage(); - - run(context: RequestContext, callback: () => void) { - this.asyncLocalStorage.run(context, callback); - } - - getContext(): RequestContext | undefined { - return this.asyncLocalStorage.getStore(); - } -} diff --git a/bizmatch-server/src/decorators/real-ip.decorator.ts b/bizmatch-server/src/decorators/real-ip.decorator.ts new file mode 100644 index 0000000..4fe33a7 --- /dev/null +++ b/bizmatch-server/src/decorators/real-ip.decorator.ts @@ -0,0 +1,8 @@ +// src/decorators/real-ip.decorator.ts +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; +import { getRealIpInfo, RealIpInfo } from '../utils/ip.util'; + +export const RealIp = createParamDecorator((data: unknown, ctx: ExecutionContext): RealIpInfo => { + const request = ctx.switchToHttp().getRequest(); + return getRealIpInfo(request); +}); diff --git a/bizmatch-server/src/drizzle/drizzle.module.ts b/bizmatch-server/src/drizzle/drizzle.module.ts index 1571f4a..4e3fab0 100644 --- a/bizmatch-server/src/drizzle/drizzle.module.ts +++ b/bizmatch-server/src/drizzle/drizzle.module.ts @@ -2,30 +2,29 @@ import { Module } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { drizzle } from 'drizzle-orm/node-postgres'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; +import { ClsService } from 'nestjs-cls'; import pkg from 'pg'; -import { ContextService, RequestContext } from 'src/context/context.service'; import { Logger } from 'winston'; import * as schema from './schema'; import { PG_CONNECTION } from './schema'; const { Pool } = pkg; @Module({ providers: [ - ContextService, { provide: PG_CONNECTION, - inject: [ConfigService, WINSTON_MODULE_PROVIDER, ContextService], - useFactory: async (configService: ConfigService, logger: Logger, contextService: ContextService) => { + inject: [ConfigService, WINSTON_MODULE_PROVIDER, ClsService], + useFactory: async (configService: ConfigService, logger: Logger, cls: ClsService) => { const connectionString = configService.get('DATABASE_URL'); const pool = new Pool({ connectionString, - // ssl: true, + // ssl: true, // Falls benötigt }); + // Definiere einen benutzerdefinierten Logger für Drizzle const drizzleLogger = { logQuery(query: string, params: unknown[]): void { - const context: RequestContext | undefined = contextService.getContext(); - const ip = context?.ip || 'unknown'; - const countryCode = context?.countryCode || 'unknown'; + const ip = cls.get('ip') || 'unknown'; + const countryCode = cls.get('countryCode') || 'unknown'; logger.info(`IP: ${ip} (${countryCode}) - Query: ${query} - Params: ${JSON.stringify(params)}`); }, }; diff --git a/bizmatch-server/src/event/event.controller.ts b/bizmatch-server/src/event/event.controller.ts index 98535c7..4295689 100644 --- a/bizmatch-server/src/event/event.controller.ts +++ b/bizmatch-server/src/event/event.controller.ts @@ -1,5 +1,5 @@ import { Body, Controller, Headers, Post } from '@nestjs/common'; -import { RealIp } from 'src/app.service'; +import { RealIp } from 'src/decorators/real-ip.decorator'; import { ListingEvent } from 'src/models/db.model'; import { RealIpInfo } from 'src/models/main.model'; import { EventService } from './event.service'; diff --git a/bizmatch-server/src/geo/geo.controller.ts b/bizmatch-server/src/geo/geo.controller.ts index 0989e69..65d18ef 100644 --- a/bizmatch-server/src/geo/geo.controller.ts +++ b/bizmatch-server/src/geo/geo.controller.ts @@ -1,5 +1,5 @@ import { Body, Controller, Get, Param, Post } from '@nestjs/common'; -import { RealIp } from 'src/app.service'; +import { RealIp } from 'src/decorators/real-ip.decorator'; import { RealIpInfo } from 'src/models/main.model'; import { CountyRequest } from 'src/models/server.model'; import { GeoService } from './geo.service'; diff --git a/bizmatch-server/src/request-duration/request-duration.middleware.ts b/bizmatch-server/src/request-duration/request-duration.middleware.ts index c0a3718..669b42f 100644 --- a/bizmatch-server/src/request-duration/request-duration.middleware.ts +++ b/bizmatch-server/src/request-duration/request-duration.middleware.ts @@ -1,36 +1,41 @@ import { Injectable, Logger, NestMiddleware } from '@nestjs/common'; import { NextFunction, Request, Response } from 'express'; -import { ContextService } from 'src/context/context.service'; +import { ClsService } from 'nestjs-cls'; import { getRealIpInfo } from 'src/utils/ip.util'; @Injectable() export class RequestDurationMiddleware implements NestMiddleware { private readonly logger = new Logger(RequestDurationMiddleware.name); - constructor(private readonly contextService: ContextService) {} + constructor(private readonly cls: ClsService) {} use(req: Request, res: Response, next: NextFunction) { const { ip, countryCode } = getRealIpInfo(req); - // Initialisieren des Kontextes für diese Anfrage - this.contextService.run({ ip, countryCode }, () => { - const start = Date.now(); + // Setze die IP-Adresse und den Ländercode im CLS-Kontext + try { + this.cls.set('ip', ip); + this.cls.set('countryCode', countryCode); + } catch (error) { + this.logger.error('Failed to set CLS context', error); + } - this.logger.log(`Entering ${req.method} ${req.originalUrl} from ${ip}`); + const start = Date.now(); - res.on('finish', () => { - const duration = Date.now() - start; - let logMessage = `${req.method} ${req.originalUrl} - ${duration}ms - IP: ${ip}`; + this.logger.log(`Entering ${req.method} ${req.originalUrl} from ${ip}`); - if (req.method === 'POST' || req.method === 'PUT') { - const body = JSON.stringify(req.body); - logMessage += ` - Incoming Body: ${body}`; - } + res.on('finish', () => { + const duration = Date.now() - start; + let logMessage = `${req.method} ${req.originalUrl} - ${duration}ms - IP: ${ip}`; - this.logger.log(logMessage); - }); + if (req.method === 'POST' || req.method === 'PUT') { + const body = JSON.stringify(req.body); + logMessage += ` - Incoming Body: ${body}`; + } - next(); + this.logger.log(logMessage); }); + + next(); } }