From 60866473f702458f5989be9249dc8a205d2dcc6e Mon Sep 17 00:00:00 2001 From: Andreas Knuth Date: Wed, 11 Sep 2024 16:51:42 +0200 Subject: [PATCH] BugFixes: #115, #114, #113. #112 --- bizmatch-server/src/image/image.controller.ts | 9 +- .../src/listings/business-listing.service.ts | 3 - .../listings/business-listings.controller.ts | 8 +- ...commercial-property-listings.controller.ts | 8 +- bizmatch-server/src/mail/mail.service.ts | 4 +- bizmatch-server/src/models/db.model.ts | 4 +- bizmatch/src/app/app.component.ts | 38 ++++++-- bizmatch/src/app/app.config.ts | 94 +++++++++---------- .../app/components/email/email.component.html | 4 +- .../components/tooltip/tooltip.component.html | 2 +- .../details-business-listing.component.ts | 8 +- ...s-commercial-property-listing.component.ts | 8 +- .../src/app/services/globalErrorHandler.ts | 24 +++++ .../services/keycloak-initializer.service.ts | 32 +------ bizmatch/src/app/services/listings.service.ts | 6 +- 15 files changed, 135 insertions(+), 117 deletions(-) create mode 100644 bizmatch/src/app/services/globalErrorHandler.ts diff --git a/bizmatch-server/src/image/image.controller.ts b/bizmatch-server/src/image/image.controller.ts index e0117b6..93a4233 100644 --- a/bizmatch-server/src/image/image.controller.ts +++ b/bizmatch-server/src/image/image.controller.ts @@ -1,6 +1,7 @@ -import { Controller, Delete, Inject, Param, Post, UploadedFile, UseInterceptors } from '@nestjs/common'; +import { Controller, Delete, Inject, Param, Post, UploadedFile, UseGuards, UseInterceptors } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; +import { JwtAuthGuard } from 'src/jwt-auth/jwt-auth.guard'; import { Logger } from 'winston'; import { FileService } from '../file/file.service'; import { CommercialPropertyService } from '../listings/commercial-property.service'; @@ -17,12 +18,14 @@ export class ImageController { // ############ // Property // ############ + @UseGuards(JwtAuthGuard) @Post('uploadPropertyPicture/:imagePath/:serial') @UseInterceptors(FileInterceptor('file')) async uploadPropertyPicture(@UploadedFile() file: Express.Multer.File, @Param('imagePath') imagePath: string, @Param('serial') serial: string) { const imagename = await this.fileService.storePropertyPicture(file, imagePath, serial); await this.listingService.addImage(imagePath, serial, imagename); } + @UseGuards(JwtAuthGuard) @Delete('propertyPicture/:imagePath/:serial/:imagename') async deletePropertyImagesById(@Param('imagePath') imagePath: string, @Param('serial') serial: string, @Param('imagename') imagename: string): Promise { this.fileService.deleteImage(`pictures/property/${imagePath}/${serial}/${imagename}`); @@ -31,11 +34,13 @@ export class ImageController { // ############ // Profile // ############ + @UseGuards(JwtAuthGuard) @Post('uploadProfile/:email') @UseInterceptors(FileInterceptor('file')) async uploadProfile(@UploadedFile() file: Express.Multer.File, @Param('email') adjustedEmail: string) { await this.fileService.storeProfilePicture(file, adjustedEmail); } + @UseGuards(JwtAuthGuard) @Delete('profile/:email/') async deleteProfileImagesById(@Param('email') email: string): Promise { this.fileService.deleteImage(`pictures/profile/${email}.avif`); @@ -43,11 +48,13 @@ export class ImageController { // ############ // Logo // ############ + @UseGuards(JwtAuthGuard) @Post('uploadCompanyLogo/:email') @UseInterceptors(FileInterceptor('file')) async uploadCompanyLogo(@UploadedFile() file: Express.Multer.File, @Param('email') adjustedEmail: string) { await this.fileService.storeCompanyLogo(file, adjustedEmail); } + @UseGuards(JwtAuthGuard) @Delete('logo/:email/') async deleteLogoImagesById(@Param('email') adjustedEmail: string): Promise { this.fileService.deleteImage(`pictures/logo/${adjustedEmail}.avif`); diff --git a/bizmatch-server/src/listings/business-listing.service.ts b/bizmatch-server/src/listings/business-listing.service.ts index 050b9f9..64b5431 100644 --- a/bizmatch-server/src/listings/business-listing.service.ts +++ b/bizmatch-server/src/listings/business-listing.service.ts @@ -103,9 +103,6 @@ export class BusinessListingService { whereConditions.push(and(ilike(schema.users.firstname, `%${firstname}%`), ilike(schema.users.lastname, `%${lastname}%`))); } } - // if (criteria.brokerName) { - // whereConditions.push(or(ilike(schema.users.firstname, `%${criteria.brokerName}%`), ilike(schema.users.lastname, `%${criteria.brokerName}%`))); - // } if (!user?.roles?.includes('ADMIN') ?? false) { whereConditions.push(or(eq(businesses.email, user?.username), ne(businesses.draft, true))); } diff --git a/bizmatch-server/src/listings/business-listings.controller.ts b/bizmatch-server/src/listings/business-listings.controller.ts index 3150060..8b26e30 100644 --- a/bizmatch-server/src/listings/business-listings.controller.ts +++ b/bizmatch-server/src/listings/business-listings.controller.ts @@ -51,16 +51,12 @@ export class BusinessListingsController { this.logger.info(`Save Listing`); return this.listingsService.updateBusinessListing(listing.id, listing); } - @Delete(':id') + @Delete('listing/:id') deleteById(@Param('id') id: string) { this.listingsService.deleteListing(id); } - // @Get('states/all') - // getStates(): any { - // return this.listingsService.getStates(); - // } @UseGuards(JwtAuthGuard) - @Delete('favorites/:id') + @Delete('favorite/:id') deleteFavorite(@Request() req, @Param('id') id: string) { this.listingsService.deleteFavorite(id, req.user as JwtUser); } diff --git a/bizmatch-server/src/listings/commercial-property-listings.controller.ts b/bizmatch-server/src/listings/commercial-property-listings.controller.ts index 49707d6..8c39d1d 100644 --- a/bizmatch-server/src/listings/commercial-property-listings.controller.ts +++ b/bizmatch-server/src/listings/commercial-property-listings.controller.ts @@ -41,10 +41,6 @@ export class CommercialPropertyListingsController { findTotal(@Request() req, @Body() criteria: CommercialPropertyListingCriteria): Promise { return this.listingsService.getCommercialPropertiesCount(criteria, req.user as JwtUser); } - // @Get('states/all') - // getStates(): any { - // return this.listingsService.getStates(); - // } @Post() async create(@Body() listing: any) { this.logger.info(`Save Listing`); @@ -55,13 +51,13 @@ export class CommercialPropertyListingsController { this.logger.info(`Save Listing`); return await this.listingsService.updateCommercialPropertyListing(listing.id, listing); } - @Delete(':id/:imagePath') + @Delete('listing/:id/:imagePath') deleteById(@Param('id') id: string, @Param('imagePath') imagePath: string) { this.listingsService.deleteListing(id); this.fileService.deleteDirectoryIfExists(imagePath); } @UseGuards(JwtAuthGuard) - @Delete('favorites/:id') + @Delete('favorite/:id') deleteFavorite(@Request() req, @Param('id') id: string) { this.listingsService.deleteFavorite(id, req.user as JwtUser); } diff --git a/bizmatch-server/src/mail/mail.service.ts b/bizmatch-server/src/mail/mail.service.ts index 288fdda..7a147af 100644 --- a/bizmatch-server/src/mail/mail.service.ts +++ b/bizmatch-server/src/mail/mail.service.ts @@ -115,8 +115,8 @@ export class MailService { //template: './inquiry', // `.hbs` extension is appended automatically template: join(__dirname, '../..', 'mail/templates/send2Friend.hbs'), context: { - name: shareByEMail.name, - email: shareByEMail.email, + name: shareByEMail.yourName, + email: shareByEMail.yourEmail, listingTitle: shareByEMail.listingTitle, url: shareByEMail.url, id: shareByEMail.id, diff --git a/bizmatch-server/src/models/db.model.ts b/bizmatch-server/src/models/db.model.ts index a25d513..f6741b7 100644 --- a/bizmatch-server/src/models/db.model.ts +++ b/bizmatch-server/src/models/db.model.ts @@ -307,9 +307,9 @@ export const SenderSchema = z.object({ }); export type Sender = z.infer; export const ShareByEMailSchema = z.object({ - name: z.string().min(6, { message: 'Name must be at least 6 characters long' }), + yourName: z.string().min(6, { message: 'Name must be at least 6 characters long' }), recipientEmail: z.string().email({ message: 'Invalid email address' }), - email: z.string().email({ message: 'Invalid email address' }), + yourEmail: z.string().email({ message: 'Invalid email address' }), listingTitle: z.string().optional().nullable(), url: z.string().url({ message: 'Invalid URL format' }).optional().nullable(), id: z.string().optional().nullable(), diff --git a/bizmatch/src/app/app.component.ts b/bizmatch/src/app/app.component.ts index abaad0b..bf0fb53 100644 --- a/bizmatch/src/app/app.component.ts +++ b/bizmatch/src/app/app.component.ts @@ -1,7 +1,7 @@ import { CommonModule } from '@angular/common'; import { Component, HostListener } from '@angular/core'; import { ActivatedRoute, NavigationEnd, Router, RouterOutlet } from '@angular/router'; -import { KeycloakService } from 'keycloak-angular'; +import { KeycloakEventType, KeycloakService } from 'keycloak-angular'; import { filter } from 'rxjs/operators'; import build from '../build'; @@ -45,14 +45,38 @@ export class AppComponent { this.actualRoute = currentRoute.snapshot.url[0].path; }); } - ngOnInit() {} + ngOnInit() { + // Überwache Keycloak-Events, um den Token-Refresh zu kontrollieren + this.keycloakService.keycloakEvents$.subscribe({ + next: event => { + if (event.type === KeycloakEventType.OnTokenExpired) { + // Wenn der Token abgelaufen ist, versuchen wir einen Refresh + this.handleTokenExpiration(); + } + }, + }); + } + private async handleTokenExpiration(): Promise { + try { + // Versuche, den Token zu erneuern + const refreshed = await this.keycloakService.updateToken(); + if (!refreshed) { + // Wenn der Token nicht erneuert werden kann, leite zur Login-Seite weiter + this.keycloakService.login({ + redirectUri: window.location.href, // oder eine andere Seite + }); + } + } catch (error) { + if (error.error === 'invalid_grant' && error.error_description === 'Token is not active') { + // Hier wird der Fehler "invalid_grant" abgefangen + this.keycloakService.login({ + redirectUri: window.location.href, + }); + } + } + } @HostListener('window:keydown', ['$event']) handleKeyboardEvent(event: KeyboardEvent) { - // this.router.events.subscribe(event => { - // if (event instanceof NavigationEnd) { - // initFlowbite(); - // } - // }); if (event.shiftKey && event.ctrlKey && event.key === 'V') { this.showVersionDialog(); } diff --git a/bizmatch/src/app/app.config.ts b/bizmatch/src/app/app.config.ts index 6fb18a6..894734a 100644 --- a/bizmatch/src/app/app.config.ts +++ b/bizmatch/src/app/app.config.ts @@ -1,4 +1,4 @@ -import { APP_INITIALIZER, ApplicationConfig } from '@angular/core'; +import { APP_INITIALIZER, ApplicationConfig, ErrorHandler } from '@angular/core'; import { provideRouter, withEnabledBlockingInitialNavigation, withInMemoryScrolling } from '@angular/router'; import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; @@ -9,11 +9,10 @@ import { provideQuillConfig } from 'ngx-quill'; import { provideShareButtonsOptions, SharerMethods, withConfig } from 'ngx-sharebuttons'; import { shareIcons } from 'ngx-sharebuttons/icons'; import { provideNgxStripe } from 'ngx-stripe'; -import { environment } from '../environments/environment'; -import { customKeycloakAdapter } from '../keycloak'; import { routes } from './app.routes'; import { LoadingInterceptor } from './interceptors/loading.interceptor'; import { TimeoutInterceptor } from './interceptors/timeout.interceptor'; +import { GlobalErrorHandler } from './services/globalErrorHandler'; import { KeycloakInitializerService } from './services/keycloak-initializer.service'; import { SelectOptionsService } from './services/select-options.service'; import { createLogger } from './utils/utils'; @@ -25,9 +24,9 @@ export const appConfig: ApplicationConfig = { { provide: KeycloakService }, { provide: APP_INITIALIZER, - // useFactory: initializeKeycloak, - //useFactory: initializeKeycloak, - useFactory: initializeKeycloak3, + // useFactory: initializeKeycloak1, + //useFactory: initializeKeycloak2, + useFactory: initializeKeycloak, multi: true, //deps: [KeycloakService], deps: [KeycloakInitializerService], @@ -64,6 +63,7 @@ export const appConfig: ApplicationConfig = { imageSize: 'cover', } as GalleryConfig, }, + { provide: ErrorHandler, useClass: GlobalErrorHandler }, // Registriere den globalen ErrorHandler provideShareButtonsOptions( shareIcons(), withConfig({ @@ -100,47 +100,47 @@ function initServices(selectOptions: SelectOptionsService) { await selectOptions.init(); }; } -export function initializeKeycloak3(keycloak: KeycloakInitializerService) { +export function initializeKeycloak(keycloak: KeycloakInitializerService) { return () => keycloak.initialize(); } -export function initializeKeycloak2(keycloak: KeycloakService): () => Promise { - return async () => { - const { url, realm, clientId } = environment.keycloak; - const adapter = customKeycloakAdapter(() => keycloak.getKeycloakInstance(), {}); - if (window.location.search.length > 0) { - sessionStorage.setItem('SEARCH', window.location.search); - } - const { host, hostname, href, origin, pathname, port, protocol, search } = window.location; - await keycloak.init({ - config: { url, realm, clientId }, - initOptions: { - onLoad: 'check-sso', - silentCheckSsoRedirectUri: window.location.hostname === 'localhost' ? `${window.location.origin}/assets/silent-check-sso.html` : `${window.location.origin}/dealerweb/assets/silent-check-sso.html`, - adapter, - redirectUri: `${origin}${pathname}`, - }, - }); - }; -} -function initializeKeycloak(keycloak: KeycloakService) { - return async () => { - logger.info(`###>calling keycloakService init ...`); - const authenticated = await keycloak.init({ - config: { - url: environment.keycloak.url, - realm: environment.keycloak.realm, - clientId: environment.keycloak.clientId, - }, - initOptions: { - onLoad: 'check-sso', - silentCheckSsoRedirectUri: (window).location.origin + '/assets/silent-check-sso.html', - }, - bearerExcludedUrls: ['/assets'], - shouldUpdateToken(request) { - return !request.headers.get('token-update') === false; - }, - }); - logger.info(`+++>${authenticated}`); - }; -} +// export function initializeKeycloak1(keycloak: KeycloakService): () => Promise { +// return async () => { +// const { url, realm, clientId } = environment.keycloak; +// const adapter = customKeycloakAdapter(() => keycloak.getKeycloakInstance(), {}); +// if (window.location.search.length > 0) { +// sessionStorage.setItem('SEARCH', window.location.search); +// } +// const { host, hostname, href, origin, pathname, port, protocol, search } = window.location; +// await keycloak.init({ +// config: { url, realm, clientId }, +// initOptions: { +// onLoad: 'check-sso', +// silentCheckSsoRedirectUri: window.location.hostname === 'localhost' ? `${window.location.origin}/assets/silent-check-sso.html` : `${window.location.origin}/dealerweb/assets/silent-check-sso.html`, +// adapter, +// redirectUri: `${origin}${pathname}`, +// }, +// }); +// }; +// } +// function initializeKeycloak2(keycloak: KeycloakService) { +// return async () => { +// logger.info(`###>calling keycloakService init ...`); +// const authenticated = await keycloak.init({ +// config: { +// url: environment.keycloak.url, +// realm: environment.keycloak.realm, +// clientId: environment.keycloak.clientId, +// }, +// initOptions: { +// onLoad: 'check-sso', +// silentCheckSsoRedirectUri: (window).location.origin + '/assets/silent-check-sso.html', +// }, +// bearerExcludedUrls: ['/assets'], +// shouldUpdateToken(request) { +// return !request.headers.get('token-update') === false; +// }, +// }); +// logger.info(`+++>${authenticated}`); +// }; +// } diff --git a/bizmatch/src/app/components/email/email.component.html b/bizmatch/src/app/components/email/email.component.html index 9df7a17..a68bcbb 100644 --- a/bizmatch/src/app/components/email/email.component.html +++ b/bizmatch/src/app/components/email/email.component.html @@ -21,10 +21,10 @@
- +
- +
diff --git a/bizmatch/src/app/components/tooltip/tooltip.component.html b/bizmatch/src/app/components/tooltip/tooltip.component.html index d72cebb..cdb5a62 100644 --- a/bizmatch/src/app/components/tooltip/tooltip.component.html +++ b/bizmatch/src/app/components/tooltip/tooltip.component.html @@ -1,7 +1,7 @@