diff --git a/bizmatch-server/src/app.module.ts b/bizmatch-server/src/app.module.ts index d2b7623..070f161 100644 --- a/bizmatch-server/src/app.module.ts +++ b/bizmatch-server/src/app.module.ts @@ -19,6 +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 { PaymentModule } from './payment/payment.module'; import { RequestDurationMiddleware } from './request-duration/request-duration.middleware'; import { SelectOptionsModule } from './select-options/select-options.module'; @@ -63,7 +64,7 @@ console.log(JSON.stringify(process.env, null, 2)); EventModule, ], controllers: [AppController, LogController], - providers: [AppService, FileService, JwtStrategy], + providers: [AppService, FileService, JwtStrategy, ContextService, ContextService], }) export class AppModule { configure(consumer: MiddlewareConsumer) { diff --git a/bizmatch-server/src/app.service.ts b/bizmatch-server/src/app.service.ts index 7105724..3bd289f 100644 --- a/bizmatch-server/src/app.service.ts +++ b/bizmatch-server/src/app.service.ts @@ -1,10 +1,15 @@ 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(); - 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 }; + return getRealIpInfo(request); }); @Injectable() export class AppService { diff --git a/bizmatch-server/src/context/context.service.ts b/bizmatch-server/src/context/context.service.ts new file mode 100644 index 0000000..07dbf93 --- /dev/null +++ b/bizmatch-server/src/context/context.service.ts @@ -0,0 +1,21 @@ +// 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/drizzle/drizzle.module.ts b/bizmatch-server/src/drizzle/drizzle.module.ts index 319ce9c..1571f4a 100644 --- a/bizmatch-server/src/drizzle/drizzle.module.ts +++ b/bizmatch-server/src/drizzle/drizzle.module.ts @@ -3,16 +3,18 @@ import { ConfigService } from '@nestjs/config'; import { drizzle } from 'drizzle-orm/node-postgres'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; 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], - useFactory: async (configService: ConfigService, logger: Logger) => { + inject: [ConfigService, WINSTON_MODULE_PROVIDER, ContextService], + useFactory: async (configService: ConfigService, logger: Logger, contextService: ContextService) => { const connectionString = configService.get('DATABASE_URL'); const pool = new Pool({ connectionString, @@ -21,7 +23,10 @@ const { Pool } = pkg; // Definiere einen benutzerdefinierten Logger für Drizzle const drizzleLogger = { logQuery(query: string, params: unknown[]): void { - logger.info(query, params); + const context: RequestContext | undefined = contextService.getContext(); + const ip = context?.ip || 'unknown'; + const countryCode = context?.countryCode || 'unknown'; + logger.info(`IP: ${ip} (${countryCode}) - Query: ${query} - Params: ${JSON.stringify(params)}`); }, }; diff --git a/bizmatch-server/src/main.ts b/bizmatch-server/src/main.ts index e3e3210..dffabdf 100644 --- a/bizmatch-server/src/main.ts +++ b/bizmatch-server/src/main.ts @@ -17,11 +17,9 @@ async function bootstrap() { app.enableCors({ origin: '*', - //origin: 'http://localhost:4200', // Die URL Ihrer Angular-App methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', allowedHeaders: 'Content-Type, Accept, Authorization, x-hide-loading', }); - //origin: 'http://localhost:4200', await app.listen(3000); } bootstrap(); diff --git a/bizmatch-server/src/request-duration/request-duration.middleware.ts b/bizmatch-server/src/request-duration/request-duration.middleware.ts index 70ba4f5..c0a3718 100644 --- a/bizmatch-server/src/request-duration/request-duration.middleware.ts +++ b/bizmatch-server/src/request-duration/request-duration.middleware.ts @@ -1,27 +1,36 @@ import { Injectable, Logger, NestMiddleware } from '@nestjs/common'; import { NextFunction, Request, Response } from 'express'; +import { ContextService } from 'src/context/context.service'; +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) {} + use(req: Request, res: Response, next: NextFunction) { - const start = Date.now(); - const clientIp = req.ip; - this.logger.log(`Entering ${req.method} ${req.originalUrl} from ${clientIp}`); + const { ip, countryCode } = getRealIpInfo(req); - res.on('finish', () => { - const duration = Date.now() - start; - let logMessage = `${req.method} ${req.originalUrl} - ${duration}ms - IP: ${clientIp}`; + // Initialisieren des Kontextes für diese Anfrage + this.contextService.run({ ip, countryCode }, () => { + const start = Date.now(); - if (req.method === 'POST' || req.method === 'PUT') { - const body = JSON.stringify(req.body); - logMessage += ` - Incoming Body: ${body}`; - } + this.logger.log(`Entering ${req.method} ${req.originalUrl} from ${ip}`); - this.logger.log(logMessage); + res.on('finish', () => { + const duration = Date.now() - start; + let logMessage = `${req.method} ${req.originalUrl} - ${duration}ms - IP: ${ip}`; + + if (req.method === 'POST' || req.method === 'PUT') { + const body = JSON.stringify(req.body); + logMessage += ` - Incoming Body: ${body}`; + } + + this.logger.log(logMessage); + }); + + next(); }); - - next(); } } diff --git a/bizmatch-server/src/utils/ip.util.ts b/bizmatch-server/src/utils/ip.util.ts new file mode 100644 index 0000000..5b98aaf --- /dev/null +++ b/bizmatch-server/src/utils/ip.util.ts @@ -0,0 +1,16 @@ +import { Request } from 'express'; + +export interface RealIpInfo { + ip: string | undefined; + countryCode?: string; +} + +export function getRealIpInfo(req: Request): RealIpInfo { + const ip = + (req.headers['cf-connecting-ip'] as string) || + (req.headers['x-real-ip'] as string) || + (typeof req.headers['x-forwarded-for'] === 'string' ? req.headers['x-forwarded-for'].split(',')[0] : req.connection.remoteAddress); + const countryCode = req.headers['cf-ipcountry'] as string; + + return { ip, countryCode }; +} diff --git a/bizmatch/src/app/pages/listings/broker-listings/broker-listings.component.ts b/bizmatch/src/app/pages/listings/broker-listings/broker-listings.component.ts index 9bd1271..7d7da8d 100644 --- a/bizmatch/src/app/pages/listings/broker-listings/broker-listings.component.ts +++ b/bizmatch/src/app/pages/listings/broker-listings/broker-listings.component.ts @@ -13,7 +13,7 @@ import { ListingsService } from '../../../services/listings.service'; import { SearchService } from '../../../services/search.service'; import { SelectOptionsService } from '../../../services/select-options.service'; import { UserService } from '../../../services/user.service'; -import { getCriteriaStateObject } from '../../../utils/utils'; +import { getCriteriaProxy } from '../../../utils/utils'; @UntilDestroy() @Component({ selector: 'app-broker-listings', @@ -54,7 +54,7 @@ export class BrokerListingsComponent { private route: ActivatedRoute, private searchService: SearchService, ) { - this.criteria = getCriteriaStateObject('brokerListings'); + this.criteria = getCriteriaProxy('brokerListings', this) as UserListingCriteria; this.init(); this.searchService.currentCriteria.pipe(untilDestroyed(this)).subscribe(criteria => { if (criteria && criteria.criteriaType === 'brokerListings') { diff --git a/bizmatch/src/app/pages/listings/business-listings/business-listings.component.ts b/bizmatch/src/app/pages/listings/business-listings/business-listings.component.ts index f52a1a7..0ad6caf 100644 --- a/bizmatch/src/app/pages/listings/business-listings/business-listings.component.ts +++ b/bizmatch/src/app/pages/listings/business-listings/business-listings.component.ts @@ -12,7 +12,7 @@ import { ImageService } from '../../../services/image.service'; import { ListingsService } from '../../../services/listings.service'; import { SearchService } from '../../../services/search.service'; import { SelectOptionsService } from '../../../services/select-options.service'; -import { getCriteriaStateObject } from '../../../utils/utils'; +import { getCriteriaProxy } from '../../../utils/utils'; @UntilDestroy() @Component({ selector: 'app-business-listings', @@ -50,7 +50,7 @@ export class BusinessListingsComponent { private route: ActivatedRoute, private searchService: SearchService, ) { - this.criteria = getCriteriaStateObject('businessListings'); + this.criteria = getCriteriaProxy('businessListings', this) as BusinessListingCriteria; this.init(); this.searchService.currentCriteria.pipe(untilDestroyed(this)).subscribe(criteria => { if (criteria && criteria.criteriaType === 'businessListings') { diff --git a/bizmatch/src/app/pages/listings/commercial-property-listings/commercial-property-listings.component.ts b/bizmatch/src/app/pages/listings/commercial-property-listings/commercial-property-listings.component.ts index dcdc9a0..4e56705 100644 --- a/bizmatch/src/app/pages/listings/commercial-property-listings/commercial-property-listings.component.ts +++ b/bizmatch/src/app/pages/listings/commercial-property-listings/commercial-property-listings.component.ts @@ -12,7 +12,7 @@ import { ImageService } from '../../../services/image.service'; import { ListingsService } from '../../../services/listings.service'; import { SearchService } from '../../../services/search.service'; import { SelectOptionsService } from '../../../services/select-options.service'; -import { getCriteriaStateObject } from '../../../utils/utils'; +import { getCriteriaProxy } from '../../../utils/utils'; @UntilDestroy() @Component({ selector: 'app-commercial-property-listings', @@ -49,7 +49,7 @@ export class CommercialPropertyListingsComponent { private route: ActivatedRoute, private searchService: SearchService, ) { - this.criteria = getCriteriaStateObject('commercialPropertyListings'); + this.criteria = getCriteriaProxy('commercialPropertyListings', this) as CommercialPropertyListingCriteria; this.init(); this.searchService.currentCriteria.pipe(untilDestroyed(this)).subscribe(criteria => { if (criteria && criteria.criteriaType === 'commercialPropertyListings') { diff --git a/bizmatch/src/app/utils/utils.ts b/bizmatch/src/app/utils/utils.ts index f19cef3..72e5ea9 100644 --- a/bizmatch/src/app/utils/utils.ts +++ b/bizmatch/src/app/utils/utils.ts @@ -335,6 +335,8 @@ export function createEnhancedProxy(obj: BusinessListingCriteria | CommercialPro sessionStorageHandler.call(this, path, value, previous, applyData); // Notify about the criteria change using the component's context - component.criteriaChangeService.notifyCriteriaChange(); + if (component.criteriaChangeService) { + component.criteriaChangeService.notifyCriteriaChange(); + } }); }