Logging Update - IPadress & user Email if possible

This commit is contained in:
Andreas Knuth 2024-09-23 13:45:46 +02:00
parent 974a6503ef
commit 1282d30b49
16 changed files with 163 additions and 22 deletions

View File

@ -19,7 +19,10 @@ 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 { APP_INTERCEPTOR } from '@nestjs/core';
import { ClsMiddleware, ClsModule } from 'nestjs-cls'; 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 { 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';
@ -71,7 +74,19 @@ console.log(JSON.stringify(process.env, null, 2));
EventModule, EventModule,
], ],
controllers: [AppController, LogController], 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 { export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) { configure(consumer: MiddlewareConsumer) {

View File

@ -25,7 +25,8 @@ const { Pool } = pkg;
logQuery(query: string, params: unknown[]): void { logQuery(query: string, params: unknown[]): void {
const ip = cls.get('ip') || 'unknown'; const ip = cls.get('ip') || 'unknown';
const countryCode = cls.get('countryCode') || '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)}`);
}, },
}; };

View File

@ -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 { 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 { ListingEvent } from 'src/models/db.model';
import { RealIpInfo } from 'src/models/main.model'; import { RealIpInfo } from 'src/models/main.model';
import { EventService } from './event.service'; import { EventService } from './event.service';
@ -7,6 +8,8 @@ import { EventService } from './event.service';
@Controller('event') @Controller('event')
export class EventController { export class EventController {
constructor(private eventService: EventService) {} constructor(private eventService: EventService) {}
@UseGuards(OptionalJwtAuthGuard)
@Post() @Post()
async createEvent( async createEvent(
@Body() event: ListingEvent, // Struktur des Body-Objekts entsprechend anpassen @Body() event: ListingEvent, // Struktur des Body-Objekts entsprechend anpassen

View File

@ -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 { 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 { RealIpInfo } from 'src/models/main.model';
import { CountyRequest } from 'src/models/server.model'; import { CountyRequest } from 'src/models/server.model';
import { GeoService } from './geo.service'; import { GeoService } from './geo.service';
@ -8,24 +9,31 @@ import { GeoService } from './geo.service';
export class GeoController { export class GeoController {
constructor(private geoService: GeoService) {} constructor(private geoService: GeoService) {}
@UseGuards(OptionalJwtAuthGuard)
@Get(':prefix') @Get(':prefix')
findByPrefix(@Param('prefix') prefix: string): any { findByPrefix(@Param('prefix') prefix: string): any {
return this.geoService.findCitiesStartingWith(prefix); return this.geoService.findCitiesStartingWith(prefix);
} }
@UseGuards(OptionalJwtAuthGuard)
@Get('citiesandstates/:prefix') @Get('citiesandstates/:prefix')
findByCitiesAndStatesByPrefix(@Param('prefix') prefix: string): any { findByCitiesAndStatesByPrefix(@Param('prefix') prefix: string): any {
return this.geoService.findCitiesAndStatesStartingWith(prefix); return this.geoService.findCitiesAndStatesStartingWith(prefix);
} }
@UseGuards(OptionalJwtAuthGuard)
@Get(':prefix/:state') @Get(':prefix/:state')
findByPrefixAndState(@Param('prefix') prefix: string, @Param('state') state: string): any { findByPrefixAndState(@Param('prefix') prefix: string, @Param('state') state: string): any {
return this.geoService.findCitiesStartingWith(prefix, state); return this.geoService.findCitiesStartingWith(prefix, state);
} }
@UseGuards(OptionalJwtAuthGuard)
@Post('counties') @Post('counties')
findByPrefixAndStates(@Body() countyRequest: CountyRequest): any { findByPrefixAndStates(@Body() countyRequest: CountyRequest): any {
return this.geoService.findCountiesStartingWith(countyRequest.prefix, countyRequest.states); return this.geoService.findCountiesStartingWith(countyRequest.prefix, countyRequest.states);
} }
@UseGuards(OptionalJwtAuthGuard)
@Get('ipinfo/georesult/wysiwyg') @Get('ipinfo/georesult/wysiwyg')
async fetchIpAndGeoLocation(@RealIp() ipInfo: RealIpInfo): Promise<any> { async fetchIpAndGeoLocation(@RealIp() ipInfo: RealIpInfo): Promise<any> {
return await this.geoService.fetchIpAndGeoLocation(ipInfo); return await this.geoService.fetchIpAndGeoLocation(ipInfo);

View File

@ -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<any> {
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);
}),
);
}
}

View File

@ -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<any> {
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();
}
}

View File

@ -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 { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { OptionalJwtAuthGuard } from 'src/jwt-auth/optional-jwt-auth.guard';
import { UserListingCriteria } from 'src/models/main.model'; import { UserListingCriteria } from 'src/models/main.model';
import { Logger } from 'winston'; import { Logger } from 'winston';
import { UserService } from '../user/user.service'; import { UserService } from '../user/user.service';
@ -11,6 +12,7 @@ export class BrokerListingsController {
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger, @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
) {} ) {}
@UseGuards(OptionalJwtAuthGuard)
@Post('search') @Post('search')
async find(@Body() criteria: UserListingCriteria): Promise<any> { async find(@Body() criteria: UserListingCriteria): Promise<any> {
return await this.userService.searchUserListings(criteria); return await this.userService.searchUserListings(criteria);

View File

@ -41,18 +41,24 @@ export class BusinessListingsController {
return await this.listingsService.getBusinessListingsCount(criteria, req.user as JwtUser); return await this.listingsService.getBusinessListingsCount(criteria, req.user as JwtUser);
} }
@UseGuards(OptionalJwtAuthGuard)
@Post() @Post()
async create(@Body() listing: any) { async create(@Body() listing: any) {
return await this.listingsService.createListing(listing); return await this.listingsService.createListing(listing);
} }
@UseGuards(OptionalJwtAuthGuard)
@Put() @Put()
async update(@Body() listing: any) { async update(@Body() listing: any) {
return await this.listingsService.updateBusinessListing(listing.id, listing); return await this.listingsService.updateBusinessListing(listing.id, listing);
} }
@UseGuards(OptionalJwtAuthGuard)
@Delete('listing/:id') @Delete('listing/:id')
async deleteById(@Param('id') id: string) { async deleteById(@Param('id') id: string) {
await this.listingsService.deleteListing(id); await this.listingsService.deleteListing(id);
} }
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Delete('favorite/:id') @Delete('favorite/:id')
async deleteFavorite(@Request() req, @Param('id') id: string) { async deleteFavorite(@Request() req, @Param('id') id: string) {

View File

@ -21,16 +21,19 @@ export class CommercialPropertyListingsController {
async findById(@Request() req, @Param('id') id: string): Promise<any> { async findById(@Request() req, @Param('id') id: string): Promise<any> {
return await this.listingsService.findCommercialPropertiesById(id, req.user as JwtUser); return await this.listingsService.findCommercialPropertiesById(id, req.user as JwtUser);
} }
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Get('favorites/all') @Get('favorites/all')
async findFavorites(@Request() req): Promise<any> { async findFavorites(@Request() req): Promise<any> {
return await this.listingsService.findFavoriteListings(req.user as JwtUser); return await this.listingsService.findFavoriteListings(req.user as JwtUser);
} }
@UseGuards(OptionalJwtAuthGuard) @UseGuards(OptionalJwtAuthGuard)
@Get('user/:email') @Get('user/:email')
async findByEmail(@Request() req, @Param('email') email: string): Promise<CommercialPropertyListing[]> { async findByEmail(@Request() req, @Param('email') email: string): Promise<CommercialPropertyListing[]> {
return await this.listingsService.findCommercialPropertiesByEmail(email, req.user as JwtUser); return await this.listingsService.findCommercialPropertiesByEmail(email, req.user as JwtUser);
} }
@UseGuards(OptionalJwtAuthGuard) @UseGuards(OptionalJwtAuthGuard)
@Post('find') @Post('find')
async find(@Request() req, @Body() criteria: CommercialPropertyListingCriteria): Promise<any> { async find(@Request() req, @Body() criteria: CommercialPropertyListingCriteria): Promise<any> {
@ -41,14 +44,20 @@ export class CommercialPropertyListingsController {
async findTotal(@Request() req, @Body() criteria: CommercialPropertyListingCriteria): Promise<number> { async findTotal(@Request() req, @Body() criteria: CommercialPropertyListingCriteria): Promise<number> {
return await this.listingsService.getCommercialPropertiesCount(criteria, req.user as JwtUser); return await this.listingsService.getCommercialPropertiesCount(criteria, req.user as JwtUser);
} }
@UseGuards(OptionalJwtAuthGuard)
@Post() @Post()
async create(@Body() listing: any) { async create(@Body() listing: any) {
return await this.listingsService.createListing(listing); return await this.listingsService.createListing(listing);
} }
@UseGuards(OptionalJwtAuthGuard)
@Put() @Put()
async update(@Body() listing: any) { async update(@Body() listing: any) {
return await this.listingsService.updateCommercialPropertyListing(listing.id, listing); return await this.listingsService.updateCommercialPropertyListing(listing.id, listing);
} }
@UseGuards(OptionalJwtAuthGuard)
@Delete('listing/:id/:imagePath') @Delete('listing/:id/:imagePath')
async deleteById(@Param('id') id: string, @Param('imagePath') imagePath: string) { async deleteById(@Param('id') id: string, @Param('imagePath') imagePath: string) {
await this.listingsService.deleteListing(id); await this.listingsService.deleteListing(id);

View File

@ -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 { ShareByEMail, User } from 'src/models/db.model';
import { ErrorResponse, MailInfo } from '../models/main.model'; import { ErrorResponse, MailInfo } from '../models/main.model';
import { MailService } from './mail.service'; import { MailService } from './mail.service';
@ -6,6 +7,8 @@ import { MailService } from './mail.service';
@Controller('mail') @Controller('mail')
export class MailController { export class MailController {
constructor(private mailService: MailService) {} constructor(private mailService: MailService) {}
@UseGuards(OptionalJwtAuthGuard)
@Post() @Post()
async sendEMail(@Body() mailInfo: MailInfo): Promise<void | ErrorResponse> { async sendEMail(@Body() mailInfo: MailInfo): Promise<void | ErrorResponse> {
if (mailInfo.listing) { if (mailInfo.listing) {
@ -14,10 +17,14 @@ export class MailController {
return await this.mailService.sendRequest(mailInfo); return await this.mailService.sendRequest(mailInfo);
} }
} }
@UseGuards(OptionalJwtAuthGuard)
@Post('subscriptionConfirmation') @Post('subscriptionConfirmation')
async sendSubscriptionConfirmation(@Body() user: User): Promise<void | ErrorResponse> { async sendSubscriptionConfirmation(@Body() user: User): Promise<void | ErrorResponse> {
return await this.mailService.sendSubscriptionConfirmation(user); return await this.mailService.sendSubscriptionConfirmation(user);
} }
@UseGuards(OptionalJwtAuthGuard)
@Post('send2Friend') @Post('send2Friend')
async send2Friend(@Body() shareByEMail: ShareByEMail): Promise<void | ErrorResponse> { async send2Friend(@Body() shareByEMail: ShareByEMail): Promise<void | ErrorResponse> {
return await this.mailService.send2Friend(shareByEMail); return await this.mailService.send2Friend(shareByEMail);

View File

@ -1,6 +1,7 @@
import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Param, Post, Req, Res, UseGuards } from '@nestjs/common'; import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Param, Post, Req, Res, UseGuards } from '@nestjs/common';
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { AdminAuthGuard } from 'src/jwt-auth/admin-auth.guard'; 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 { Checkout } from 'src/models/main.model';
import Stripe from 'stripe'; import Stripe from 'stripe';
import { PaymentService } from './payment.service'; import { PaymentService } from './payment.service';
@ -19,16 +20,20 @@ export class PaymentController {
async getAllStripeCustomer(): Promise<Stripe.Customer[]> { async getAllStripeCustomer(): Promise<Stripe.Customer[]> {
return await this.paymentService.getAllStripeCustomer(); return await this.paymentService.getAllStripeCustomer();
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@Get('subscription/all') @Get('subscription/all')
async getAllStripeSubscriptions(): Promise<Stripe.Subscription[]> { async getAllStripeSubscriptions(): Promise<Stripe.Subscription[]> {
return await this.paymentService.getAllStripeSubscriptions(); return await this.paymentService.getAllStripeSubscriptions();
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@Get('paymentmethod/:email') @Get('paymentmethod/:email')
async getStripePaymentMethods(@Param('email') email: string): Promise<Stripe.PaymentMethod[]> { async getStripePaymentMethods(@Param('email') email: string): Promise<Stripe.PaymentMethod[]> {
return await this.paymentService.getStripePaymentMethod(email); return await this.paymentService.getStripePaymentMethod(email);
} }
@UseGuards(OptionalJwtAuthGuard)
@Post('create-checkout-session') @Post('create-checkout-session')
async createCheckoutSession(@Body() checkout: Checkout) { async createCheckoutSession(@Body() checkout: Checkout) {
return await this.paymentService.createCheckoutSession(checkout); return await this.paymentService.createCheckoutSession(checkout);
@ -53,6 +58,8 @@ export class PaymentController {
throw new HttpException('Webhook Error', HttpStatus.BAD_REQUEST); throw new HttpException('Webhook Error', HttpStatus.BAD_REQUEST);
} }
} }
@UseGuards(OptionalJwtAuthGuard)
@Get('subscriptions/:email') @Get('subscriptions/:email')
async findSubscriptionsById(@Param('email') email: string): Promise<any> { async findSubscriptionsById(@Param('email') email: string): Promise<any> {
return await this.paymentService.getSubscription(email); return await this.paymentService.getSubscription(email);

View File

@ -20,21 +20,22 @@ export class RequestDurationMiddleware implements NestMiddleware {
this.logger.error('Failed to set CLS context', error); 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', () => { // res.on('finish', () => {
const duration = Date.now() - start; // const duration = Date.now() - start;
let logMessage = `${req.method} ${req.originalUrl} - ${duration}ms - IP: ${ip}`; // 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') { // if (req.method === 'POST' || req.method === 'PUT') {
const body = JSON.stringify(req.body); // const body = JSON.stringify(req.body);
logMessage += ` - Incoming Body: ${body}`; // logMessage += ` - Incoming Body: ${body}`;
} // }
this.logger.log(logMessage); // this.logger.log(logMessage);
}); // });
next(); next();
} }

View File

@ -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'; import { SelectOptionsService } from './select-options.service';
@Controller('select-options') @Controller('select-options')
export class SelectOptionsController { export class SelectOptionsController {
constructor(private selectOptionsService: SelectOptionsService) {} constructor(private selectOptionsService: SelectOptionsService) {}
@UseGuards(OptionalJwtAuthGuard)
@Get() @Get()
getSelectOption(): any { getSelectOption(): any {
return { return {

View File

@ -17,6 +17,7 @@ export class UserController {
private fileService: FileService, private fileService: FileService,
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger, @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
) {} ) {}
@UseGuards(OptionalJwtAuthGuard) @UseGuards(OptionalJwtAuthGuard)
@Get() @Get()
async findByMail(@Request() req, @Query('mail') mail: string): Promise<User> { async findByMail(@Request() req, @Query('mail') mail: string): Promise<User> {
@ -24,6 +25,7 @@ export class UserController {
return user; return user;
} }
@UseGuards(OptionalJwtAuthGuard)
@Get(':id') @Get(':id')
async findById(@Param('id') id: string): Promise<User> { async findById(@Param('id') id: string): Promise<User> {
const user = await this.userService.getUserById(id); const user = await this.userService.getUserById(id);
@ -34,6 +36,8 @@ export class UserController {
async getAllUser(): Promise<User[]> { async getAllUser(): Promise<User[]> {
return await this.userService.getAllUser(); return await this.userService.getAllUser();
} }
@UseGuards(OptionalJwtAuthGuard)
@Post() @Post()
async save(@Body() user: any): Promise<User> { async save(@Body() user: any): Promise<User> {
try { try {
@ -52,16 +56,22 @@ export class UserController {
throw error; // Andere Fehler einfach durchreichen throw error; // Andere Fehler einfach durchreichen
} }
} }
@UseGuards(OptionalJwtAuthGuard)
@Post('guaranteed') @Post('guaranteed')
async saveGuaranteed(@Body() user: any): Promise<User> { async saveGuaranteed(@Body() user: any): Promise<User> {
const savedUser = await this.userService.saveUser(user, false); const savedUser = await this.userService.saveUser(user, false);
return savedUser; return savedUser;
} }
@UseGuards(OptionalJwtAuthGuard)
@Post('search') @Post('search')
async find(@Body() criteria: UserListingCriteria): Promise<{ results: User[]; totalCount: number }> { async find(@Body() criteria: UserListingCriteria): Promise<{ results: User[]; totalCount: number }> {
const foundUsers = await this.userService.searchUserListings(criteria); const foundUsers = await this.userService.searchUserListings(criteria);
return foundUsers; return foundUsers;
} }
@UseGuards(OptionalJwtAuthGuard)
@Post('findTotal') @Post('findTotal')
async findTotal(@Body() criteria: UserListingCriteria): Promise<number> { async findTotal(@Body() criteria: UserListingCriteria): Promise<number> {
return await this.userService.getUserListingsCount(criteria); return await this.userService.getUserListingsCount(criteria);

View File

@ -19,7 +19,7 @@
<tr class="border-b"> <tr class="border-b">
<td class="py-2 px-4">{{ listing.title }}</td> <td class="py-2 px-4">{{ listing.title }}</td>
<td class="py-2 px-4">{{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }}</td> <td class="py-2 px-4">{{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }}</td>
<td class="py-2 px-4">{{ listing.location.name }}, {{ listing.location.state }}</td> <td class="py-2 px-4">{{ listing.location.name ? listing.location.name : listing.location.county }}, {{ listing.location.state }}</td>
<td class="py-2 px-4">${{ listing.price.toLocaleString() }}</td> <td class="py-2 px-4">${{ listing.price.toLocaleString() }}</td>
<td class="py-2 px-4 flex"> <td class="py-2 px-4 flex">
@if(listing.listingsCategory==='business'){ @if(listing.listingsCategory==='business'){
@ -47,7 +47,7 @@
<div *ngFor="let listing of favorites" class="bg-white shadow-md rounded-lg p-4 mb-4"> <div *ngFor="let listing of favorites" class="bg-white shadow-md rounded-lg p-4 mb-4">
<h2 class="text-xl font-semibold mb-2">{{ listing.title }}</h2> <h2 class="text-xl font-semibold mb-2">{{ listing.title }}</h2>
<p class="text-gray-600 mb-2">Category: {{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }}</p> <p class="text-gray-600 mb-2">Category: {{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }}</p>
<p class="text-gray-600 mb-2">Located in: {{ listing.location.name }}, {{ listing.location.state }}</p> <p class="text-gray-600 mb-2">Located in: {{ listing.location.name ? listing.location.name : listing.location.county }}, {{ listing.location.state }}</p>
<p class="text-gray-600 mb-2">Price: ${{ listing.price.toLocaleString() }}</p> <p class="text-gray-600 mb-2">Price: ${{ listing.price.toLocaleString() }}</p>
<div class="flex justify-start"> <div class="flex justify-start">
@if(listing.listingsCategory==='business'){ @if(listing.listingsCategory==='business'){

View File

@ -18,7 +18,7 @@
<tr *ngFor="let listing of myListings" class="border-b"> <tr *ngFor="let listing of myListings" class="border-b">
<td class="py-2 px-4">{{ listing.title }}</td> <td class="py-2 px-4">{{ listing.title }}</td>
<td class="py-2 px-4">{{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }}</td> <td class="py-2 px-4">{{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }}</td>
<td class="py-2 px-4">{{ listing.location.name }}, {{ listing.location.state }}</td> <td class="py-2 px-4">{{ listing.location.name ? listing.location.name : listing.location.county }}, {{ listing.location.state }}</td>
<td class="py-2 px-4">${{ listing.price.toLocaleString() }}</td> <td class="py-2 px-4">${{ listing.price.toLocaleString() }}</td>
<td class="py-2 px-4"> <td class="py-2 px-4">
@if(listing.listingsCategory==='business'){ @if(listing.listingsCategory==='business'){
@ -54,7 +54,7 @@
<div *ngFor="let listing of myListings" class="bg-white shadow-md rounded-lg p-4 mb-4"> <div *ngFor="let listing of myListings" class="bg-white shadow-md rounded-lg p-4 mb-4">
<h2 class="text-xl font-semibold mb-2">{{ listing.title }}</h2> <h2 class="text-xl font-semibold mb-2">{{ listing.title }}</h2>
<p class="text-gray-600 mb-2">Category: {{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }}</p> <p class="text-gray-600 mb-2">Category: {{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }}</p>
<p class="text-gray-600 mb-2">Located in: {{ listing.location.name }} - {{ listing.location.state }}</p> <p class="text-gray-600 mb-2">Located in: {{ listing.location.name ? listing.location.name : listing.location.county }} - {{ listing.location.state }}</p>
<p class="text-gray-600 mb-2">Price: ${{ listing.price.toLocaleString() }}</p> <p class="text-gray-600 mb-2">Price: ${{ listing.price.toLocaleString() }}</p>
<div class="flex justify-start"> <div class="flex justify-start">
@if(listing.listingsCategory==='business'){ @if(listing.listingsCategory==='business'){