From c2515898858a0fd37276e45b37decedb72d8602f Mon Sep 17 00:00:00 2001 From: Marek Lesko Date: Fri, 1 Aug 2025 09:51:22 +0200 Subject: [PATCH] feat: implement API endpoint interceptor and update app configuration #5 --- Web/src/app/app.config.ts | 20 ++++++++++++------- Web/src/app/app.ts | 1 - Web/src/app/content/content.ts | 9 ++++----- Web/src/app/services/config.service.ts | 22 ++++++++++++++++----- Web/src/app/services/http.interceptor.ts | 25 ++++++++++++++++++++++++ 5 files changed, 59 insertions(+), 18 deletions(-) create mode 100644 Web/src/app/services/http.interceptor.ts diff --git a/Web/src/app/app.config.ts b/Web/src/app/app.config.ts index 6827d46..f8fb9e1 100644 --- a/Web/src/app/app.config.ts +++ b/Web/src/app/app.config.ts @@ -2,9 +2,10 @@ import { ApplicationConfig, inject, provideAppInitializer, provideBrowserGlobalE import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; -import { HTTP_INTERCEPTORS, provideHttpClient } from '@angular/common/http'; +import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; import { DefaultOAuthInterceptor, provideOAuthClient } from 'angular-oauth2-oidc'; import { AppConfigService } from './services/config.service'; +import { ApiEndpointInterceptor } from './services/http.interceptor'; export const appConfig: ApplicationConfig = { providers: [ @@ -12,17 +13,22 @@ export const appConfig: ApplicationConfig = { provideZonelessChangeDetection(), provideRouter(routes), provideAppInitializer(() => inject(AppConfigService).loadConfig()), - provideHttpClient(), + { + provide: HTTP_INTERCEPTORS, + useClass: DefaultOAuthInterceptor, + multi: true, + }, + { + provide: HTTP_INTERCEPTORS, + useClass: ApiEndpointInterceptor, + multi: true, + }, + provideHttpClient(withInterceptorsFromDi()), provideOAuthClient({ resourceServer: { allowedUrls: ['http://localhost:5000', 'https://localhost:5001', 'https://centrum.lesko.me'], sendAccessToken: true, }, }), - { - provide: HTTP_INTERCEPTORS, - useClass: DefaultOAuthInterceptor, - multi: true, - } ] }; diff --git a/Web/src/app/app.ts b/Web/src/app/app.ts index fb52337..c1044df 100644 --- a/Web/src/app/app.ts +++ b/Web/src/app/app.ts @@ -22,7 +22,6 @@ export class App implements OnInit { clientId: '21131567-fea1-42a2-8907-21abd874eff8', scope: 'openid profile email', responseType: 'code', - showDebugInformation: true, timeoutFactor: 0.01, }); } diff --git a/Web/src/app/content/content.ts b/Web/src/app/content/content.ts index c74aaf3..58c9684 100644 --- a/Web/src/app/content/content.ts +++ b/Web/src/app/content/content.ts @@ -14,10 +14,9 @@ export class Content { data = signal({}); constructor(httpClient: HttpClient, readonly as: OAuthService, readonly cs: AppConfigService) { - httpClient.get((isDevMode() ? cs.setting.apiEndpoint : '') + '/api/product', { - headers: new HttpHeaders({ Authorization: `Bearer ${as.getAccessToken()}` }).append('Content-Type', 'application/json') - }).subscribe(data => { - this.data.set(data); - }); + httpClient.get('/api/product') + .subscribe(data => { + this.data.set(data); + }); } } diff --git a/Web/src/app/services/config.service.ts b/Web/src/app/services/config.service.ts index ea23318..7182880 100644 --- a/Web/src/app/services/config.service.ts +++ b/Web/src/app/services/config.service.ts @@ -1,16 +1,28 @@ import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { Observable, tap } from 'rxjs'; +import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class AppConfigService { private config: any; - constructor(private readonly http: HttpClient) { } + public loaded: boolean = false; + + constructor() { } loadConfig(): Observable { - return this.http.get('/config.json') - .pipe(tap(data => this.config = data)) + return new Observable(observer => { + fetch('/config.json') + .then(async response => { + if (!response.ok) { + throw new Error(`Could not load config.json: ${response.statusText}`); + } + this.config = await response.json(); + this.loaded = true; + observer.next(this.config); + observer.complete(); + }) + .catch(error => observer.error(error)); + }); } get setting() { diff --git a/Web/src/app/services/http.interceptor.ts b/Web/src/app/services/http.interceptor.ts new file mode 100644 index 0000000..63a7a22 --- /dev/null +++ b/Web/src/app/services/http.interceptor.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@angular/core'; +import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { AppConfigService } from './config.service'; + +@Injectable({ providedIn: 'root' }) +export class ApiEndpointInterceptor implements HttpInterceptor { + constructor(private readonly configService: AppConfigService) { } + + intercept(req: HttpRequest, next: HttpHandler): Observable> { + return this.prependUrl(req, next); + } + + prependUrl(req: HttpRequest, next: HttpHandler): Observable> { + const apiEndpoint = this.configService.setting.apiEndpoint; + if (apiEndpoint === undefined || !this.configService.loaded) + throw new Error('API endpoint is not defined or config not loaded'); + + const isRelative = !/^https?:\/\//i.test(req.url); + const url = isRelative ? `${apiEndpoint.replace(/\/$/, '')}/${req.url.replace(/^\//, '')}` : req.url; + const cloned = req.clone({ url }); + console.trace('ApiEndpointInterceptor hit:', url); + return next.handle(cloned); + } +}