174 lines
6.7 KiB
TypeScript
174 lines
6.7 KiB
TypeScript
import { inject, Injectable } from '@angular/core';
|
|
import { KeycloakService } from 'keycloak-angular';
|
|
import { BehaviorSubject, firstValueFrom, Observable, throwError } from 'rxjs';
|
|
import { catchError, tap } from 'rxjs/operators';
|
|
import { BASE_HREF, List, mapEach, RequiredParam, Validate } from '../../utils';
|
|
import { QueryParam, Toaster } from '../../services';
|
|
import { CacheApiService } from '../../caching';
|
|
import { EntitiesService } from '../../listing';
|
|
import { IIqserUser } from '../types/user.response';
|
|
import { ICreateUserRequest } from '../types/create-user.request';
|
|
import { IResetPasswordRequest } from '../types/reset-password.request';
|
|
import { IMyProfileUpdateRequest } from '../types/my-profile-update.request';
|
|
import { IProfileUpdateRequest } from '../types/profile-update.request';
|
|
import { KeycloakProfile } from 'keycloak-js';
|
|
import { IqserUser } from '../iqser-user.model';
|
|
import { IqserPermissionsService, IqserRolesService } from '../../permissions';
|
|
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
|
|
import { IqserConfigService } from '../../services';
|
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
|
|
|
@Injectable()
|
|
export abstract class IqserUserService<
|
|
Interface extends IIqserUser = IIqserUser,
|
|
Class extends IqserUser & Interface = IqserUser & Interface,
|
|
> extends EntitiesService<Interface, Class> {
|
|
readonly currentUser$: Observable<Class | undefined>;
|
|
protected abstract readonly _defaultModelPath: string;
|
|
protected abstract readonly _permissionsFilter: (role: string) => boolean;
|
|
protected abstract readonly _rolesFilter: (role: string) => boolean;
|
|
protected abstract readonly _entityClass: new (entityInterface: Interface | KeycloakProfile, ...args: unknown[]) => Class;
|
|
protected readonly _currentUser$ = new BehaviorSubject<Class | undefined>(undefined);
|
|
protected readonly _baseHref = inject(BASE_HREF);
|
|
protected readonly _toaster = inject(Toaster);
|
|
protected readonly _configService = inject(IqserConfigService);
|
|
protected readonly _keycloakService = inject(KeycloakService);
|
|
protected readonly _cacheApiService = inject(CacheApiService);
|
|
protected readonly _permissionsService = inject(IqserPermissionsService, { optional: true });
|
|
protected readonly _rolesService = inject(IqserRolesService, { optional: true });
|
|
|
|
constructor() {
|
|
super();
|
|
this.currentUser$ = this._currentUser$.asObservable();
|
|
}
|
|
|
|
get currentUser(): Class | undefined {
|
|
return this._currentUser$.value;
|
|
}
|
|
|
|
async initialize(): Promise<void> {
|
|
await this.loadCurrentUser();
|
|
|
|
console.log('Initializing users for: ', this.currentUser);
|
|
if (!this.currentUser) {
|
|
return;
|
|
}
|
|
|
|
await firstValueFrom(this.loadAll());
|
|
}
|
|
|
|
async logout() {
|
|
try {
|
|
await this._keycloakService.loadUserProfile(true);
|
|
await this._cacheApiService.wipeCaches();
|
|
await this._keycloakService.logout();
|
|
} catch (e) {
|
|
await this.redirectToLogin();
|
|
}
|
|
}
|
|
|
|
async redirectToLogin() {
|
|
await this._cacheApiService.wipeCaches();
|
|
window.location.href = this._keycloakService.getKeycloakInstance().createLoginUrl({
|
|
redirectUri: window.location.origin + this._baseHref,
|
|
idpHint: this._configService.values.OAUTH_IDP_HINT
|
|
});
|
|
}
|
|
|
|
loadAll() {
|
|
return this.getAll().pipe(
|
|
mapEach(user => new this._entityClass(user, user.roles, user.userId)),
|
|
tap(users => this.setEntities(users)),
|
|
);
|
|
}
|
|
|
|
async loadCurrentUser(): Promise<Class | undefined> {
|
|
let profile: KeycloakProfile | undefined;
|
|
try {
|
|
profile = await this._keycloakService.loadUserProfile(true);
|
|
if (!profile.id) {
|
|
throw new Error('No user id');
|
|
}
|
|
} catch (e) {
|
|
console.log('Load KC profile failed');
|
|
await this.redirectToLogin();
|
|
return;
|
|
}
|
|
|
|
const all = this._keycloakService.getUserRoles(true);
|
|
const permissions = all.filter(role => this._permissionsFilter(role));
|
|
const roles = all.filter(role => this._rolesFilter(role));
|
|
this._permissionsService?.load(permissions);
|
|
this._rolesService?.load(roles);
|
|
const user = new this._entityClass(profile, roles, profile.id);
|
|
this.replace(user);
|
|
|
|
this._currentUser$.next(this.find(profile.id));
|
|
return user;
|
|
}
|
|
|
|
getName(user: string | Interface | Class): string | undefined {
|
|
if (!user) {
|
|
return;
|
|
}
|
|
|
|
const userId = typeof user === 'string' ? user : user.userId;
|
|
return this.find(userId)?.name;
|
|
}
|
|
|
|
getAll(url = this._defaultModelPath): Observable<Interface[]> {
|
|
return super.getAll(url, [{ key: 'refreshCache', value: true }]);
|
|
}
|
|
|
|
@Validate()
|
|
updateProfile<T = IProfileUpdateRequest>(@RequiredParam() body: T, @RequiredParam() userId: string) {
|
|
return this._post<unknown>(body, `${this._defaultModelPath}/profile/${userId}`);
|
|
}
|
|
|
|
@Validate()
|
|
updateMyProfile<T = IMyProfileUpdateRequest>(@RequiredParam() body: T) {
|
|
const showToast = (error: HttpErrorResponse) => {
|
|
if (error.status === HttpStatusCode.BadRequest) {
|
|
const { message } = error.error;
|
|
this._toaster.error(_('update-profile.errors.bad-request'), { params: { message } });
|
|
} else {
|
|
this._toaster.error(_('update-profile.errors.generic'));
|
|
}
|
|
return throwError(() => error);
|
|
};
|
|
|
|
return this._post<unknown>(body, `${this._defaultModelPath}/my-profile`).pipe(catchError(showToast));
|
|
}
|
|
|
|
@Validate()
|
|
resetPassword<T = IResetPasswordRequest>(@RequiredParam() body: T, @RequiredParam() userId: string) {
|
|
return this._post<unknown>(body, `${this._defaultModelPath}/${userId}/reset-password`);
|
|
}
|
|
|
|
@Validate()
|
|
create<T = ICreateUserRequest>(@RequiredParam() body: T) {
|
|
return this._post(body);
|
|
}
|
|
|
|
delete(userIds: List) {
|
|
const queryParams = userIds.map<QueryParam>(userId => ({ key: 'userId', value: userId }));
|
|
return super.delete(userIds, this._defaultModelPath, queryParams);
|
|
}
|
|
|
|
find(id: string): Class | undefined {
|
|
if (id?.toLowerCase() === 'system') {
|
|
return new this._entityClass({ username: 'System' }, [], 'system');
|
|
}
|
|
|
|
if (!id) {
|
|
return undefined;
|
|
}
|
|
|
|
return super.find(id) ?? new this._entityClass({ username: 'Deleted User' }, [], 'deleted');
|
|
}
|
|
}
|
|
|
|
export function getCurrentUser<Class extends IqserUser = IqserUser>() {
|
|
return inject<IqserUserService<IIqserUser, Class>>(IqserUserService).currentUser;
|
|
}
|