diff --git a/bizmatch-server/src/app.module.ts b/bizmatch-server/src/app.module.ts index 8440260..b40aec7 100644 --- a/bizmatch-server/src/app.module.ts +++ b/bizmatch-server/src/app.module.ts @@ -19,7 +19,10 @@ import { EventModule } from './event/event.module'; import { JwtStrategy } from './jwt.strategy'; import { MailModule } from './mail/mail.module'; +import { APP_INTERCEPTOR } from '@nestjs/core'; import { ClsMiddleware, ClsModule } from 'nestjs-cls'; +import { LoggingInterceptor } from './interceptors/logging.interceptor'; +import { UserInterceptor } from './interceptors/user.interceptor'; import { PaymentModule } from './payment/payment.module'; import { RequestDurationMiddleware } from './request-duration/request-duration.middleware'; import { SelectOptionsModule } from './select-options/select-options.module'; @@ -71,7 +74,19 @@ console.log(JSON.stringify(process.env, null, 2)); EventModule, ], controllers: [AppController, LogController], - providers: [AppService, FileService, JwtStrategy], + providers: [ + AppService, + FileService, + JwtStrategy, + { + provide: APP_INTERCEPTOR, + useClass: UserInterceptor, // Registriere den Interceptor global + }, + { + provide: APP_INTERCEPTOR, + useClass: LoggingInterceptor, // Registriere den LoggingInterceptor global + }, + ], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { diff --git a/bizmatch-server/src/drizzle/drizzle.module.ts b/bizmatch-server/src/drizzle/drizzle.module.ts index 4e3fab0..74bd1e3 100644 --- a/bizmatch-server/src/drizzle/drizzle.module.ts +++ b/bizmatch-server/src/drizzle/drizzle.module.ts @@ -25,7 +25,8 @@ const { Pool } = pkg; logQuery(query: string, params: unknown[]): void { const ip = cls.get('ip') || 'unknown'; const countryCode = cls.get('countryCode') || 'unknown'; - logger.info(`IP: ${ip} (${countryCode}) - Query: ${query} - Params: ${JSON.stringify(params)}`); + const username = cls.get('username') || 'unknown'; + logger.info(`IP: ${ip} (${countryCode}) (${username}) - 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 4295689..13df3cb 100644 --- a/bizmatch-server/src/event/event.controller.ts +++ b/bizmatch-server/src/event/event.controller.ts @@ -1,5 +1,6 @@ -import { Body, Controller, Headers, Post } from '@nestjs/common'; +import { Body, Controller, Headers, Post, UseGuards } from '@nestjs/common'; import { RealIp } from 'src/decorators/real-ip.decorator'; +import { OptionalJwtAuthGuard } from 'src/jwt-auth/optional-jwt-auth.guard'; import { ListingEvent } from 'src/models/db.model'; import { RealIpInfo } from 'src/models/main.model'; import { EventService } from './event.service'; @@ -7,6 +8,8 @@ import { EventService } from './event.service'; @Controller('event') export class EventController { constructor(private eventService: EventService) {} + + @UseGuards(OptionalJwtAuthGuard) @Post() async createEvent( @Body() event: ListingEvent, // Struktur des Body-Objekts entsprechend anpassen diff --git a/bizmatch-server/src/geo/geo.controller.ts b/bizmatch-server/src/geo/geo.controller.ts index 65d18ef..500d061 100644 --- a/bizmatch-server/src/geo/geo.controller.ts +++ b/bizmatch-server/src/geo/geo.controller.ts @@ -1,5 +1,6 @@ -import { Body, Controller, Get, Param, Post } from '@nestjs/common'; +import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; import { RealIp } from 'src/decorators/real-ip.decorator'; +import { OptionalJwtAuthGuard } from 'src/jwt-auth/optional-jwt-auth.guard'; import { RealIpInfo } from 'src/models/main.model'; import { CountyRequest } from 'src/models/server.model'; import { GeoService } from './geo.service'; @@ -8,24 +9,31 @@ import { GeoService } from './geo.service'; export class GeoController { constructor(private geoService: GeoService) {} + @UseGuards(OptionalJwtAuthGuard) @Get(':prefix') findByPrefix(@Param('prefix') prefix: string): any { return this.geoService.findCitiesStartingWith(prefix); } + + @UseGuards(OptionalJwtAuthGuard) @Get('citiesandstates/:prefix') findByCitiesAndStatesByPrefix(@Param('prefix') prefix: string): any { return this.geoService.findCitiesAndStatesStartingWith(prefix); } + @UseGuards(OptionalJwtAuthGuard) @Get(':prefix/:state') findByPrefixAndState(@Param('prefix') prefix: string, @Param('state') state: string): any { return this.geoService.findCitiesStartingWith(prefix, state); } + @UseGuards(OptionalJwtAuthGuard) @Post('counties') findByPrefixAndStates(@Body() countyRequest: CountyRequest): any { return this.geoService.findCountiesStartingWith(countyRequest.prefix, countyRequest.states); } + + @UseGuards(OptionalJwtAuthGuard) @Get('ipinfo/georesult/wysiwyg') async fetchIpAndGeoLocation(@RealIp() ipInfo: RealIpInfo): Promise { return await this.geoService.fetchIpAndGeoLocation(ipInfo); diff --git a/bizmatch-server/src/interceptors/logging.interceptor.ts b/bizmatch-server/src/interceptors/logging.interceptor.ts new file mode 100644 index 0000000..c0be9b0 --- /dev/null +++ b/bizmatch-server/src/interceptors/logging.interceptor.ts @@ -0,0 +1,40 @@ +// src/interceptors/logging.interceptor.ts +import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common'; +import { ClsService } from 'nestjs-cls'; +import { Observable } from 'rxjs'; +import { tap } from 'rxjs/operators'; + +@Injectable() +export class LoggingInterceptor implements NestInterceptor { + private readonly logger = new Logger(LoggingInterceptor.name); + + constructor(private readonly cls: ClsService) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const request = context.switchToHttp().getRequest(); + + const ip = this.cls.get('ip') || 'unknown'; + const countryCode = this.cls.get('countryCode') || 'unknown'; + const username = this.cls.get('username') || 'unknown'; + + const method = request.method; + const url = request.originalUrl; + const start = Date.now(); + + this.logger.log(`Entering ${method} ${url} from ${ip} (${countryCode})- User: ${username}`); + + return next.handle().pipe( + tap(() => { + const duration = Date.now() - start; + let logMessage = `${method} ${url} - ${duration}ms - IP: ${ip} - User: ${username}`; + + if (method === 'POST' || method === 'PUT') { + const body = JSON.stringify(request.body); + logMessage += ` - Incoming Body: ${body}`; + } + + this.logger.log(logMessage); + }), + ); + } +} diff --git a/bizmatch-server/src/interceptors/user.interceptor.ts b/bizmatch-server/src/interceptors/user.interceptor.ts new file mode 100644 index 0000000..45b058a --- /dev/null +++ b/bizmatch-server/src/interceptors/user.interceptor.ts @@ -0,0 +1,29 @@ +// src/interceptors/user.interceptor.ts +import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common'; +import { ClsService } from 'nestjs-cls'; +import { Observable } from 'rxjs'; + +@Injectable() +export class UserInterceptor implements NestInterceptor { + private readonly logger = new Logger(UserInterceptor.name); + + constructor(private readonly cls: ClsService) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const request = context.switchToHttp().getRequest(); + + // Überprüfe, ob der Benutzer authentifiziert ist + if (request.user && request.user.username) { + try { + this.cls.set('username', request.user.username); + this.logger.log(`CLS context gesetzt: Username=${request.user.username}`); + } catch (error) { + this.logger.error('Fehler beim Setzen der Username im CLS-Kontext', error); + } + } else { + this.logger.log('Kein authentifizierter Benutzer gefunden'); + } + + return next.handle(); + } +} diff --git a/bizmatch-server/src/listings/broker-listings.controller.ts b/bizmatch-server/src/listings/broker-listings.controller.ts index 9411ba6..f50aa63 100644 --- a/bizmatch-server/src/listings/broker-listings.controller.ts +++ b/bizmatch-server/src/listings/broker-listings.controller.ts @@ -1,5 +1,6 @@ -import { Body, Controller, Inject, Post } from '@nestjs/common'; +import { Body, Controller, Inject, Post, UseGuards } from '@nestjs/common'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; +import { OptionalJwtAuthGuard } from 'src/jwt-auth/optional-jwt-auth.guard'; import { UserListingCriteria } from 'src/models/main.model'; import { Logger } from 'winston'; import { UserService } from '../user/user.service'; @@ -11,6 +12,7 @@ export class BrokerListingsController { @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger, ) {} + @UseGuards(OptionalJwtAuthGuard) @Post('search') async find(@Body() criteria: UserListingCriteria): Promise { return await this.userService.searchUserListings(criteria); diff --git a/bizmatch-server/src/listings/business-listings.controller.ts b/bizmatch-server/src/listings/business-listings.controller.ts index ef1e587..3a1591a 100644 --- a/bizmatch-server/src/listings/business-listings.controller.ts +++ b/bizmatch-server/src/listings/business-listings.controller.ts @@ -41,18 +41,24 @@ export class BusinessListingsController { return await this.listingsService.getBusinessListingsCount(criteria, req.user as JwtUser); } + @UseGuards(OptionalJwtAuthGuard) @Post() async create(@Body() listing: any) { return await this.listingsService.createListing(listing); } + + @UseGuards(OptionalJwtAuthGuard) @Put() async update(@Body() listing: any) { return await this.listingsService.updateBusinessListing(listing.id, listing); } + + @UseGuards(OptionalJwtAuthGuard) @Delete('listing/:id') async deleteById(@Param('id') id: string) { await this.listingsService.deleteListing(id); } + @UseGuards(JwtAuthGuard) @Delete('favorite/:id') async deleteFavorite(@Request() req, @Param('id') id: string) { diff --git a/bizmatch-server/src/listings/commercial-property-listings.controller.ts b/bizmatch-server/src/listings/commercial-property-listings.controller.ts index a3cfb46..217deaa 100644 --- a/bizmatch-server/src/listings/commercial-property-listings.controller.ts +++ b/bizmatch-server/src/listings/commercial-property-listings.controller.ts @@ -21,16 +21,19 @@ export class CommercialPropertyListingsController { async findById(@Request() req, @Param('id') id: string): Promise { return await this.listingsService.findCommercialPropertiesById(id, req.user as JwtUser); } + @UseGuards(JwtAuthGuard) @Get('favorites/all') async findFavorites(@Request() req): Promise { return await this.listingsService.findFavoriteListings(req.user as JwtUser); } + @UseGuards(OptionalJwtAuthGuard) @Get('user/:email') async findByEmail(@Request() req, @Param('email') email: string): Promise { return await this.listingsService.findCommercialPropertiesByEmail(email, req.user as JwtUser); } + @UseGuards(OptionalJwtAuthGuard) @Post('find') async find(@Request() req, @Body() criteria: CommercialPropertyListingCriteria): Promise { @@ -41,14 +44,20 @@ export class CommercialPropertyListingsController { async findTotal(@Request() req, @Body() criteria: CommercialPropertyListingCriteria): Promise { return await this.listingsService.getCommercialPropertiesCount(criteria, req.user as JwtUser); } + + @UseGuards(OptionalJwtAuthGuard) @Post() async create(@Body() listing: any) { return await this.listingsService.createListing(listing); } + + @UseGuards(OptionalJwtAuthGuard) @Put() async update(@Body() listing: any) { return await this.listingsService.updateCommercialPropertyListing(listing.id, listing); } + + @UseGuards(OptionalJwtAuthGuard) @Delete('listing/:id/:imagePath') async deleteById(@Param('id') id: string, @Param('imagePath') imagePath: string) { await this.listingsService.deleteListing(id); diff --git a/bizmatch-server/src/mail/mail.controller.ts b/bizmatch-server/src/mail/mail.controller.ts index 1b708e4..d8cf345 100644 --- a/bizmatch-server/src/mail/mail.controller.ts +++ b/bizmatch-server/src/mail/mail.controller.ts @@ -1,4 +1,5 @@ -import { Body, Controller, Post } from '@nestjs/common'; +import { Body, Controller, Post, UseGuards } from '@nestjs/common'; +import { OptionalJwtAuthGuard } from 'src/jwt-auth/optional-jwt-auth.guard'; import { ShareByEMail, User } from 'src/models/db.model'; import { ErrorResponse, MailInfo } from '../models/main.model'; import { MailService } from './mail.service'; @@ -6,6 +7,8 @@ import { MailService } from './mail.service'; @Controller('mail') export class MailController { constructor(private mailService: MailService) {} + + @UseGuards(OptionalJwtAuthGuard) @Post() async sendEMail(@Body() mailInfo: MailInfo): Promise { if (mailInfo.listing) { @@ -14,10 +17,14 @@ export class MailController { return await this.mailService.sendRequest(mailInfo); } } + + @UseGuards(OptionalJwtAuthGuard) @Post('subscriptionConfirmation') async sendSubscriptionConfirmation(@Body() user: User): Promise { return await this.mailService.sendSubscriptionConfirmation(user); } + + @UseGuards(OptionalJwtAuthGuard) @Post('send2Friend') async send2Friend(@Body() shareByEMail: ShareByEMail): Promise { return await this.mailService.send2Friend(shareByEMail); diff --git a/bizmatch-server/src/payment/payment.controller.ts b/bizmatch-server/src/payment/payment.controller.ts index 8c84b6a..9b6fcd0 100644 --- a/bizmatch-server/src/payment/payment.controller.ts +++ b/bizmatch-server/src/payment/payment.controller.ts @@ -1,6 +1,7 @@ import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Param, Post, Req, Res, UseGuards } from '@nestjs/common'; import { Request, Response } from 'express'; import { AdminAuthGuard } from 'src/jwt-auth/admin-auth.guard'; +import { OptionalJwtAuthGuard } from 'src/jwt-auth/optional-jwt-auth.guard'; import { Checkout } from 'src/models/main.model'; import Stripe from 'stripe'; import { PaymentService } from './payment.service'; @@ -19,16 +20,20 @@ export class PaymentController { async getAllStripeCustomer(): Promise { return await this.paymentService.getAllStripeCustomer(); } + @UseGuards(AdminAuthGuard) @Get('subscription/all') async getAllStripeSubscriptions(): Promise { return await this.paymentService.getAllStripeSubscriptions(); } + @UseGuards(AdminAuthGuard) @Get('paymentmethod/:email') async getStripePaymentMethods(@Param('email') email: string): Promise { return await this.paymentService.getStripePaymentMethod(email); } + + @UseGuards(OptionalJwtAuthGuard) @Post('create-checkout-session') async createCheckoutSession(@Body() checkout: Checkout) { return await this.paymentService.createCheckoutSession(checkout); @@ -53,6 +58,8 @@ export class PaymentController { throw new HttpException('Webhook Error', HttpStatus.BAD_REQUEST); } } + + @UseGuards(OptionalJwtAuthGuard) @Get('subscriptions/:email') async findSubscriptionsById(@Param('email') email: string): Promise { return await this.paymentService.getSubscription(email); diff --git a/bizmatch-server/src/request-duration/request-duration.middleware.ts b/bizmatch-server/src/request-duration/request-duration.middleware.ts index 669b42f..ed81756 100644 --- a/bizmatch-server/src/request-duration/request-duration.middleware.ts +++ b/bizmatch-server/src/request-duration/request-duration.middleware.ts @@ -20,21 +20,22 @@ export class RequestDurationMiddleware implements NestMiddleware { this.logger.error('Failed to set CLS context', error); } - const start = Date.now(); + // const start = Date.now(); - this.logger.log(`Entering ${req.method} ${req.originalUrl} from ${ip}`); + // this.logger.log(`Entering ${req.method} ${req.originalUrl} from ${ip}`); - res.on('finish', () => { - const duration = Date.now() - start; - let logMessage = `${req.method} ${req.originalUrl} - ${duration}ms - IP: ${ip}`; + // res.on('finish', () => { + // const duration = Date.now() - start; + // const userEmail = this.cls.get('userEmail') || 'unknown'; + // let logMessage = `${req.method} ${req.originalUrl} - ${duration}ms - IP: ${ip} - User: ${userEmail}`; - if (req.method === 'POST' || req.method === 'PUT') { - const body = JSON.stringify(req.body); - logMessage += ` - Incoming Body: ${body}`; - } + // if (req.method === 'POST' || req.method === 'PUT') { + // const body = JSON.stringify(req.body); + // logMessage += ` - Incoming Body: ${body}`; + // } - this.logger.log(logMessage); - }); + // this.logger.log(logMessage); + // }); next(); } diff --git a/bizmatch-server/src/select-options/select-options.controller.ts b/bizmatch-server/src/select-options/select-options.controller.ts index a34192b..bcbb02e 100644 --- a/bizmatch-server/src/select-options/select-options.controller.ts +++ b/bizmatch-server/src/select-options/select-options.controller.ts @@ -1,9 +1,12 @@ -import { Controller, Get } from '@nestjs/common'; +import { Controller, Get, UseGuards } from '@nestjs/common'; +import { OptionalJwtAuthGuard } from 'src/jwt-auth/optional-jwt-auth.guard'; import { SelectOptionsService } from './select-options.service'; @Controller('select-options') export class SelectOptionsController { constructor(private selectOptionsService: SelectOptionsService) {} + + @UseGuards(OptionalJwtAuthGuard) @Get() getSelectOption(): any { return { diff --git a/bizmatch-server/src/user/user.controller.ts b/bizmatch-server/src/user/user.controller.ts index e8cddf1..8dff143 100644 --- a/bizmatch-server/src/user/user.controller.ts +++ b/bizmatch-server/src/user/user.controller.ts @@ -17,6 +17,7 @@ export class UserController { private fileService: FileService, @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger, ) {} + @UseGuards(OptionalJwtAuthGuard) @Get() async findByMail(@Request() req, @Query('mail') mail: string): Promise { @@ -24,6 +25,7 @@ export class UserController { return user; } + @UseGuards(OptionalJwtAuthGuard) @Get(':id') async findById(@Param('id') id: string): Promise { const user = await this.userService.getUserById(id); @@ -34,6 +36,8 @@ export class UserController { async getAllUser(): Promise { return await this.userService.getAllUser(); } + + @UseGuards(OptionalJwtAuthGuard) @Post() async save(@Body() user: any): Promise { try { @@ -52,16 +56,22 @@ export class UserController { throw error; // Andere Fehler einfach durchreichen } } + + @UseGuards(OptionalJwtAuthGuard) @Post('guaranteed') async saveGuaranteed(@Body() user: any): Promise { const savedUser = await this.userService.saveUser(user, false); return savedUser; } + + @UseGuards(OptionalJwtAuthGuard) @Post('search') async find(@Body() criteria: UserListingCriteria): Promise<{ results: User[]; totalCount: number }> { const foundUsers = await this.userService.searchUserListings(criteria); return foundUsers; } + + @UseGuards(OptionalJwtAuthGuard) @Post('findTotal') async findTotal(@Body() criteria: UserListingCriteria): Promise { return await this.userService.getUserListingsCount(criteria); diff --git a/bizmatch/src/app/pages/subscription/favorites/favorites.component.html b/bizmatch/src/app/pages/subscription/favorites/favorites.component.html index e3356b1..74eb33c 100644 --- a/bizmatch/src/app/pages/subscription/favorites/favorites.component.html +++ b/bizmatch/src/app/pages/subscription/favorites/favorites.component.html @@ -19,7 +19,7 @@ {{ listing.title }} {{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }} - {{ listing.location.name }}, {{ listing.location.state }} + {{ listing.location.name ? listing.location.name : listing.location.county }}, {{ listing.location.state }} ${{ listing.price.toLocaleString() }} @if(listing.listingsCategory==='business'){ @@ -47,7 +47,7 @@

{{ listing.title }}

Category: {{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }}

-

Located in: {{ listing.location.name }}, {{ listing.location.state }}

+

Located in: {{ listing.location.name ? listing.location.name : listing.location.county }}, {{ listing.location.state }}

Price: ${{ listing.price.toLocaleString() }}

@if(listing.listingsCategory==='business'){ diff --git a/bizmatch/src/app/pages/subscription/my-listing/my-listing.component.html b/bizmatch/src/app/pages/subscription/my-listing/my-listing.component.html index 65282d5..892bb16 100644 --- a/bizmatch/src/app/pages/subscription/my-listing/my-listing.component.html +++ b/bizmatch/src/app/pages/subscription/my-listing/my-listing.component.html @@ -18,7 +18,7 @@ {{ listing.title }} {{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }} - {{ listing.location.name }}, {{ listing.location.state }} + {{ listing.location.name ? listing.location.name : listing.location.county }}, {{ listing.location.state }} ${{ listing.price.toLocaleString() }} @if(listing.listingsCategory==='business'){ @@ -54,7 +54,7 @@

{{ listing.title }}

Category: {{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }}

-

Located in: {{ listing.location.name }} - {{ listing.location.state }}

+

Located in: {{ listing.location.name ? listing.location.name : listing.location.county }} - {{ listing.location.state }}

Price: ${{ listing.price.toLocaleString() }}

@if(listing.listingsCategory==='business'){