From 50a83f784d328d9b819d1f72c68f759efced1d26 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Tue, 24 Aug 2021 13:43:03 +0200 Subject: [PATCH] feat(login): Now adding orgnr to all API requests. (TV-399) Squashed commit of the following: commit b0cc0cf07a4eeaf85c8fdfc111fee1898fa14185 Merge: be9d909 59ce393 Author: Erik Tiekstra Date: Tue Aug 24 13:42:09 2021 +0200 Merged develop and fixed conflicts commit be9d909232326eb06221336a966fc40c7c88289d Author: Erik Tiekstra Date: Tue Aug 24 08:02:12 2021 +0200 Updated auth guard to remove localstorage data when user is not logged in commit a4a182f565689a44e612b9353ae46514c1b439c7 Author: Erik Tiekstra Date: Mon Aug 23 15:08:00 2021 +0200 Updated organization functionality to check if organization matches users organizations commit c170245c2799118bbf7961e95d507885a0571de6 Author: Erik Tiekstra Date: Fri Aug 20 14:34:33 2021 +0200 Now saving organization instead of organization number commit 7c19600f712f48c9c56ba797e4e281a82adcf72f Author: Erik Tiekstra Date: Fri Aug 20 13:43:27 2021 +0200 Removed all headers from API requests from services commit 7c243bafc63f0544e11f1fa8729a139615cb14c0 Author: Erik Tiekstra Date: Fri Aug 20 13:18:19 2021 +0200 Dynamically adding orgnr to interceptor --- .../src/app/app-routing.module.ts | 2 + apps/mina-sidor-fa/src/app/app.module.ts | 4 +- .../organization-picker.component.html | 6 +- .../organization-picker.component.ts | 43 ++--- .../components/layout/layout.component.ts | 4 +- .../shared/constants/local-storage-keys.ts | 10 +- .../src/app/shared/guards/auth.guard.ts | 26 +-- .../shared/interceptors/auth.interceptor.ts | 31 ++++ .../shared/services/api/auth.interceptor.ts | 23 --- .../services/api/authentication.service.ts | 33 ++-- .../services/api/authorizations.service.ts | 6 +- .../shared/services/api/avrop-api.service.ts | 24 +-- .../shared/services/api/deltagare.service.ts | 159 ++++++++---------- .../shared/services/api/employee.service.ts | 52 +++--- .../services/api/participants.service.ts | 8 +- .../shared/services/api/service.service.ts | 6 +- .../app/shared/services/api/tjanst.service.ts | 6 +- .../app/shared/services/api/user.service.ts | 69 ++++---- .../src/environments/environment.api.ts | 4 +- 19 files changed, 242 insertions(+), 274 deletions(-) create mode 100644 apps/mina-sidor-fa/src/app/shared/interceptors/auth.interceptor.ts delete mode 100644 apps/mina-sidor-fa/src/app/shared/services/api/auth.interceptor.ts diff --git a/apps/mina-sidor-fa/src/app/app-routing.module.ts b/apps/mina-sidor-fa/src/app/app-routing.module.ts index d8e9134..061437f 100644 --- a/apps/mina-sidor-fa/src/app/app-routing.module.ts +++ b/apps/mina-sidor-fa/src/app/app-routing.module.ts @@ -56,6 +56,7 @@ const routes: Routes = [ path: 'logga-ut', data: { title: 'Logga ut' }, loadChildren: () => import('./pages/logout/logout.module').then(m => m.LogoutModule), + canActivate: [AuthGuard], }, { path: 'organization-picker', @@ -66,6 +67,7 @@ const routes: Routes = [ path: 'mitt-konto', data: { title: 'Mitt konto' }, loadChildren: () => import('./pages/my-account/my-account.module').then(m => m.MyAccountModule), + canActivate: [AuthGuard], }, ]; diff --git a/apps/mina-sidor-fa/src/app/app.module.ts b/apps/mina-sidor-fa/src/app/app.module.ts index 1dd9ff5..02e7667 100644 --- a/apps/mina-sidor-fa/src/app/app.module.ts +++ b/apps/mina-sidor-fa/src/app/app.module.ts @@ -1,9 +1,8 @@ import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { ErrorHandler, NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; -import { AuthGuard } from '@msfa-guards/auth.guard'; +import { AuthInterceptor } from '@msfa-interceptors/auth.interceptor'; import { CustomErrorHandler } from '@msfa-interceptors/custom-error-handler.module'; -import { AuthInterceptor } from '@msfa-services/api/auth.interceptor'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { ToastListModule } from './components/toast-list/toast-list.module'; @@ -18,7 +17,6 @@ import { AvropModule } from './pages/avrop/avrop.module'; useClass: CustomErrorHandler, }, { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }, - AuthGuard, ], bootstrap: [AppComponent], }) diff --git a/apps/mina-sidor-fa/src/app/pages/organization-picker/organization-picker.component.html b/apps/mina-sidor-fa/src/app/pages/organization-picker/organization-picker.component.html index 9b2b951..d4db8d5 100644 --- a/apps/mina-sidor-fa/src/app/pages/organization-picker/organization-picker.component.html +++ b/apps/mina-sidor-fa/src/app/pages/organization-picker/organization-picker.component.html @@ -6,11 +6,11 @@ att logga ut applikationen och logga in på nytt.

- + diff --git a/apps/mina-sidor-fa/src/app/pages/organization-picker/organization-picker.component.ts b/apps/mina-sidor-fa/src/app/pages/organization-picker/organization-picker.component.ts index 4014a53..0281035 100644 --- a/apps/mina-sidor-fa/src/app/pages/organization-picker/organization-picker.component.ts +++ b/apps/mina-sidor-fa/src/app/pages/organization-picker/organization-picker.component.ts @@ -1,10 +1,10 @@ -import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { Router } from '@angular/router'; import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive'; import { Organization } from '@msfa-models/organization.model'; import { UserService } from '@msfa-services/api/user.service'; - -export const redirectUriQueryParam = 'redirect_uri'; +import { Observable } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; @Component({ selector: 'msfa-organization-picker', @@ -12,38 +12,25 @@ export const redirectUriQueryParam = 'redirect_uri'; styleUrls: ['./organization-picker.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class OrganizationPickerComponent extends UnsubscribeDirective implements OnInit { - user$ = this.userService.user$; +export class OrganizationPickerComponent extends UnsubscribeDirective { + organizations$: Observable = this.userService.user$.pipe( + filter(user => !!(user && user.organizations?.length)), + map(({ organizations }) => organizations) + ); - private redirectUri: string | null = null; - - constructor(private userService: UserService, private router: Router, private route: ActivatedRoute) { + constructor(private userService: UserService, private router: Router) { super(); super.unsubscribeOnDestroy( - this.user$.subscribe(user => { - if (user?.organizations?.length === 1) { - this.loginWithOrganization(user.organizations[0]); + this.organizations$.subscribe(organizations => { + if (organizations.length === 1) { + this.loginWithOrganization(organizations[0]); } }) ); } - ngOnInit(): void { - super.unsubscribeOnDestroy( - this.route.queryParams.subscribe(params => { - this.redirectUri = params[redirectUriQueryParam] ? decodeURI(params[redirectUriQueryParam]) : null; - }) - ); - } - loginWithOrganization(organization: Organization): void { - this.userService.setSelectedUserOrganization(organization); - - if (this.redirectUri) { - location.href = this.redirectUri; - return; - } - - this.router.navigateByUrl('/'); + this.userService.setSelectedOrganization(organization); + void this.router.navigateByUrl('/'); } } diff --git a/apps/mina-sidor-fa/src/app/shared/components/layout/layout.component.ts b/apps/mina-sidor-fa/src/app/shared/components/layout/layout.component.ts index 4094e4e..da2710d 100644 --- a/apps/mina-sidor-fa/src/app/shared/components/layout/layout.component.ts +++ b/apps/mina-sidor-fa/src/app/shared/components/layout/layout.component.ts @@ -21,7 +21,7 @@ export class LayoutComponent extends UnsubscribeDirective { routerLink: '/', }; private _breadcrumbsItems$ = new BehaviorSubject([this.startBreadcrumb]); - isLoggedIn$: Observable = this.authService.isLoggedIn$; + isLoggedIn$: Observable = this.authenticationService.isLoggedIn$; user$: Observable = this.isLoggedIn$.pipe( filter(loggedIn => !!loggedIn), switchMap(() => this.userService.user$) @@ -34,7 +34,7 @@ export class LayoutComponent extends UnsubscribeDirective { constructor( private router: Router, private activatedRoute: ActivatedRoute, - private authService: AuthenticationService, + private authenticationService: AuthenticationService, private userService: UserService ) { super(); diff --git a/apps/mina-sidor-fa/src/app/shared/constants/local-storage-keys.ts b/apps/mina-sidor-fa/src/app/shared/constants/local-storage-keys.ts index 06c9479..e79912c 100644 --- a/apps/mina-sidor-fa/src/app/shared/constants/local-storage-keys.ts +++ b/apps/mina-sidor-fa/src/app/shared/constants/local-storage-keys.ts @@ -1 +1,9 @@ -export const selectedUserOrganizationNumberKey = 'selectedOrganizationId'; +export const AUTH_TOKEN_KEY = 'id_token'; +export const AUTH_TOKEN_EXPIRE_KEY = 'expires_at'; +export const SELECTED_ORGANIZATION_NUMBER_KEY = 'selected_orgnr'; + +export const ALL_LOCAL_STORAGE_KEYS = { + AUTH_TOKEN_KEY, + AUTH_TOKEN_EXPIRE_KEY, + SELECTED_ORGANIZATION_NUMBER_KEY, +}; diff --git a/apps/mina-sidor-fa/src/app/shared/guards/auth.guard.ts b/apps/mina-sidor-fa/src/app/shared/guards/auth.guard.ts index 96f4d9d..0a9dfa5 100644 --- a/apps/mina-sidor-fa/src/app/shared/guards/auth.guard.ts +++ b/apps/mina-sidor-fa/src/app/shared/guards/auth.guard.ts @@ -1,33 +1,39 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router'; -import { UserService } from '@msfa-services/api/user.service'; import { environment } from '@msfa-environment'; import { AuthenticationService } from '@msfa-services/api/authentication.service'; +import { UserService } from '@msfa-services/api/user.service'; import { Observable, of } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; -import { redirectUriQueryParam } from '../../pages/organization-picker/organization-picker.component'; -@Injectable() +@Injectable({ + providedIn: 'root', +}) export class AuthGuard implements CanActivate { constructor( private authenticationService: AuthenticationService, - private userService: UserService, - private router: Router + private router: Router, + private userService: UserService ) {} canActivate(route: ActivatedRouteSnapshot): Observable { return this.authenticationService.isLoggedIn$.pipe( switchMap(loggedIn => { if (loggedIn) { - if (!this.authenticationService.hasSelectedUserOrganization()) { - this.router.navigateByUrl(`/organization-picker?${redirectUriQueryParam}=${encodeURI(location.href)}`); - } - - return of(true); + return this.userService.selectedOrganization$.pipe( + map(organization => { + if (!organization) { + void this.router.navigateByUrl(`/organization-picker`); + } + return true; + }) + ); } else if (route.queryParams.code) { return this.authenticationService.login$(route.queryParams.code).pipe(map(result => !!result)); } + void this.authenticationService.removeLocalStorageData(); + if (environment.environment === 'local') { void this.router.navigateByUrl(environment.loginUrl); } else { diff --git a/apps/mina-sidor-fa/src/app/shared/interceptors/auth.interceptor.ts b/apps/mina-sidor-fa/src/app/shared/interceptors/auth.interceptor.ts new file mode 100644 index 0000000..bf172f5 --- /dev/null +++ b/apps/mina-sidor-fa/src/app/shared/interceptors/auth.interceptor.ts @@ -0,0 +1,31 @@ +import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { SELECTED_ORGANIZATION_NUMBER_KEY } from '@msfa-constants/local-storage-keys'; +import { environment } from '@msfa-environment'; +import { AuthenticationService } from '@msfa-services/api/authentication.service'; +import { Observable } from 'rxjs'; + +@Injectable() +export class AuthInterceptor implements HttpInterceptor { + constructor(private authenticationService: AuthenticationService) {} + + private get authorizationToken(): { Authorization: string } | null { + const bearerToken = this.authenticationService.currentAuthorizationToken; + + return bearerToken ? { Authorization: `Bearer ${bearerToken}` } : null; + } + + private get selectedOrganizationNumber(): { orgnr: string } | null { + const orgnr = localStorage.getItem(SELECTED_ORGANIZATION_NUMBER_KEY); + + return orgnr ? { orgnr } : null; + } + + intercept(req: HttpRequest, next: HttpHandler): Observable> { + const clonedRequest: HttpRequest = req.clone({ + setHeaders: { ...environment.api.headers, ...this.authorizationToken, ...this.selectedOrganizationNumber }, + }); + + return next.handle(clonedRequest); + } +} diff --git a/apps/mina-sidor-fa/src/app/shared/services/api/auth.interceptor.ts b/apps/mina-sidor-fa/src/app/shared/services/api/auth.interceptor.ts deleted file mode 100644 index 6ad5095..0000000 --- a/apps/mina-sidor-fa/src/app/shared/services/api/auth.interceptor.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; -import { AuthenticationService } from './authentication.service'; - -@Injectable() -export class AuthInterceptor implements HttpInterceptor { - constructor(private auth: AuthenticationService) {} - - intercept(req: HttpRequest, next: HttpHandler): Observable> { - const idToken = this.auth.currentAuthorizationToken; - - if (idToken) { - const cloned = req.clone({ - headers: req.headers.set('Authorization', 'Bearer ' + idToken), - }); - - return next.handle(cloned); - } else { - return next.handle(req); - } - } -} diff --git a/apps/mina-sidor-fa/src/app/shared/services/api/authentication.service.ts b/apps/mina-sidor-fa/src/app/shared/services/api/authentication.service.ts index 0f20361..2cb39fd 100644 --- a/apps/mina-sidor-fa/src/app/shared/services/api/authentication.service.ts +++ b/apps/mina-sidor-fa/src/app/shared/services/api/authentication.service.ts @@ -1,7 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; -import { selectedUserOrganizationNumberKey } from '@msfa-constants/local-storage-keys'; +import { ALL_LOCAL_STORAGE_KEYS, AUTH_TOKEN_EXPIRE_KEY, AUTH_TOKEN_KEY } from '@msfa-constants/local-storage-keys'; import { environment } from '@msfa-environment'; import { AuthenticationResponse } from '@msfa-models/api/authentication.response.model'; import { Authentication, mapAuthApiResponseToAuthenticationResult } from '@msfa-models/authentication.model'; @@ -9,8 +9,6 @@ import { add, isBefore } from 'date-fns'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { map, tap } from 'rxjs/operators'; -const API_HEADERS = { headers: environment.api.headers }; - @Injectable({ providedIn: 'root', }) @@ -36,17 +34,16 @@ export class AuthenticationService { const expiresAt = add(new Date(), { seconds: authenticationResult.expiresIn }); this._token$.next(authenticationResult.idToken); - localStorage.setItem('id_token', authenticationResult.idToken); + localStorage.setItem(AUTH_TOKEN_KEY, authenticationResult.idToken); this._expiration$.next(expiresAt.valueOf()); - localStorage.setItem('expires_at', JSON.stringify(expiresAt.valueOf())); + localStorage.setItem(AUTH_TOKEN_EXPIRE_KEY, JSON.stringify(expiresAt.valueOf())); } login$(authorizationCodeFromCiam: string): Observable { + this.removeLocalStorageData(); + return this.httpClient - .get<{ data: AuthenticationResponse }>( - AuthenticationService._authTokenApiUrl(authorizationCodeFromCiam), - API_HEADERS - ) + .get<{ data: AuthenticationResponse }>(AuthenticationService._authTokenApiUrl(authorizationCodeFromCiam)) .pipe( map(({ data }) => mapAuthApiResponseToAuthenticationResult(data)), tap(authenticationResult => { @@ -56,8 +53,8 @@ export class AuthenticationService { } private _getLocalStorageData(): { id_token: string; expires_at: number } { - const id_token = localStorage.getItem('id_token'); - const expiration = localStorage.getItem('expires_at'); + const id_token = localStorage.getItem(AUTH_TOKEN_KEY); + const expiration = localStorage.getItem(AUTH_TOKEN_EXPIRE_KEY); return id_token && expiration ? { @@ -67,6 +64,12 @@ export class AuthenticationService { : null; } + public removeLocalStorageData(): void { + Object.values(ALL_LOCAL_STORAGE_KEYS).forEach(value => { + localStorage.removeItem(value); + }); + } + get currentAuthorizationToken(): string { return this._token$.getValue(); } @@ -81,9 +84,7 @@ export class AuthenticationService { } logout(): void { - localStorage.removeItem('id_token'); - localStorage.removeItem('expires_at'); - localStorage.removeItem(selectedUserOrganizationNumberKey); + this.removeLocalStorageData(); if (environment.environment === 'local') { void this.router.navigateByUrl(environment.logoutUrl); @@ -91,8 +92,4 @@ export class AuthenticationService { document.location.href = environment.logoutUrl; } } - - hasSelectedUserOrganization(): boolean { - return !!localStorage.getItem(selectedUserOrganizationNumberKey); - } } diff --git a/apps/mina-sidor-fa/src/app/shared/services/api/authorizations.service.ts b/apps/mina-sidor-fa/src/app/shared/services/api/authorizations.service.ts index 54f9f7d..62679bf 100644 --- a/apps/mina-sidor-fa/src/app/shared/services/api/authorizations.service.ts +++ b/apps/mina-sidor-fa/src/app/shared/services/api/authorizations.service.ts @@ -9,15 +9,13 @@ import { import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -const API_HEADERS = { headers: environment.api.headers }; - @Injectable({ providedIn: 'root', }) export class AuthorizationService { - private _authorizationsApiUrl = `${environment.api.url}/authorizations`; + private _apiBaseUrl = `${environment.api.url}/authorizations`; public authorizations$: Observable = this.httpClient - .get(this._authorizationsApiUrl, API_HEADERS) + .get(this._apiBaseUrl) .pipe(map(({ data }) => data.map(authorization => mapAuthorizationApiResponseToAuthorization(authorization)))); constructor(private httpClient: HttpClient) {} diff --git a/apps/mina-sidor-fa/src/app/shared/services/api/avrop-api.service.ts b/apps/mina-sidor-fa/src/app/shared/services/api/avrop-api.service.ts index 82f7647..1b40a9a 100644 --- a/apps/mina-sidor-fa/src/app/shared/services/api/avrop-api.service.ts +++ b/apps/mina-sidor-fa/src/app/shared/services/api/avrop-api.service.ts @@ -14,8 +14,6 @@ import { Observable, of } from 'rxjs'; import { delay, filter, map } from 'rxjs/operators'; import { HandledareAvrop } from '../../../pages/avrop/models/handledare-avrop'; -const API_HEADERS = { headers: environment.api.headers }; - const tempHandledareMock: HandledareAvrop[] = [ { id: '1', fullName: 'Göran Persson' }, { id: '2', fullName: 'Stefan Löfven' }, @@ -38,12 +36,10 @@ export class AvropApiService { offset = 0, limit = 20 ): Observable { - return this.httpClient - .get<{ data: AvropResponse[] }>(`${this._apiBaseUrl}`, { ...API_HEADERS }) - .pipe( - filter(response => !!response?.data), - map(({ data }) => data.map(avrop => mapAvropResponseToAvrop(avrop))) - ); + return this.httpClient.get<{ data: AvropResponse[] }>(`${this._apiBaseUrl}`).pipe( + filter(response => !!response?.data), + map(({ data }) => data.map(avrop => mapAvropResponseToAvrop(avrop))) + ); } getSelectableHandledare$(deltagare: Avrop[]): Observable { @@ -67,7 +63,7 @@ export class AvropApiService { selectedKommuner: MultiselectFilterOption[] ): Observable { return this.httpClient - .get<{ data: UtforandeVerksamhetResponse[] }>(`${this._apiBaseUrl}/utforandeverksamheter`, { ...API_HEADERS }) + .get<{ data: UtforandeVerksamhetResponse[] }>(`${this._apiBaseUrl}/utforandeverksamheter`) .pipe( filter(response => !!response?.data), map(({ data }) => @@ -82,12 +78,10 @@ export class AvropApiService { selectedTjanster: MultiselectFilterOption[], selectedUtforandeVerksamheter: MultiselectFilterOption[] ): Observable { - return this.httpClient - .get<{ data: KommunResponse[] }>(`${this._apiBaseUrl}/kommuner`, { ...API_HEADERS }) - .pipe( - filter(response => !!response?.data), - map(({ data }) => data.map(kommun => ({ label: mapKommunResponseToKommun(kommun).name }))) - ); + return this.httpClient.get<{ data: KommunResponse[] }>(`${this._apiBaseUrl}/kommuner`).pipe( + filter(response => !!response?.data), + map(({ data }) => data.map(kommun => ({ label: mapKommunResponseToKommun(kommun).name }))) + ); } async tilldelaHandledare(deltagare: Avrop[], handledare: HandledareAvrop): Promise { diff --git a/apps/mina-sidor-fa/src/app/shared/services/api/deltagare.service.ts b/apps/mina-sidor-fa/src/app/shared/services/api/deltagare.service.ts index a534322..fd020f0 100644 --- a/apps/mina-sidor-fa/src/app/shared/services/api/deltagare.service.ts +++ b/apps/mina-sidor-fa/src/app/shared/services/api/deltagare.service.ts @@ -33,8 +33,6 @@ import { sortFromToDates } from '@msfa-utils/sort.util'; import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs'; import { catchError, filter, map, switchMap } from 'rxjs/operators'; -const API_HEADERS = { headers: environment.api.headers }; - @Injectable({ providedIn: 'root', }) @@ -96,7 +94,6 @@ export class DeltagareService extends UnsubscribeDirective { console.log(params); return this.httpClient .get(this._apiBaseUrl, { - ...API_HEADERS, params, }) .pipe( @@ -112,32 +109,28 @@ export class DeltagareService extends UnsubscribeDirective { } private _fetchContactInformation$(id: string): Observable> { - return this.httpClient - .get<{ data: ContactInformationResponse }>(`${this._apiBaseUrl}/${id}/contact`, { ...API_HEADERS }) - .pipe( - map(({ data }) => mapResponseToContactInformation(data)), - catchError(error => { - this.errorService.add(errorToCustomError(error)); - return of({}); - }) - ); + return this.httpClient.get<{ data: ContactInformationResponse }>(`${this._apiBaseUrl}/${id}/contact`).pipe( + map(({ data }) => mapResponseToContactInformation(data)), + catchError(error => { + this.errorService.add(errorToCustomError(error)); + return of({}); + }) + ); } private _fetchDriversLicense$(id: string): Observable> { - return this.httpClient - .get<{ data: DriversLicenseResponse }>(`${this._apiBaseUrl}/${id}/driverlicense`, { ...API_HEADERS }) - .pipe( - map(({ data }) => mapResponseToDriversLicense(data)), - catchError(error => { - this.errorService.add(errorToCustomError(error)); - return of({}); - }) - ); + return this.httpClient.get<{ data: DriversLicenseResponse }>(`${this._apiBaseUrl}/${id}/driverlicense`).pipe( + map(({ data }) => mapResponseToDriversLicense(data)), + catchError(error => { + this.errorService.add(errorToCustomError(error)); + return of({}); + }) + ); } private _fetchHighestEducation$(id: string): Observable> { return this.httpClient - .get<{ data: HighestEducationResponse }>(`${this._apiBaseUrl}/${id}/educationlevels/highest`, { ...API_HEADERS }) + .get<{ data: HighestEducationResponse }>(`${this._apiBaseUrl}/${id}/educationlevels/highest`) .pipe( map(({ data }) => mapResponseToHighestEducation(data)), catchError(error => { @@ -148,88 +141,76 @@ export class DeltagareService extends UnsubscribeDirective { } private _fetchEducations$(id: string): Observable { - return this.httpClient - .get<{ data: EducationsResponse }>(`${this._apiBaseUrl}/${id}/educations`, { ...API_HEADERS }) - .pipe( - map(({ data }) => - data.utbildningar - ? data.utbildningar.sort((a, b) => - sortFromToDates({ from: a.period_from, to: a.period_tom }, { from: b.period_from, to: b.period_tom }) - ) - : [] - ), - map(educations => educations.map(utbildning => mapResponseToEducation(utbildning))), - catchError(error => { - this.errorService.add(errorToCustomError(error)); - return of([]); - }) - ); + return this.httpClient.get<{ data: EducationsResponse }>(`${this._apiBaseUrl}/${id}/educations`).pipe( + map(({ data }) => + data.utbildningar + ? data.utbildningar.sort((a, b) => + sortFromToDates({ from: a.period_from, to: a.period_tom }, { from: b.period_from, to: b.period_tom }) + ) + : [] + ), + map(educations => educations.map(utbildning => mapResponseToEducation(utbildning))), + catchError(error => { + this.errorService.add(errorToCustomError(error)); + return of([]); + }) + ); } private _fetchTranslator$(id: string): Observable { - return this.httpClient - .get<{ data: TranslatorResponse }>(`${this._apiBaseUrl}/${id}/translator`, { ...API_HEADERS }) - .pipe( - map(({ data }) => data.sprak?.beskrivning || null), - catchError(error => { - this.errorService.add(errorToCustomError(error)); - return of(''); - }) - ); + return this.httpClient.get<{ data: TranslatorResponse }>(`${this._apiBaseUrl}/${id}/translator`).pipe( + map(({ data }) => data.sprak?.beskrivning || null), + catchError(error => { + this.errorService.add(errorToCustomError(error)); + return of(''); + }) + ); } private _fetchWorkLanguages$(id: string): Observable { - return this.httpClient - .get<{ data: WorkLanguagesResponse }>(`${this._apiBaseUrl}/${id}/work/languages`, { ...API_HEADERS }) - .pipe( - map(({ data }) => data?.sprak?.map(sprak => sprak.beskrivning) || []), - catchError(error => { - this.errorService.add(errorToCustomError(error)); - return of([]); - }) - ); + return this.httpClient.get<{ data: WorkLanguagesResponse }>(`${this._apiBaseUrl}/${id}/work/languages`).pipe( + map(({ data }) => data?.sprak?.map(sprak => sprak.beskrivning) || []), + catchError(error => { + this.errorService.add(errorToCustomError(error)); + return of([]); + }) + ); } private _fetchDisabilities$(id: string): Observable { - return this.httpClient - .get<{ data: DisabilityResponse[] }>(`${this._apiBaseUrl}/${id}/work/disabilities`, { ...API_HEADERS }) - .pipe( - map(({ data }) => data?.map(funktionsnedsattning => mapResponseToDisability(funktionsnedsattning)) || []), - catchError(error => { - this.errorService.add(errorToCustomError(error)); - return of([]); - }) - ); + return this.httpClient.get<{ data: DisabilityResponse[] }>(`${this._apiBaseUrl}/${id}/work/disabilities`).pipe( + map(({ data }) => data?.map(funktionsnedsattning => mapResponseToDisability(funktionsnedsattning)) || []), + catchError(error => { + this.errorService.add(errorToCustomError(error)); + return of([]); + }) + ); } private _fetchWorkExperiences$(id: string): Observable { - return this.httpClient - .get<{ data: WorkExperiencesResponse }>(`${this._apiBaseUrl}/${id}/work/experiences`, { ...API_HEADERS }) - .pipe( - map( - ({ data }) => - data?.arbetslivserfarenheter?.sort((a, b) => - sortFromToDates({ from: a.period_from, to: a.period_tom }, { from: b.period_from, to: b.period_tom }) - ) || [] - ), - map(workExperiences => workExperiences.map(erfarenhet => mapResponseToWorkExperience(erfarenhet))), - catchError(error => { - this.errorService.add(errorToCustomError(error)); - return of([]); - }) - ); + return this.httpClient.get<{ data: WorkExperiencesResponse }>(`${this._apiBaseUrl}/${id}/work/experiences`).pipe( + map( + ({ data }) => + data?.arbetslivserfarenheter?.sort((a, b) => + sortFromToDates({ from: a.period_from, to: a.period_tom }, { from: b.period_from, to: b.period_tom }) + ) || [] + ), + map(workExperiences => workExperiences.map(erfarenhet => mapResponseToWorkExperience(erfarenhet))), + catchError(error => { + this.errorService.add(errorToCustomError(error)); + return of([]); + }) + ); } private _fetchAvropInformation$(id: string): Observable> { - return this.httpClient - .get<{ data: AvropResponse }>(`${this._apiBaseUrl}/${id}/avrop`, { ...API_HEADERS }) - .pipe( - map(({ data }) => (data ? mapAvropResponseToAvrop(data) : {})), - catchError(error => { - this.errorService.add(errorToCustomError(error)); - return of({}); - }) - ); + return this.httpClient.get<{ data: AvropResponse }>(`${this._apiBaseUrl}/${id}/avrop`).pipe( + map(({ data }) => (data ? mapAvropResponseToAvrop(data) : {})), + catchError(error => { + this.errorService.add(errorToCustomError(error)); + return of({}); + }) + ); } // As TypeScript has some limitations regarding combining Observables this way, diff --git a/apps/mina-sidor-fa/src/app/shared/services/api/employee.service.ts b/apps/mina-sidor-fa/src/app/shared/services/api/employee.service.ts index c2bc232..16856c4 100644 --- a/apps/mina-sidor-fa/src/app/shared/services/api/employee.service.ts +++ b/apps/mina-sidor-fa/src/app/shared/services/api/employee.service.ts @@ -26,13 +26,11 @@ import { BehaviorSubject, combineLatest, Observable, of, throwError } from 'rxjs import { catchError, filter, map, switchMap, take } from 'rxjs/operators'; import { TjanstService } from './tjanst.service'; -const API_HEADERS = { headers: environment.api.headers }; - @Injectable({ providedIn: 'root', }) export class EmployeeService extends UnsubscribeDirective { - private _apiUrl = `${environment.api.url}/users`; + private _apiBaseUrl = `${environment.api.url}/users`; private _currentEmployeeId$ = new BehaviorSubject(null); private _limit$ = new BehaviorSubject(20); private _page$ = new BehaviorSubject(1); @@ -43,6 +41,7 @@ export class EmployeeService extends UnsubscribeDirective { private _employee$ = new BehaviorSubject(null); public employee$: Observable = this._employee$.asObservable(); + constructor( private httpClient: HttpClient, private errorService: ErrorService, @@ -118,10 +117,7 @@ export class EmployeeService extends UnsubscribeDirective { } return this.httpClient - .get(this._apiUrl, { - ...API_HEADERS, - params, - }) + .get(this._apiBaseUrl, { params }) .pipe( map(({ data, meta }) => { return { data: data.map(employee => mapResponseToEmployeeCompact(employee)), meta }; @@ -130,15 +126,13 @@ export class EmployeeService extends UnsubscribeDirective { } private _fetchEmployee$(id: string): Observable> { - return this.httpClient - .get<{ data: EmployeeResponse }>(`${this._apiUrl}/${id}`, { ...API_HEADERS }) - .pipe( - map(({ data }) => mapResponseToEmployee(data)), - catchError(error => { - this.errorService.add(errorToCustomError(error)); - return of({}); - }) - ); + return this.httpClient.get<{ data: EmployeeResponse }>(`${this._apiBaseUrl}/${id}`).pipe( + map(({ data }) => mapResponseToEmployee(data)), + catchError(error => { + this.errorService.add(errorToCustomError(error)); + return of({}); + }) + ); } public setSearchFilter(value: string): void { @@ -151,18 +145,16 @@ export class EmployeeService extends UnsubscribeDirective { // Not done, waiting for delete api http response public deleteEmployee(id: string): Observable { - return this.httpClient - .delete(`${this._apiUrl}/${id}`, { ...API_HEADERS }) - .pipe( - take(1), - map(response => { - return { - status: response.status || 200, // mockresponse - message: response.message || 'deleted succeeded', // mockresponse - }; - }), - catchError(error => throwError({ message: error as string, type: ErrorType.API })) - ); + return this.httpClient.delete(`${this._apiBaseUrl}/${id}`).pipe( + take(1), + map(response => { + return { + status: response.status || 200, // mockresponse + message: response.message || 'deleted succeeded', // mockresponse + }; + }), + catchError(error => throwError({ message: error as string, type: ErrorType.API })) + ); } public setSort(newSortKey: keyof EmployeeCompactResponse): void { @@ -178,7 +170,7 @@ export class EmployeeService extends UnsubscribeDirective { } public postNewEmployee(employeeData: Employee): Observable { - return this.httpClient.post<{ id: string }>(this._apiUrl, mapEmployeeToRequestData(employeeData), API_HEADERS).pipe( + return this.httpClient.post<{ id: string }>(this._apiBaseUrl, mapEmployeeToRequestData(employeeData)).pipe( map(({ id }) => id), catchError(error => throwError({ message: error as string, type: ErrorType.API })) ); @@ -186,7 +178,7 @@ export class EmployeeService extends UnsubscribeDirective { public postEmployeeInvitation(email: string): Observable { return this.httpClient - .post<{ data: EmployeeInviteMockApiResponse }>(`${this._apiUrl}/invite`, { email }, API_HEADERS) + .post<{ data: EmployeeInviteMockApiResponse }>(`${this._apiBaseUrl}/invite`, { email }) .pipe( take(1), map(res => res.data), diff --git a/apps/mina-sidor-fa/src/app/shared/services/api/participants.service.ts b/apps/mina-sidor-fa/src/app/shared/services/api/participants.service.ts index f531913..b757ac6 100644 --- a/apps/mina-sidor-fa/src/app/shared/services/api/participants.service.ts +++ b/apps/mina-sidor-fa/src/app/shared/services/api/participants.service.ts @@ -23,15 +23,13 @@ function filterParticipants(participants: Participant[], searchFilter: string): }); } -const API_HEADERS = { headers: environment.api.headers }; - @Injectable({ providedIn: 'root', }) export class ParticipantsService { - private _apiUrl = `${environment.api.url}/participants`; + private _apiBaseUrl = `${environment.api.url}/participants`; private _allParticipants$: Observable = this.httpClient - .get(this._apiUrl) + .get(this._apiBaseUrl) .pipe(map(response => response.data.map(participant => mapParticipantApiResponseToParticipant(participant)))); private _activeParticipantsSortBy$ = new BehaviorSubject | null>({ key: 'handleBefore', @@ -79,7 +77,7 @@ export class ParticipantsService { public fetchDetailedParticipantData$(id: string): Observable { return this.httpClient - .get(`${this._apiUrl}/${id}`, { ...API_HEADERS }) + .get(`${this._apiBaseUrl}/${id}`) .pipe(map(result => mapParticipantApiResponseToParticipant(result.data))); } diff --git a/apps/mina-sidor-fa/src/app/shared/services/api/service.service.ts b/apps/mina-sidor-fa/src/app/shared/services/api/service.service.ts index 7b8d2f9..9c607f8 100644 --- a/apps/mina-sidor-fa/src/app/shared/services/api/service.service.ts +++ b/apps/mina-sidor-fa/src/app/shared/services/api/service.service.ts @@ -5,15 +5,13 @@ import { mapServiceApiResponseToService, Service, ServiceApiResponse } from '@ms import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -const API_HEADERS = { headers: environment.api.headers }; - @Injectable({ providedIn: 'root', }) export class ServiceService { - private _servicesApiUrl = `${environment.api.url}/services`; + private _apiBaseUrl = `${environment.api.url}/services`; public services$: Observable = this.httpClient - .get(this._servicesApiUrl, API_HEADERS) + .get(this._apiBaseUrl) .pipe(map(response => response.data.map(service => mapServiceApiResponseToService(service)))); constructor(private httpClient: HttpClient) {} diff --git a/apps/mina-sidor-fa/src/app/shared/services/api/tjanst.service.ts b/apps/mina-sidor-fa/src/app/shared/services/api/tjanst.service.ts index 14d79cf..afdeb15 100644 --- a/apps/mina-sidor-fa/src/app/shared/services/api/tjanst.service.ts +++ b/apps/mina-sidor-fa/src/app/shared/services/api/tjanst.service.ts @@ -7,13 +7,11 @@ import { mapResponseToTjanst, Tjanst } from '@msfa-models/tjanst.model'; import { BehaviorSubject, Observable } from 'rxjs'; import { filter, map, switchMap } from 'rxjs/operators'; -const API_HEADERS = { headers: environment.api.headers }; - @Injectable({ providedIn: 'root', }) export class TjanstService extends UnsubscribeDirective { - private _apiUrl = `${environment.api.url}/tjanster`; + private _apiBaseUrl = `${environment.api.url}/tjanster`; private _tjanster$ = new BehaviorSubject(null); public tjanster$: Observable = this._tjanster$.asObservable(); @@ -22,7 +20,7 @@ export class TjanstService extends UnsubscribeDirective { filter(tjanster => !tjanster?.length), switchMap(() => this.httpClient - .get<{ data: TjanstResponse[] }>(this._apiUrl, API_HEADERS) + .get<{ data: TjanstResponse[] }>(this._apiBaseUrl) .pipe(map(({ data }) => data.map(tjanst => mapResponseToTjanst(tjanst)))) ) ); diff --git a/apps/mina-sidor-fa/src/app/shared/services/api/user.service.ts b/apps/mina-sidor-fa/src/app/shared/services/api/user.service.ts index ede2eb7..0955969 100644 --- a/apps/mina-sidor-fa/src/app/shared/services/api/user.service.ts +++ b/apps/mina-sidor-fa/src/app/shared/services/api/user.service.ts @@ -1,6 +1,6 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { selectedUserOrganizationNumberKey } from '@msfa-constants/local-storage-keys'; +import { SELECTED_ORGANIZATION_NUMBER_KEY } from '@msfa-constants/local-storage-keys'; import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive'; import { environment } from '@msfa-environment'; import { OrganizationResponse } from '@msfa-models/api/organization.response.model'; @@ -9,59 +9,64 @@ import { mapResponseToOrganization, Organization } from '@msfa-models/organizati import { mapResponseToUserInfo, UserInfo } from '@msfa-models/user-info.model'; import { User } from '@msfa-models/user.model'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; -import { filter, map } from 'rxjs/operators'; - -const API_HEADERS = { headers: environment.api.headers }; +import { filter, map, switchMap } from 'rxjs/operators'; +import { AuthenticationService } from './authentication.service'; @Injectable({ providedIn: 'root', }) export class UserService extends UnsubscribeDirective { - private readonly selectedUserOrganizationNumberKey = 'selectedOrganizationId'; - - private _authApiUrl = `${environment.api.url}/auth`; + private _apiBaseUrl = `${environment.api.url}/auth`; private _user$ = new BehaviorSubject(null); - public user$: Observable = this._user$.asObservable(); + private _selectedOrganizationNumber$ = new BehaviorSubject(null); - constructor(private httpClient: HttpClient) { + constructor(private httpClient: HttpClient, private authenticationService: AuthenticationService) { super(); super.unsubscribeOnDestroy( - combineLatest([this._fetchUserInfo$(), this._fetchOrganizations$()]).subscribe(([userInfo, organizations]) => { - this._user$.next({ ...userInfo, organizations }); - }) + this.authenticationService.isLoggedIn$ + .pipe( + filter(loggedIn => !!loggedIn), + switchMap(() => combineLatest([this._fetchUserInfo$(), this._fetchOrganizations$()])) + ) + .subscribe(([userInfo, organizations]) => { + this._user$.next({ ...userInfo, organizations }); + }) ); - } - - getSelectedUserOrganization(user: User): Organization { - if (!user) { - return null; - } - - return user.organizations.find( - organization => organization.organizationNumber === localStorage.getItem(selectedUserOrganizationNumberKey) - ); - } - - setSelectedUserOrganization(organization: Organization): void { - if (!organization) { - return; - } - - localStorage.setItem(selectedUserOrganizationNumberKey, organization?.organizationNumber); + this._selectedOrganizationNumber$.next(this._selectedOrganizationNumber); } private _fetchOrganizations$(): Observable { - return this.httpClient.get<{ data: OrganizationResponse[] }>(`${this._authApiUrl}/organizations`, API_HEADERS).pipe( + return this.httpClient.get<{ data: OrganizationResponse[] }>(`${this._apiBaseUrl}/organizations`).pipe( filter(response => !!response?.data), map(({ data }) => data.map(organization => mapResponseToOrganization(organization))) ); } private _fetchUserInfo$(): Observable { - return this.httpClient.get<{ data: UserInfoResponse }>(`${this._authApiUrl}/userinfo`, API_HEADERS).pipe( + return this.httpClient.get<{ data: UserInfoResponse }>(`${this._apiBaseUrl}/userinfo`).pipe( filter(response => !!response?.data), map(({ data }) => mapResponseToUserInfo(data)) ); } + + private get _selectedOrganizationNumber(): string | null { + return localStorage.getItem(SELECTED_ORGANIZATION_NUMBER_KEY); + } + + public get selectedOrganization$(): Observable { + return combineLatest([this._selectedOrganizationNumber$, this._user$]).pipe( + filter(([, user]) => !!user), + map(([organizationNumber, user]) => { + return organizationNumber + ? user.organizations.find(organization => organization.organizationNumber === organizationNumber) + : null; + }) + ); + } + + public setSelectedOrganization(organization: Organization): void { + localStorage.setItem(SELECTED_ORGANIZATION_NUMBER_KEY, organization.organizationNumber); + this._selectedOrganizationNumber$.next(organization.organizationNumber); + } } diff --git a/apps/mina-sidor-fa/src/environments/environment.api.ts b/apps/mina-sidor-fa/src/environments/environment.api.ts index 5a1f163..0264465 100644 --- a/apps/mina-sidor-fa/src/environments/environment.api.ts +++ b/apps/mina-sidor-fa/src/environments/environment.api.ts @@ -8,8 +8,6 @@ export const environment: Environment = { production: false, api: { url: '/api', - headers: { - orgnr: '5564673381', // Until we have an organisation-selector - }, + headers: {}, }, };