feat: implement API endpoint interceptor and update app configuration #5

This commit is contained in:
Marek Lesko
2025-08-01 09:51:22 +02:00
parent 7902cc4dcf
commit c251589885
5 changed files with 59 additions and 18 deletions

View File

@@ -2,9 +2,10 @@ import { ApplicationConfig, inject, provideAppInitializer, provideBrowserGlobalE
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { routes } from './app.routes'; 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 { DefaultOAuthInterceptor, provideOAuthClient } from 'angular-oauth2-oidc';
import { AppConfigService } from './services/config.service'; import { AppConfigService } from './services/config.service';
import { ApiEndpointInterceptor } from './services/http.interceptor';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
@@ -12,17 +13,22 @@ export const appConfig: ApplicationConfig = {
provideZonelessChangeDetection(), provideZonelessChangeDetection(),
provideRouter(routes), provideRouter(routes),
provideAppInitializer(() => inject(AppConfigService).loadConfig()), provideAppInitializer(() => inject(AppConfigService).loadConfig()),
provideHttpClient(), {
provide: HTTP_INTERCEPTORS,
useClass: DefaultOAuthInterceptor,
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: ApiEndpointInterceptor,
multi: true,
},
provideHttpClient(withInterceptorsFromDi()),
provideOAuthClient({ provideOAuthClient({
resourceServer: { resourceServer: {
allowedUrls: ['http://localhost:5000', 'https://localhost:5001', 'https://centrum.lesko.me'], allowedUrls: ['http://localhost:5000', 'https://localhost:5001', 'https://centrum.lesko.me'],
sendAccessToken: true, sendAccessToken: true,
}, },
}), }),
{
provide: HTTP_INTERCEPTORS,
useClass: DefaultOAuthInterceptor,
multi: true,
}
] ]
}; };

View File

@@ -22,7 +22,6 @@ export class App implements OnInit {
clientId: '21131567-fea1-42a2-8907-21abd874eff8', clientId: '21131567-fea1-42a2-8907-21abd874eff8',
scope: 'openid profile email', scope: 'openid profile email',
responseType: 'code', responseType: 'code',
showDebugInformation: true,
timeoutFactor: 0.01, timeoutFactor: 0.01,
}); });
} }

View File

@@ -14,10 +14,9 @@ export class Content {
data = signal({}); data = signal({});
constructor(httpClient: HttpClient, readonly as: OAuthService, readonly cs: AppConfigService) { constructor(httpClient: HttpClient, readonly as: OAuthService, readonly cs: AppConfigService) {
httpClient.get((isDevMode() ? cs.setting.apiEndpoint : '') + '/api/product', { httpClient.get('/api/product')
headers: new HttpHeaders({ Authorization: `Bearer ${as.getAccessToken()}` }).append('Content-Type', 'application/json') .subscribe(data => {
}).subscribe(data => { this.data.set(data);
this.data.set(data); });
});
} }
} }

View File

@@ -1,16 +1,28 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs';
import { Observable, tap } from 'rxjs';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class AppConfigService { export class AppConfigService {
private config: any; private config: any;
constructor(private readonly http: HttpClient) { } public loaded: boolean = false;
constructor() { }
loadConfig(): Observable<any> { loadConfig(): Observable<any> {
return this.http.get('/config.json') return new Observable(observer => {
.pipe(tap(data => this.config = data)) 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() { get setting() {

View File

@@ -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<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return this.prependUrl(req, next);
}
prependUrl(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
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);
}
}