BugFix: Proxy data, Logging with IP adresses
This commit is contained in:
parent
178f2b4810
commit
860d30b16f
|
|
@ -19,6 +19,7 @@ import { EventModule } from './event/event.module';
|
||||||
import { JwtStrategy } from './jwt.strategy';
|
import { JwtStrategy } from './jwt.strategy';
|
||||||
import { MailModule } from './mail/mail.module';
|
import { MailModule } from './mail/mail.module';
|
||||||
|
|
||||||
|
import { ContextService } from './context/context.service';
|
||||||
import { PaymentModule } from './payment/payment.module';
|
import { PaymentModule } from './payment/payment.module';
|
||||||
import { RequestDurationMiddleware } from './request-duration/request-duration.middleware';
|
import { RequestDurationMiddleware } from './request-duration/request-duration.middleware';
|
||||||
import { SelectOptionsModule } from './select-options/select-options.module';
|
import { SelectOptionsModule } from './select-options/select-options.module';
|
||||||
|
|
@ -63,7 +64,7 @@ console.log(JSON.stringify(process.env, null, 2));
|
||||||
EventModule,
|
EventModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController, LogController],
|
controllers: [AppController, LogController],
|
||||||
providers: [AppService, FileService, JwtStrategy],
|
providers: [AppService, FileService, JwtStrategy, ContextService, ContextService],
|
||||||
})
|
})
|
||||||
export class AppModule {
|
export class AppModule {
|
||||||
configure(consumer: MiddlewareConsumer) {
|
configure(consumer: MiddlewareConsumer) {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
import { createParamDecorator, ExecutionContext, Injectable } from '@nestjs/common';
|
import { createParamDecorator, ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
import { RealIpInfo } from './models/main.model';
|
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 => {
|
export const RealIp = createParamDecorator((data: unknown, ctx: ExecutionContext): RealIpInfo => {
|
||||||
const request = ctx.switchToHttp().getRequest();
|
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;
|
return getRealIpInfo(request);
|
||||||
const countryCode = request.headers['cf-ipcountry'];
|
|
||||||
return { ip, countryCode };
|
|
||||||
});
|
});
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppService {
|
export class AppService {
|
||||||
|
|
|
||||||
|
|
@ -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<RequestContext>();
|
||||||
|
|
||||||
|
run(context: RequestContext, callback: () => void) {
|
||||||
|
this.asyncLocalStorage.run(context, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
getContext(): RequestContext | undefined {
|
||||||
|
return this.asyncLocalStorage.getStore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,16 +3,18 @@ import { ConfigService } from '@nestjs/config';
|
||||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
import pkg from 'pg';
|
import pkg from 'pg';
|
||||||
|
import { ContextService, RequestContext } from 'src/context/context.service';
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
import * as schema from './schema';
|
import * as schema from './schema';
|
||||||
import { PG_CONNECTION } from './schema';
|
import { PG_CONNECTION } from './schema';
|
||||||
const { Pool } = pkg;
|
const { Pool } = pkg;
|
||||||
@Module({
|
@Module({
|
||||||
providers: [
|
providers: [
|
||||||
|
ContextService,
|
||||||
{
|
{
|
||||||
provide: PG_CONNECTION,
|
provide: PG_CONNECTION,
|
||||||
inject: [ConfigService, WINSTON_MODULE_PROVIDER],
|
inject: [ConfigService, WINSTON_MODULE_PROVIDER, ContextService],
|
||||||
useFactory: async (configService: ConfigService, logger: Logger) => {
|
useFactory: async (configService: ConfigService, logger: Logger, contextService: ContextService) => {
|
||||||
const connectionString = configService.get<string>('DATABASE_URL');
|
const connectionString = configService.get<string>('DATABASE_URL');
|
||||||
const pool = new Pool({
|
const pool = new Pool({
|
||||||
connectionString,
|
connectionString,
|
||||||
|
|
@ -21,7 +23,10 @@ const { Pool } = pkg;
|
||||||
// Definiere einen benutzerdefinierten Logger für Drizzle
|
// Definiere einen benutzerdefinierten Logger für Drizzle
|
||||||
const drizzleLogger = {
|
const drizzleLogger = {
|
||||||
logQuery(query: string, params: unknown[]): void {
|
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)}`);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,9 @@ async function bootstrap() {
|
||||||
|
|
||||||
app.enableCors({
|
app.enableCors({
|
||||||
origin: '*',
|
origin: '*',
|
||||||
//origin: 'http://localhost:4200', // Die URL Ihrer Angular-App
|
|
||||||
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
||||||
allowedHeaders: 'Content-Type, Accept, Authorization, x-hide-loading',
|
allowedHeaders: 'Content-Type, Accept, Authorization, x-hide-loading',
|
||||||
});
|
});
|
||||||
//origin: 'http://localhost:4200',
|
|
||||||
await app.listen(3000);
|
await app.listen(3000);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,36 @@
|
||||||
import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
|
import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
|
||||||
import { NextFunction, Request, Response } from 'express';
|
import { NextFunction, Request, Response } from 'express';
|
||||||
|
import { ContextService } from 'src/context/context.service';
|
||||||
|
import { getRealIpInfo } from 'src/utils/ip.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RequestDurationMiddleware implements NestMiddleware {
|
export class RequestDurationMiddleware implements NestMiddleware {
|
||||||
private readonly logger = new Logger(RequestDurationMiddleware.name);
|
private readonly logger = new Logger(RequestDurationMiddleware.name);
|
||||||
|
|
||||||
|
constructor(private readonly contextService: ContextService) {}
|
||||||
|
|
||||||
use(req: Request, res: Response, next: NextFunction) {
|
use(req: Request, res: Response, next: NextFunction) {
|
||||||
const start = Date.now();
|
const { ip, countryCode } = getRealIpInfo(req);
|
||||||
const clientIp = req.ip;
|
|
||||||
this.logger.log(`Entering ${req.method} ${req.originalUrl} from ${clientIp}`);
|
|
||||||
|
|
||||||
res.on('finish', () => {
|
// Initialisieren des Kontextes für diese Anfrage
|
||||||
const duration = Date.now() - start;
|
this.contextService.run({ ip, countryCode }, () => {
|
||||||
let logMessage = `${req.method} ${req.originalUrl} - ${duration}ms - IP: ${clientIp}`;
|
const start = Date.now();
|
||||||
|
|
||||||
if (req.method === 'POST' || req.method === 'PUT') {
|
this.logger.log(`Entering ${req.method} ${req.originalUrl} from ${ip}`);
|
||||||
const body = JSON.stringify(req.body);
|
|
||||||
logMessage += ` - Incoming Body: ${body}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ import { ListingsService } from '../../../services/listings.service';
|
||||||
import { SearchService } from '../../../services/search.service';
|
import { SearchService } from '../../../services/search.service';
|
||||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||||
import { UserService } from '../../../services/user.service';
|
import { UserService } from '../../../services/user.service';
|
||||||
import { getCriteriaStateObject } from '../../../utils/utils';
|
import { getCriteriaProxy } from '../../../utils/utils';
|
||||||
@UntilDestroy()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-broker-listings',
|
selector: 'app-broker-listings',
|
||||||
|
|
@ -54,7 +54,7 @@ export class BrokerListingsComponent {
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
) {
|
) {
|
||||||
this.criteria = getCriteriaStateObject('brokerListings');
|
this.criteria = getCriteriaProxy('brokerListings', this) as UserListingCriteria;
|
||||||
this.init();
|
this.init();
|
||||||
this.searchService.currentCriteria.pipe(untilDestroyed(this)).subscribe(criteria => {
|
this.searchService.currentCriteria.pipe(untilDestroyed(this)).subscribe(criteria => {
|
||||||
if (criteria && criteria.criteriaType === 'brokerListings') {
|
if (criteria && criteria.criteriaType === 'brokerListings') {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { ImageService } from '../../../services/image.service';
|
||||||
import { ListingsService } from '../../../services/listings.service';
|
import { ListingsService } from '../../../services/listings.service';
|
||||||
import { SearchService } from '../../../services/search.service';
|
import { SearchService } from '../../../services/search.service';
|
||||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||||
import { getCriteriaStateObject } from '../../../utils/utils';
|
import { getCriteriaProxy } from '../../../utils/utils';
|
||||||
@UntilDestroy()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-business-listings',
|
selector: 'app-business-listings',
|
||||||
|
|
@ -50,7 +50,7 @@ export class BusinessListingsComponent {
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
) {
|
) {
|
||||||
this.criteria = getCriteriaStateObject('businessListings');
|
this.criteria = getCriteriaProxy('businessListings', this) as BusinessListingCriteria;
|
||||||
this.init();
|
this.init();
|
||||||
this.searchService.currentCriteria.pipe(untilDestroyed(this)).subscribe(criteria => {
|
this.searchService.currentCriteria.pipe(untilDestroyed(this)).subscribe(criteria => {
|
||||||
if (criteria && criteria.criteriaType === 'businessListings') {
|
if (criteria && criteria.criteriaType === 'businessListings') {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { ImageService } from '../../../services/image.service';
|
||||||
import { ListingsService } from '../../../services/listings.service';
|
import { ListingsService } from '../../../services/listings.service';
|
||||||
import { SearchService } from '../../../services/search.service';
|
import { SearchService } from '../../../services/search.service';
|
||||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||||
import { getCriteriaStateObject } from '../../../utils/utils';
|
import { getCriteriaProxy } from '../../../utils/utils';
|
||||||
@UntilDestroy()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-commercial-property-listings',
|
selector: 'app-commercial-property-listings',
|
||||||
|
|
@ -49,7 +49,7 @@ export class CommercialPropertyListingsComponent {
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
) {
|
) {
|
||||||
this.criteria = getCriteriaStateObject('commercialPropertyListings');
|
this.criteria = getCriteriaProxy('commercialPropertyListings', this) as CommercialPropertyListingCriteria;
|
||||||
this.init();
|
this.init();
|
||||||
this.searchService.currentCriteria.pipe(untilDestroyed(this)).subscribe(criteria => {
|
this.searchService.currentCriteria.pipe(untilDestroyed(this)).subscribe(criteria => {
|
||||||
if (criteria && criteria.criteriaType === 'commercialPropertyListings') {
|
if (criteria && criteria.criteriaType === 'commercialPropertyListings') {
|
||||||
|
|
|
||||||
|
|
@ -335,6 +335,8 @@ export function createEnhancedProxy(obj: BusinessListingCriteria | CommercialPro
|
||||||
sessionStorageHandler.call(this, path, value, previous, applyData);
|
sessionStorageHandler.call(this, path, value, previous, applyData);
|
||||||
|
|
||||||
// Notify about the criteria change using the component's context
|
// Notify about the criteria change using the component's context
|
||||||
component.criteriaChangeService.notifyCriteriaChange();
|
if (component.criteriaChangeService) {
|
||||||
|
component.criteriaChangeService.notifyCriteriaChange();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue