BugFixes bzgl. Observables, Logging Anpassungen, Endpunkt hardening, dotenv-flow usage

This commit is contained in:
Andreas Knuth 2024-09-20 13:49:50 +02:00
parent 205793faab
commit 3e84b82c92
25 changed files with 141 additions and 181 deletions

View File

@ -0,0 +1,4 @@
REALM=bizmatch-dev
usersURL=/admin/realms/bizmatch-dev/users
WEB_HOST=https://dev.bizmatch.net
STRIPE_WEBHOOK_SECRET=whsec_w2yvJY8qFMfO5wJgyNHCn6oYT7o2J5pS

View File

@ -0,0 +1,2 @@
REALM=bizmatch
WEB_HOST=https://www.bizmatch.net

View File

@ -8,11 +8,11 @@
"scripts": { "scripts": {
"build": "nest build", "build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "HOST_NAME=localhost nest start", "start": "nest start",
"start:local": "HOST_NAME=localhost node dist/src/main", "start:local": "HOST_NAME=localhost node dist/src/main",
"start:dev": "HOST_NAME=dev.bizmatch.net node dist/src/main", "start:dev": "NODE_ENV=development node dist/src/main",
"start:debug": "nest start --debug --watch", "start:debug": "nest start --debug --watch",
"start:prod": "HOST_NAME=www.bizmatch.net node dist/src/main", "start:prod": "NODE_ENV=production node dist/src/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest", "test": "jest",
"test:watch": "jest --watch", "test:watch": "jest --watch",
@ -38,6 +38,7 @@
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"dotenv-flow": "^4.1.0",
"drizzle-orm": "^0.32.0", "drizzle-orm": "^0.32.0",
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"groq-sdk": "^0.5.0", "groq-sdk": "^0.5.0",

View File

@ -1,8 +1,6 @@
import { MiddlewareConsumer, Module } from '@nestjs/common'; import { MiddlewareConsumer, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import { PassportModule } from '@nestjs/passport'; import { PassportModule } from '@nestjs/passport';
import * as dotenv from 'dotenv';
import fs from 'fs-extra';
import { WinstonModule, utilities as nestWinstonModuleUtilities } from 'nest-winston'; import { WinstonModule, utilities as nestWinstonModuleUtilities } from 'nest-winston';
import * as winston from 'winston'; import * as winston from 'winston';
import { AiModule } from './ai/ai.module'; import { AiModule } from './ai/ai.module';
@ -16,46 +14,48 @@ import { ListingsModule } from './listings/listings.module';
import { LogController } from './log/log.controller'; import { LogController } from './log/log.controller';
import { LogModule } from './log/log.module'; import { LogModule } from './log/log.module';
import dotenvFlow from 'dotenv-flow';
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 { 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';
import { UserModule } from './user/user.module'; import { UserModule } from './user/user.module';
import { EventModule } from './event/event.module';
// const __filename = fileURLToPath(import.meta.url);
// const __dirname = path.dirname(__filename);
function loadEnvFiles() { // function loadEnvFiles() {
// Determine which additional env file to load // // Determine which additional env file to load
let envFilePath = ''; // let envFilePath = '';
const host = process.env.HOST_NAME || ''; // const host = process.env.HOST_NAME || '';
if (host.includes('localhost')) { // if (host.includes('localhost')) {
envFilePath = '.env.local'; // envFilePath = '.env.local';
} else if (host.includes('dev.bizmatch.net')) { // } else if (host.includes('dev.bizmatch.net')) {
envFilePath = '.env.dev'; // envFilePath = '.env.dev';
} else if (host.includes('www.bizmatch.net') || host.includes('bizmatch.net')) { // } else if (host.includes('www.bizmatch.net') || host.includes('bizmatch.net')) {
envFilePath = '.env.prod'; // envFilePath = '.env.prod';
} // }
// Load the additional env file if it exists // // Load the additional env file if it exists
if (fs.existsSync(envFilePath)) { // if (fs.existsSync(envFilePath)) {
dotenv.config({ path: envFilePath }); // dotenv.config({ path: envFilePath });
console.log(`Loaded ${envFilePath} file`); // console.log(`Loaded ${envFilePath} file`);
} else { // } else {
console.log(`No additional .env file found for HOST_NAME: ${host}`); // console.log(`No additional .env file found for HOST_NAME: ${host}`);
} // }
// Load the .env file // // Load the .env file
dotenv.config(); // dotenv.config();
console.log('Loaded .env file'); // console.log('Loaded .env file');
// Output all loaded environment variables // // Output all loaded environment variables
console.log('Loaded environment variables:'); // console.log('Loaded environment variables:');
console.log(JSON.stringify(process.env, null, 2)); // console.log(JSON.stringify(process.env, null, 2));
} // }
loadEnvFiles(); //loadEnvFiles();
dotenvFlow.config();
console.log('Loaded environment variables:');
console.log(JSON.stringify(process.env, null, 2));
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot({ isGlobal: true }), ConfigModule.forRoot({ isGlobal: true }),
@ -65,7 +65,9 @@ loadEnvFiles();
transports: [ transports: [
new winston.transports.Console({ new winston.transports.Console({
format: winston.format.combine( format: winston.format.combine(
winston.format.timestamp(), winston.format.timestamp({
format: 'YYYY-MM-DD hh:mm:ss.SSS A',
}),
winston.format.ms(), winston.format.ms(),
nestWinstonModuleUtilities.format.nestLike('Bizmatch', { nestWinstonModuleUtilities.format.nestLike('Bizmatch', {
colors: true, colors: true,

View File

@ -10,25 +10,25 @@ export class AuthController {
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@Get() @Get()
getAccessToken(): any { async getAccessToken(): Promise<any> {
return this.authService.getAccessToken(); return await this.authService.getAccessToken();
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@Get('user/all') @Get('user/all')
getUsers(): any { async getUsers(): Promise<any> {
return this.authService.getUsers(); return await this.authService.getUsers();
} }
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Get('users/:userid') @Get('users/:userid')
getUser(@Param('userid') userId: string): any { async getUser(@Param('userid') userId: string): Promise<any> {
return this.authService.getUser(userId); return await this.authService.getUser(userId);
} }
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Put('users/:userid') @Put('users/:userid')
updateKeycloakUser(@Body() keycloakUser: KeycloakUser): any { async updateKeycloakUser(@Body() keycloakUser: KeycloakUser): Promise<any> {
return this.authService.updateKeycloakUser(keycloakUser); return await this.authService.updateKeycloakUser(keycloakUser);
} }
// @UseGuards(AdminAuthGuard) // @UseGuards(AdminAuthGuard)
// @Get('user/:userid/lastlogin') //e0811669-c7eb-4e5e-a699-e8334d5c5b01 -> aknuth // @Get('user/:userid/lastlogin') //e0811669-c7eb-4e5e-a699-e8334d5c5b01 -> aknuth

View File

@ -1,7 +1,9 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; 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 pkg from 'pg'; import pkg from 'pg';
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;
@ -9,15 +11,21 @@ const { Pool } = pkg;
providers: [ providers: [
{ {
provide: PG_CONNECTION, provide: PG_CONNECTION,
inject: [ConfigService], inject: [ConfigService, WINSTON_MODULE_PROVIDER],
useFactory: async (configService: ConfigService) => { useFactory: async (configService: ConfigService, logger: Logger) => {
const connectionString = configService.get<string>('DATABASE_URL'); const connectionString = configService.get<string>('DATABASE_URL');
const pool = new Pool({ const pool = new Pool({
connectionString, connectionString,
// ssl: true, // ssl: true,
}); });
// Definiere einen benutzerdefinierten Logger für Drizzle
const drizzleLogger = {
logQuery(query: string, params: unknown[]): void {
logger.info(query, params);
},
};
return drizzle(pool, { schema, logger: true }); return drizzle(pool, { schema, logger: drizzleLogger });
}, },
}, },
], ],

View File

@ -11,10 +11,9 @@ export class EventController {
@Ip() userIp: string, // IP Adresse des Clients @Ip() userIp: string, // IP Adresse des Clients
@Headers('user-agent') userAgent: string, // User-Agent des Clients @Headers('user-agent') userAgent: string, // User-Agent des Clients
) { ) {
//const { listingId, userId, eventType, locationCountry, locationCity, locationLat, locationLng, referrer, additionalData } = body;
event.userIp = userIp; event.userIp = userIp;
event.userAgent = userAgent; event.userAgent = userAgent;
this.eventService.createEvent(event); await this.eventService.createEvent(event);
return { message: 'Event gespeichert' }; return { message: 'Event gespeichert' };
} }
} }

View File

@ -1,38 +1,22 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { readFileSync } from 'fs';
import fs from 'fs-extra'; import fs from 'fs-extra';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { join } from 'path';
import sharp from 'sharp'; import sharp from 'sharp';
import { Logger } from 'winston'; import { Logger } from 'winston';
import { ImageProperty, Subscription } from '../models/main.model';
@Injectable() @Injectable()
export class FileService { export class FileService {
private subscriptions: any;
constructor(@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) { constructor(@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
this.loadSubscriptions();
fs.ensureDirSync(`./pictures`); fs.ensureDirSync(`./pictures`);
fs.ensureDirSync(`./pictures/profile`); fs.ensureDirSync(`./pictures/profile`);
fs.ensureDirSync(`./pictures/logo`); fs.ensureDirSync(`./pictures/logo`);
fs.ensureDirSync(`./pictures/property`); fs.ensureDirSync(`./pictures/property`);
} }
// ############ // ############
// Subscriptions
// ############
private loadSubscriptions(): void {
const filePath = join(__dirname, '../..', 'assets', 'subscriptions.json');
const rawData = readFileSync(filePath, 'utf8');
this.subscriptions = JSON.parse(rawData);
}
getSubscriptions(): Subscription[] {
return this.subscriptions;
}
// ############
// Profile // Profile
// ############ // ############
async storeProfilePicture(file: Express.Multer.File, adjustedEmail: string) { async storeProfilePicture(file: Express.Multer.File, adjustedEmail: string) {
let quality = 50; const quality = 50;
const output = await sharp(file.buffer) const output = await sharp(file.buffer)
.resize({ width: 300 }) .resize({ width: 300 })
.avif({ quality }) // Verwende AVIF .avif({ quality }) // Verwende AVIF
@ -47,7 +31,7 @@ export class FileService {
// Logo // Logo
// ############ // ############
async storeCompanyLogo(file: Express.Multer.File, adjustedEmail: string) { async storeCompanyLogo(file: Express.Multer.File, adjustedEmail: string) {
let quality = 50; const quality = 50;
const output = await sharp(file.buffer) const output = await sharp(file.buffer)
.resize({ width: 300 }) .resize({ width: 300 })
.avif({ quality }) // Verwende AVIF .avif({ quality }) // Verwende AVIF
@ -76,7 +60,6 @@ export class FileService {
} }
} }
async hasPropertyImages(imagePath: string, serial: string): Promise<boolean> { async hasPropertyImages(imagePath: string, serial: string): Promise<boolean> {
const result: ImageProperty[] = [];
const directory = `./pictures/property/${imagePath}/${serial}`; const directory = `./pictures/property/${imagePath}/${serial}`;
if (fs.existsSync(directory)) { if (fs.existsSync(directory)) {
const files = await fs.readdir(directory); const files = await fs.readdir(directory);
@ -86,7 +69,6 @@ export class FileService {
} }
} }
async storePropertyPicture(file: Express.Multer.File, imagePath: string, serial: string): Promise<string> { async storePropertyPicture(file: Express.Multer.File, imagePath: string, serial: string): Promise<string> {
const suffix = file.mimetype.includes('png') ? 'png' : 'jpg';
const directory = `./pictures/property/${imagePath}/${serial}`; const directory = `./pictures/property/${imagePath}/${serial}`;
fs.ensureDirSync(`${directory}`); fs.ensureDirSync(`${directory}`);
const imageName = await this.getNextImageName(directory); const imageName = await this.getNextImageName(directory);
@ -113,16 +95,15 @@ export class FileService {
} }
} }
async resizeImageToAVIF(buffer: Buffer, maxSize: number, imageName: string, directory: string) { async resizeImageToAVIF(buffer: Buffer, maxSize: number, imageName: string, directory: string) {
let quality = 50; // AVIF kann mit niedrigeren Qualitätsstufen gute Ergebnisse erzielen const quality = 50; // AVIF kann mit niedrigeren Qualitätsstufen gute Ergebnisse erzielen
let output; const start = Date.now();
let start = Date.now(); const output = await sharp(buffer)
output = await sharp(buffer)
.resize({ width: 1500 }) .resize({ width: 1500 })
.avif({ quality }) // Verwende AVIF .avif({ quality }) // Verwende AVIF
//.webp({ quality }) // Verwende Webp //.webp({ quality }) // Verwende Webp
.toBuffer(); .toBuffer();
await sharp(output).toFile(`${directory}/${imageName}.avif`); // Ersetze Dateierweiterung await sharp(output).toFile(`${directory}/${imageName}.avif`); // Ersetze Dateierweiterung
let timeTaken = Date.now() - start; const timeTaken = Date.now() - start;
this.logger.info(`Quality: ${quality} - Time: ${timeTaken} milliseconds`); this.logger.info(`Quality: ${quality} - Time: ${timeTaken} milliseconds`);
} }
deleteImage(path: string) { deleteImage(path: string) {

View File

@ -35,7 +35,7 @@ export class GeoController {
return this.geoService.findCountiesStartingWith(countyRequest.prefix, countyRequest.states); return this.geoService.findCountiesStartingWith(countyRequest.prefix, countyRequest.states);
} }
@Get('ipinfo/georesult/wysiwyg') @Get('ipinfo/georesult/wysiwyg')
fetchIpAndGeoLocation(@RealIp() ipInfo: RealIpInfo): any { async fetchIpAndGeoLocation(@RealIp() ipInfo: RealIpInfo): Promise<any> {
return this.geoService.fetchIpAndGeoLocation(ipInfo); return await this.geoService.fetchIpAndGeoLocation(ipInfo);
} }
} }

View File

@ -31,7 +31,7 @@ export class GeoService {
this.counties.forEach(stateData => { this.counties.forEach(stateData => {
if (!states || states.includes(stateData.state)) { if (!states || states.includes(stateData.state)) {
stateData.counties.forEach(county => { stateData.counties.forEach(county => {
if (county.startsWith(prefix.toUpperCase())) { if (county.startsWith(prefix?.toUpperCase())) {
results.push({ results.push({
id: idCounter++, id: idCounter++,
name: county, name: county,

View File

@ -29,28 +29,6 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
// jwksRequestsPerMinute: 5, // jwksRequestsPerMinute: 5,
jwksUri: `https://auth.bizmatch.net/realms/${realm}/protocol/openid-connect/certs`, jwksUri: `https://auth.bizmatch.net/realms/${realm}/protocol/openid-connect/certs`,
}), }),
// cache: true, // Enable caching of validated tokens
// cacheTTL: 60 * 60 * 1000, // 1 hour cache TTL
// secretOrKeyProvider: (request, rawJwtToken, done) => {
// const start = Date.now();
// const decodedToken = jwt.decode(rawJwtToken, { complete: true });
// const kid = decodedToken?.header?.kid;
// if (pemCache.has(kid)) {
// this.logger.info(`Using cached PEM for kid ${kid}`);
// return done(null, pemCache.get(kid));
// }
// const key = staticCerts.keys.find(k => k.kid === kid);
// if (!key) {
// this.logger.error(`No matching key found for kid: ${kid}`);
// return done(new Error('No matching key found'), null);
// }
// const publicKey = jwkToPem(key);
// pemCache.set(kid, publicKey);
// done(null, publicKey);
// this.logger.info(`Total JWT verification time: ${Date.now() - start}ms`);
// },
audience: 'account', // Keycloak Client ID audience: 'account', // Keycloak Client ID
authorize: '', authorize: '',
issuer: `https://auth.bizmatch.net/realms/${realm}`, issuer: `https://auth.bizmatch.net/realms/${realm}`,
@ -67,7 +45,6 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
throw new UnauthorizedException(); throw new UnauthorizedException();
} }
const result = { userId: payload.sub, firstname: payload.given_name, lastname: payload.family_name, username: payload.preferred_username, roles: payload.realm_access?.roles }; const result = { userId: payload.sub, firstname: payload.given_name, lastname: payload.family_name, username: payload.preferred_username, roles: payload.realm_access?.roles };
this.logger.info(`JWT User: ${JSON.stringify(result)}`); // Debugging: JWT Payload anzeigen
return result; return result;
} }
} }

View File

@ -12,7 +12,7 @@ export class BrokerListingsController {
) {} ) {}
@Post('search') @Post('search')
find(@Body() criteria: UserListingCriteria): any { async find(@Body() criteria: UserListingCriteria): Promise<any> {
return this.userService.searchUserListings(criteria); return await this.userService.searchUserListings(criteria);
} }
} }

View File

@ -280,14 +280,4 @@ export class BusinessListingService {
}) })
.where(sql`${businesses.id} = ${id}`); .where(sql`${businesses.id} = ${id}`);
} }
// ##############################################################
// States
// ##############################################################
// async getStates(): Promise<any[]> {
// return await this.conn
// .select({ state: businesses.state, count: sql<number>`count(${businesses.id})`.mapWith(Number) })
// .from(businesses)
// .groupBy(sql`${businesses.state}`)
// .orderBy(sql`count desc`);
// }
} }

View File

@ -16,48 +16,46 @@ export class BusinessListingsController {
@UseGuards(OptionalJwtAuthGuard) @UseGuards(OptionalJwtAuthGuard)
@Get(':id') @Get(':id')
findById(@Request() req, @Param('id') id: string): any { async findById(@Request() req, @Param('id') id: string): Promise<any> {
return this.listingsService.findBusinessesById(id, req.user as JwtUser); return await this.listingsService.findBusinessesById(id, req.user as JwtUser);
} }
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Get('favorites/all') @Get('favorites/all')
findFavorites(@Request() req): any { async findFavorites(@Request() req): Promise<any> {
return this.listingsService.findFavoriteListings(req.user as JwtUser); return await this.listingsService.findFavoriteListings(req.user as JwtUser);
} }
@UseGuards(OptionalJwtAuthGuard) @UseGuards(OptionalJwtAuthGuard)
@Get('user/:userid') @Get('user/:userid')
findByUserId(@Request() req, @Param('userid') userid: string): Promise<BusinessListing[]> { async findByUserId(@Request() req, @Param('userid') userid: string): Promise<BusinessListing[]> {
return this.listingsService.findBusinessesByEmail(userid, req.user as JwtUser); return await this.listingsService.findBusinessesByEmail(userid, req.user as JwtUser);
} }
@UseGuards(OptionalJwtAuthGuard) @UseGuards(OptionalJwtAuthGuard)
@Post('find') @Post('find')
find(@Request() req, @Body() criteria: BusinessListingCriteria): any { async find(@Request() req, @Body() criteria: BusinessListingCriteria): Promise<any> {
return this.listingsService.searchBusinessListings(criteria, req.user as JwtUser); return await this.listingsService.searchBusinessListings(criteria, req.user as JwtUser);
} }
@UseGuards(OptionalJwtAuthGuard) @UseGuards(OptionalJwtAuthGuard)
@Post('findTotal') @Post('findTotal')
findTotal(@Request() req, @Body() criteria: BusinessListingCriteria): Promise<number> { async findTotal(@Request() req, @Body() criteria: BusinessListingCriteria): Promise<number> {
return this.listingsService.getBusinessListingsCount(criteria, req.user as JwtUser); return await this.listingsService.getBusinessListingsCount(criteria, req.user as JwtUser);
} }
@Post() @Post()
create(@Body() listing: any) { async create(@Body() listing: any) {
this.logger.info(`Save Listing`); return await this.listingsService.createListing(listing);
return this.listingsService.createListing(listing);
} }
@Put() @Put()
update(@Body() listing: any) { async update(@Body() listing: any) {
this.logger.info(`Save Listing`); return await this.listingsService.updateBusinessListing(listing.id, listing);
return this.listingsService.updateBusinessListing(listing.id, listing);
} }
@Delete('listing/:id') @Delete('listing/:id')
deleteById(@Param('id') id: string) { async deleteById(@Param('id') id: string) {
this.listingsService.deleteListing(id); await this.listingsService.deleteListing(id);
} }
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Delete('favorite/:id') @Delete('favorite/:id')
deleteFavorite(@Request() req, @Param('id') id: string) { async deleteFavorite(@Request() req, @Param('id') id: string) {
this.listingsService.deleteFavorite(id, req.user as JwtUser); await this.listingsService.deleteFavorite(id, req.user as JwtUser);
} }
} }

View File

@ -18,18 +18,18 @@ export class CommercialPropertyListingsController {
@UseGuards(OptionalJwtAuthGuard) @UseGuards(OptionalJwtAuthGuard)
@Get(':id') @Get(':id')
findById(@Request() req, @Param('id') id: string): any { async findById(@Request() req, @Param('id') id: string): Promise<any> {
return 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')
findFavorites(@Request() req): any { async findFavorites(@Request() req): Promise<any> {
return 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')
findByEmail(@Request() req, @Param('email') email: string): Promise<CommercialPropertyListing[]> { async findByEmail(@Request() req, @Param('email') email: string): Promise<CommercialPropertyListing[]> {
return 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')
@ -38,27 +38,25 @@ export class CommercialPropertyListingsController {
} }
@UseGuards(OptionalJwtAuthGuard) @UseGuards(OptionalJwtAuthGuard)
@Post('findTotal') @Post('findTotal')
findTotal(@Request() req, @Body() criteria: CommercialPropertyListingCriteria): Promise<number> { async findTotal(@Request() req, @Body() criteria: CommercialPropertyListingCriteria): Promise<number> {
return this.listingsService.getCommercialPropertiesCount(criteria, req.user as JwtUser); return await this.listingsService.getCommercialPropertiesCount(criteria, req.user as JwtUser);
} }
@Post() @Post()
async create(@Body() listing: any) { async create(@Body() listing: any) {
this.logger.info(`Save Listing`);
return await this.listingsService.createListing(listing); return await this.listingsService.createListing(listing);
} }
@Put() @Put()
async update(@Body() listing: any) { async update(@Body() listing: any) {
this.logger.info(`Save Listing`);
return await this.listingsService.updateCommercialPropertyListing(listing.id, listing); return await this.listingsService.updateCommercialPropertyListing(listing.id, listing);
} }
@Delete('listing/:id/:imagePath') @Delete('listing/:id/:imagePath')
deleteById(@Param('id') id: string, @Param('imagePath') imagePath: string) { async deleteById(@Param('id') id: string, @Param('imagePath') imagePath: string) {
this.listingsService.deleteListing(id); await this.listingsService.deleteListing(id);
this.fileService.deleteDirectoryIfExists(imagePath); this.fileService.deleteDirectoryIfExists(imagePath);
} }
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Delete('favorite/:id') @Delete('favorite/:id')
deleteFavorite(@Request() req, @Param('id') id: string) { async deleteFavorite(@Request() req, @Param('id') id: string) {
this.listingsService.deleteFavorite(id, req.user as JwtUser); await this.listingsService.deleteFavorite(id, req.user as JwtUser);
} }
} }

View File

@ -7,19 +7,19 @@ import { MailService } from './mail.service';
export class MailController { export class MailController {
constructor(private mailService: MailService) {} constructor(private mailService: MailService) {}
@Post() @Post()
sendEMail(@Body() mailInfo: MailInfo): Promise<void | ErrorResponse> { async sendEMail(@Body() mailInfo: MailInfo): Promise<void | ErrorResponse> {
if (mailInfo.listing) { if (mailInfo.listing) {
return this.mailService.sendInquiry(mailInfo); return await this.mailService.sendInquiry(mailInfo);
} else { } else {
return this.mailService.sendRequest(mailInfo); return await this.mailService.sendRequest(mailInfo);
} }
} }
@Post('subscriptionConfirmation') @Post('subscriptionConfirmation')
sendSubscriptionConfirmation(@Body() user: User): Promise<void | ErrorResponse> { async sendSubscriptionConfirmation(@Body() user: User): Promise<void | ErrorResponse> {
return this.mailService.sendSubscriptionConfirmation(user); return await this.mailService.sendSubscriptionConfirmation(user);
} }
@Post('send2Friend') @Post('send2Friend')
send2Friend(@Body() shareByEMail: ShareByEMail): Promise<void | ErrorResponse> { async send2Friend(@Body() shareByEMail: ShareByEMail): Promise<void | ErrorResponse> {
return this.mailService.send2Friend(shareByEMail); return await this.mailService.send2Friend(shareByEMail);
} }
} }

View File

@ -1,14 +1,20 @@
import { LoggerService } from '@nestjs/common';
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import bodyParser from 'body-parser'; import bodyParser from 'body-parser';
import express from 'express'; import express from 'express';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
async function bootstrap() { async function bootstrap() {
const server = express(); const server = express();
server.set('trust proxy', true); server.set('trust proxy', true);
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);
// const logger = app.get<Logger>(WINSTON_MODULE_NEST_PROVIDER);
const logger = app.get<LoggerService>(WINSTON_MODULE_NEST_PROVIDER);
app.useLogger(logger);
app.use('/bizmatch/payment/webhook', bodyParser.raw({ type: 'application/json' })); app.use('/bizmatch/payment/webhook', bodyParser.raw({ type: 'application/json' }));
app.setGlobalPrefix('bizmatch'); app.setGlobalPrefix('bizmatch');
app.enableCors({ app.enableCors({
origin: '*', origin: '*',
//origin: 'http://localhost:4200', // Die URL Ihrer Angular-App //origin: 'http://localhost:4200', // Die URL Ihrer Angular-App

View File

@ -17,21 +17,21 @@ export class PaymentController {
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@Get('user/all') @Get('user/all')
async getAllStripeCustomer(): Promise<Stripe.Customer[]> { async getAllStripeCustomer(): Promise<Stripe.Customer[]> {
return 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 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 this.paymentService.getStripePaymentMethod(email); return await this.paymentService.getStripePaymentMethod(email);
} }
@Post('create-checkout-session') @Post('create-checkout-session')
async createCheckoutSession(@Body() checkout: Checkout) { async createCheckoutSession(@Body() checkout: Checkout) {
return this.paymentService.createCheckoutSession(checkout); return await this.paymentService.createCheckoutSession(checkout);
} }
@Post('webhook') @Post('webhook')
async handleWebhook(@Req() req: Request, @Res() res: Response): Promise<void> { async handleWebhook(@Req() req: Request, @Res() res: Response): Promise<void> {

View File

@ -101,7 +101,6 @@ export class PaymentService {
username: keycloakUser.email, username: keycloakUser.email,
roles: [], roles: [],
}); });
this.logger.info(JSON.stringify(session));
user.subscriptionId = session.subscription as string; user.subscriptionId = session.subscription as string;
const subscription = await this.stripe.subscriptions.retrieve(user.subscriptionId); const subscription = await this.stripe.subscriptions.retrieve(user.subscriptionId);
user.customerType = 'professional'; user.customerType = 'professional';

View File

@ -7,19 +7,21 @@ export class RequestDurationMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) { use(req: Request, res: Response, next: NextFunction) {
const start = Date.now(); const start = Date.now();
const clientIp = req.ip;
this.logger.log(`Entering ${req.method} ${req.originalUrl} from ${clientIp}`);
res.on('finish', () => { res.on('finish', () => {
// const duration = Date.now() - start;
// this.logger.log(`${req.method} ${req.url} - ${duration}ms`);
const duration = Date.now() - start; const duration = Date.now() - start;
let logMessage = `${req.method} ${req.url} - ${duration}ms`; let logMessage = `${req.method} ${req.originalUrl} - ${duration}ms - IP: ${clientIp}`;
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 += ` - Body: ${body}`; logMessage += ` - Incoming Body: ${body}`;
} }
this.logger.log(logMessage); this.logger.log(logMessage);
}); });
next(); next();
} }
} }

View File

@ -20,17 +20,13 @@ export class UserController {
@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> {
this.logger.info(`Searching for user with EMail: ${mail}`);
const user = await this.userService.getUserByMail(mail, req.user as JwtUser); const user = await this.userService.getUserByMail(mail, req.user as JwtUser);
this.logger.info(`Found user: ${JSON.stringify(user)}`);
return user; return user;
} }
@Get(':id') @Get(':id')
async findById(@Param('id') id: string): Promise<User> { async findById(@Param('id') id: string): Promise<User> {
this.logger.info(`Searching for user with ID: ${id}`);
const user = await this.userService.getUserById(id); const user = await this.userService.getUserById(id);
this.logger.info(`Found user: ${JSON.stringify(user)}`);
return user; return user;
} }
@UseGuards(AdminAuthGuard) @UseGuards(AdminAuthGuard)
@ -40,10 +36,8 @@ export class UserController {
} }
@Post() @Post()
async save(@Body() user: any): Promise<User> { async save(@Body() user: any): Promise<User> {
this.logger.info(`Saving user: ${JSON.stringify(user)}`);
try { try {
const savedUser = await this.userService.saveUser(user); const savedUser = await this.userService.saveUser(user);
this.logger.info(`User persisted: ${JSON.stringify(savedUser)}`);
return savedUser; return savedUser;
} catch (error) { } catch (error) {
if (error instanceof ZodError) { if (error instanceof ZodError) {
@ -60,27 +54,23 @@ export class UserController {
} }
@Post('guaranteed') @Post('guaranteed')
async saveGuaranteed(@Body() user: any): Promise<User> { async saveGuaranteed(@Body() user: any): Promise<User> {
this.logger.info(`Saving user guaranteed: ${JSON.stringify(user)}`);
const savedUser = await this.userService.saveUser(user, false); const savedUser = await this.userService.saveUser(user, false);
this.logger.info(`User persisted guaranteed: ${JSON.stringify(savedUser)}`);
return savedUser; return savedUser;
} }
@Post('search') @Post('search')
async find(@Body() criteria: UserListingCriteria): Promise<{ results: User[]; totalCount: number }> { async find(@Body() criteria: UserListingCriteria): Promise<{ results: User[]; totalCount: number }> {
this.logger.info(`Searching for users with criteria: ${JSON.stringify(criteria)}`);
const foundUsers = await this.userService.searchUserListings(criteria); const foundUsers = await this.userService.searchUserListings(criteria);
this.logger.info(`Found users: ${JSON.stringify(foundUsers)}`);
return foundUsers; return foundUsers;
} }
@Post('findTotal') @Post('findTotal')
findTotal(@Body() criteria: UserListingCriteria): Promise<number> { async findTotal(@Body() criteria: UserListingCriteria): Promise<number> {
return this.userService.getUserListingsCount(criteria); return await this.userService.getUserListingsCount(criteria);
} }
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Get('subscriptions/:id') @Get('subscriptions/:id')
async findSubscriptionsById(@Param('id') id: string): Promise<Subscription[]> { async findSubscriptionsById(@Param('id') id: string): Promise<Subscription[]> {
const subscriptions = this.fileService.getSubscriptions(); const subscriptions = [];
const user = await this.userService.getUserById(id); const user = await this.userService.getUserById(id);
subscriptions.forEach(s => { subscriptions.forEach(s => {
s.userId = user.id; s.userId = user.id;

View File

@ -100,7 +100,7 @@ export class HeaderComponent {
this.baseRoute = url.split('/')[1]; // Nimmt den ersten Teil der Route nach dem ersten '/' this.baseRoute = url.split('/')[1]; // Nimmt den ersten Teil der Route nach dem ersten '/'
const specialRoutes = [, '', '']; const specialRoutes = [, '', ''];
this.criteria = getCriteriaProxy(this.baseRoute, this); this.criteria = getCriteriaProxy(this.baseRoute, this);
this.searchService.search(this.criteria); // this.searchService.search(this.criteria);
} }
setupSortByOptions() { setupSortByOptions() {
this.sortByOptions = []; this.sortByOptions = [];

View File

@ -2,6 +2,7 @@ import { CommonModule, NgOptimizedImage } from '@angular/common';
import { ChangeDetectorRef, Component } from '@angular/core'; import { ChangeDetectorRef, Component } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { BusinessListing, User } from '../../../../../../bizmatch-server/src/models/db.model'; import { BusinessListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
import { LISTINGS_PER_PAGE, ListingType, UserListingCriteria, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model'; import { LISTINGS_PER_PAGE, ListingType, UserListingCriteria, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../../../environments/environment'; import { environment } from '../../../../environments/environment';
@ -13,7 +14,7 @@ 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 { getCriteriaStateObject } from '../../../utils/utils';
@UntilDestroy()
@Component({ @Component({
selector: 'app-broker-listings', selector: 'app-broker-listings',
standalone: true, standalone: true,
@ -55,7 +56,7 @@ export class BrokerListingsComponent {
) { ) {
this.criteria = getCriteriaStateObject('brokerListings'); this.criteria = getCriteriaStateObject('brokerListings');
this.init(); this.init();
this.searchService.currentCriteria.subscribe(criteria => { this.searchService.currentCriteria.pipe(untilDestroyed(this)).subscribe(criteria => {
if (criteria && criteria.criteriaType === 'brokerListings') { if (criteria && criteria.criteriaType === 'brokerListings') {
this.criteria = criteria as UserListingCriteria; this.criteria = criteria as UserListingCriteria;
this.search(); this.search();

View File

@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
import { ChangeDetectorRef, Component } from '@angular/core'; import { ChangeDetectorRef, Component } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { BusinessListing } from '../../../../../../bizmatch-server/src/models/db.model'; import { BusinessListing } from '../../../../../../bizmatch-server/src/models/db.model';
import { BusinessListingCriteria, LISTINGS_PER_PAGE, ListingType, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model'; import { BusinessListingCriteria, LISTINGS_PER_PAGE, ListingType, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
@ -12,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 { getCriteriaStateObject } from '../../../utils/utils'; import { getCriteriaStateObject } from '../../../utils/utils';
@UntilDestroy()
@Component({ @Component({
selector: 'app-business-listings', selector: 'app-business-listings',
standalone: true, standalone: true,
@ -51,7 +52,7 @@ export class BusinessListingsComponent {
) { ) {
this.criteria = getCriteriaStateObject('businessListings'); this.criteria = getCriteriaStateObject('businessListings');
this.init(); this.init();
this.searchService.currentCriteria.subscribe(criteria => { this.searchService.currentCriteria.pipe(untilDestroyed(this)).subscribe(criteria => {
if (criteria && criteria.criteriaType === 'businessListings') { if (criteria && criteria.criteriaType === 'businessListings') {
this.criteria = criteria as BusinessListingCriteria; this.criteria = criteria as BusinessListingCriteria;
this.search(); this.search();

View File

@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
import { ChangeDetectorRef, Component } from '@angular/core'; import { ChangeDetectorRef, Component } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { CommercialPropertyListing } from '../../../../../../bizmatch-server/src/models/db.model'; import { CommercialPropertyListing } from '../../../../../../bizmatch-server/src/models/db.model';
import { CommercialPropertyListingCriteria, LISTINGS_PER_PAGE, ResponseCommercialPropertyListingArray } from '../../../../../../bizmatch-server/src/models/main.model'; import { CommercialPropertyListingCriteria, LISTINGS_PER_PAGE, ResponseCommercialPropertyListingArray } from '../../../../../../bizmatch-server/src/models/main.model';
@ -12,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 { getCriteriaStateObject } from '../../../utils/utils'; import { getCriteriaStateObject } from '../../../utils/utils';
@UntilDestroy()
@Component({ @Component({
selector: 'app-commercial-property-listings', selector: 'app-commercial-property-listings',
standalone: true, standalone: true,
@ -50,7 +51,7 @@ export class CommercialPropertyListingsComponent {
) { ) {
this.criteria = getCriteriaStateObject('commercialPropertyListings'); this.criteria = getCriteriaStateObject('commercialPropertyListings');
this.init(); this.init();
this.searchService.currentCriteria.subscribe(criteria => { this.searchService.currentCriteria.pipe(untilDestroyed(this)).subscribe(criteria => {
if (criteria && criteria.criteriaType === 'commercialPropertyListings') { if (criteria && criteria.criteriaType === 'commercialPropertyListings') {
this.criteria = criteria as CommercialPropertyListingCriteria; this.criteria = criteria as CommercialPropertyListingCriteria;
this.search(); this.search();