RED-4718: use common-ui user related stuff
This commit is contained in:
parent
164c527ce7
commit
b57b600fc0
@ -9,7 +9,7 @@ import { ApiPathInterceptor } from '@utils/api-path-interceptor';
|
||||
import { MissingTranslationHandler, TranslateCompiler, TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import {
|
||||
BASE_HREF,
|
||||
BaseConfigService,
|
||||
BaseUserService,
|
||||
CachingModule,
|
||||
CommonUiModule,
|
||||
HELP_DOCS,
|
||||
@ -24,7 +24,6 @@ import {
|
||||
import { ToastrModule } from 'ngx-toastr';
|
||||
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||
import { environment } from '@environments/environment';
|
||||
import { AuthModule } from './modules/auth/auth.module';
|
||||
import { AuthErrorComponent } from '@components/auth-error/auth-error.component';
|
||||
import { NotificationsComponent } from '@components/notifications/notifications.component';
|
||||
import { DownloadsListScreenComponent } from '@components/downloads-list-screen/downloads-list-screen.component';
|
||||
@ -60,6 +59,7 @@ import { PdfViewerModule } from './modules/pdf-viewer/pdf-viewer.module';
|
||||
import { LicenseService } from '@services/license.service';
|
||||
import { TenantIdInterceptor } from '@utils/tenant-id-interceptor';
|
||||
import { UI_CACHES } from '@utils/constants';
|
||||
import { AuthModule } from './modules/auth/auth.module';
|
||||
|
||||
export function httpLoaderFactory(httpClient: HttpClient, configService: ConfigService): PruningTranslationLoader {
|
||||
return new PruningTranslationLoader(httpClient, '/assets/i18n/', `.json?version=${configService.values.FRONTEND_APP_VERSION}`);
|
||||
@ -78,14 +78,19 @@ export const appModuleFactory = (config: AppConfig) => {
|
||||
SharedModule,
|
||||
FileUploadDownloadModule,
|
||||
HttpClientModule,
|
||||
AuthModule,
|
||||
AppRoutingModule,
|
||||
MonacoEditorModule,
|
||||
IqserHelpModeModule,
|
||||
CommonUiModule.forRoot({
|
||||
existingUserPreferenceService: UserPreferenceService,
|
||||
configServiceFactory: () => new BaseConfigService(config),
|
||||
configService: ConfigService,
|
||||
configServiceFactory: () => new ConfigService(config),
|
||||
}),
|
||||
AuthModule,
|
||||
// AuthModule.forRoot({
|
||||
// existingUserService: UserService,
|
||||
// existingRoleGuard: RedRoleGuard,
|
||||
// }),
|
||||
CachingModule.forRoot(UI_CACHES),
|
||||
PdfViewerModule,
|
||||
ToastrModule.forRoot({
|
||||
@ -155,6 +160,10 @@ export const appModuleFactory = (config: AppConfig) => {
|
||||
provide: ErrorHandler,
|
||||
useClass: GlobalErrorHandler,
|
||||
},
|
||||
{
|
||||
provide: BaseUserService,
|
||||
useExisting: UserService,
|
||||
},
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
multi: true,
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
|
||||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { AdminDialogService } from '../../../services/admin-dialog.service';
|
||||
import { BaseFormComponent, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { BaseFormComponent, IconButtonTypes, IProfileUpdateRequest, LoadingService, Toaster } from '@iqser/common-ui';
|
||||
import { rolesTranslations } from '@translations/roles-translations';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { IProfileUpdateRequest, User } from '@red/domain';
|
||||
import { User } from '@red/domain';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { HttpStatusCode } from '@angular/common/http';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
@ -53,7 +53,7 @@ export class UserDetailsComponent extends BaseFormComponent implements OnChanges
|
||||
...prev,
|
||||
[role]: [
|
||||
{
|
||||
value: this.user && this.user.hasRole(role),
|
||||
value: this.user && this.user.has(role),
|
||||
disabled: this.shouldBeDisabled(role),
|
||||
},
|
||||
],
|
||||
|
||||
@ -52,7 +52,7 @@
|
||||
<div class="center cell">
|
||||
<mat-slide-toggle
|
||||
(toggleChange)="toggleActive(user)"
|
||||
[checked]="user.isActive"
|
||||
[checked]="user.hasAnyRole"
|
||||
[disabled]="!canDeactivate(user)"
|
||||
color="primary"
|
||||
></mat-slide-toggle>
|
||||
|
||||
@ -109,7 +109,7 @@ export class UserListingScreenComponent extends ListingComponent<User> implement
|
||||
|
||||
async toggleActive(user: User) {
|
||||
this._loadingService.start();
|
||||
const requestBody = { ...user, roles: user.isActive ? [] : ['RED_USER'] };
|
||||
const requestBody = { ...user, roles: user.hasAnyRole ? [] : ['RED_USER'] };
|
||||
await firstValueFrom(this._userService.updateProfile(requestBody, user.id));
|
||||
await this.#loadData();
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ export class ConfigService {
|
||||
userId =>
|
||||
new NestedFilter({
|
||||
id: userId,
|
||||
label: this._userService.getNameForId(userId),
|
||||
label: this._userService.getName(userId),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@ -5,7 +5,6 @@ import { HttpClientModule } from '@angular/common/http';
|
||||
import { KeycloakAngularModule, KeycloakOptions, KeycloakService } from 'keycloak-angular';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { BASE_HREF } from '@iqser/common-ui';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
function getKeycloakOptions(configService: ConfigService, baseUrl: string) {
|
||||
let url: string = configService.values.OAUTH_URL;
|
||||
@ -37,11 +36,9 @@ function configureAutomaticRedirectToLoginScreen(keyCloakService: KeycloakServic
|
||||
|
||||
export function keycloakInitializer(keycloakService: KeycloakService, configService: ConfigService, baseUrl: string): () => Promise<void> {
|
||||
return () =>
|
||||
firstValueFrom(configService.loadLocalConfig()).then(() =>
|
||||
keycloakService
|
||||
.init(getKeycloakOptions(configService, baseUrl))
|
||||
.then(() => configureAutomaticRedirectToLoginScreen(keycloakService)),
|
||||
);
|
||||
keycloakService
|
||||
.init(getKeycloakOptions(configService, baseUrl))
|
||||
.then(() => configureAutomaticRedirectToLoginScreen(keycloakService));
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
|
||||
@ -1,71 +1,51 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { LoadingService } from '@iqser/common-ui';
|
||||
import { Observable } from 'rxjs';
|
||||
import { RoleGuard } from '@iqser/common-ui';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class RedRoleGuard implements CanActivate {
|
||||
constructor(
|
||||
protected readonly _router: Router,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _userService: UserService,
|
||||
) {}
|
||||
export class RedRoleGuard extends RoleGuard {
|
||||
protected readonly _userService = inject(UserService);
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
|
||||
return new Observable(obs => {
|
||||
if (!this._userService.currentUser.hasAnyREDRoles) {
|
||||
this._router.navigate(['/auth-error']);
|
||||
this._loadingService.stop();
|
||||
obs.next(false);
|
||||
obs.complete();
|
||||
return;
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
|
||||
const currentUser = this._userService.currentUser;
|
||||
|
||||
if (!currentUser.hasAnyREDRoles) {
|
||||
await this._router.navigate(['/auth-error']);
|
||||
this._loadingService.stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
// we have at least 1 RED Role -> if it's not user he must be an admin
|
||||
if (currentUser.isUserAdmin && !currentUser.isAdmin && state.url.includes('admin') && !state.url.includes('users')) {
|
||||
await this._router.navigate(['/main/admin/users']);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
currentUser.isUserAdmin &&
|
||||
!currentUser.isAdmin &&
|
||||
!currentUser.isUser &&
|
||||
!(state.url.startsWith('/main/admin/users') || state.url.startsWith('/main/my-profile'))
|
||||
) {
|
||||
await this._router.navigate(['/main/admin/users']);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (route.data.requiredRoles) {
|
||||
if (currentUser.hasAny(route.data.requiredRoles)) {
|
||||
return true;
|
||||
}
|
||||
// we have at least 1 RED Role -> if it's not user he must be admin
|
||||
|
||||
if (
|
||||
this._userService.currentUser.isUserAdmin &&
|
||||
!this._userService.currentUser.isAdmin &&
|
||||
state.url.includes('admin') &&
|
||||
!state.url.includes('users')
|
||||
) {
|
||||
this._router.navigate(['/main/admin/users']);
|
||||
obs.next(false);
|
||||
obs.complete();
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this._userService.currentUser.isUserAdmin &&
|
||||
!this._userService.currentUser.isAdmin &&
|
||||
!this._userService.currentUser.isUser &&
|
||||
!(state.url.startsWith('/main/admin/users') || state.url.startsWith('/main/my-profile'))
|
||||
) {
|
||||
this._router.navigate(['/main/admin/users']);
|
||||
obs.next(false);
|
||||
obs.complete();
|
||||
return;
|
||||
}
|
||||
|
||||
if (route.data.requiredRoles) {
|
||||
if (this._userService.hasAnyRole(route.data.requiredRoles)) {
|
||||
obs.next(true);
|
||||
obs.complete();
|
||||
} else {
|
||||
if (!this._userService.currentUser.isUser) {
|
||||
this._router.navigate(['/main/admin']);
|
||||
} else {
|
||||
this._router.navigate(['/']);
|
||||
}
|
||||
obs.next(false);
|
||||
obs.complete();
|
||||
}
|
||||
if (!currentUser.isUser) {
|
||||
await this._router.navigate(['/main/admin']);
|
||||
} else {
|
||||
obs.next(true);
|
||||
obs.complete();
|
||||
await this._router.navigate(['/']);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@ export class DossierDetailsComponent {
|
||||
const dossierRequest: IDossierRequest = { ...dossier, ownerId: owner.id };
|
||||
await firstValueFrom(this._dossiersService.createOrUpdate(dossierRequest));
|
||||
|
||||
const ownerName = this._userService.getNameForId(owner.id);
|
||||
const ownerName = this._userService.getName(owner.id);
|
||||
const dossierName = dossier.dossierName;
|
||||
this._toaster.info(_('assignment.owner'), { params: { ownerName, dossierName } });
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@ export class DossierOverviewScreenHeaderComponent implements OnInit {
|
||||
const fileName = this.dossier.dossierName + '.export.csv';
|
||||
const mapper = (file?: File) => ({
|
||||
...file,
|
||||
assignee: this._userService.getNameForId(file.assignee) || '-',
|
||||
assignee: this._userService.getName(file.assignee) || '-',
|
||||
primaryAttribute: this._primaryFileAttributeService.getPrimaryFileAttributeValue(file, this.dossier.dossierTemplateId),
|
||||
});
|
||||
const fileFields = [
|
||||
|
||||
@ -284,7 +284,7 @@ export class ConfigService {
|
||||
peopleFilters.push(
|
||||
new NestedFilter({
|
||||
id: userId,
|
||||
label: this._userService.getNameForId(userId),
|
||||
label: this._userService.getName(userId),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@ -142,7 +142,7 @@ export class ConfigService {
|
||||
userId =>
|
||||
new NestedFilter({
|
||||
id: userId,
|
||||
label: this._userService.getNameForId(userId),
|
||||
label: this._userService.getName(userId),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@ -97,7 +97,7 @@ export class UserManagementComponent {
|
||||
|
||||
async assignReviewer(file: File, user: User | string) {
|
||||
const assigneeId = typeof user === 'string' ? user : user?.id;
|
||||
const reviewerName = this.userService.getNameForId(assigneeId);
|
||||
const reviewerName = this.userService.getName(assigneeId);
|
||||
|
||||
const { dossierId, filename } = file;
|
||||
this.loadingService.start();
|
||||
|
||||
@ -196,7 +196,7 @@ export class SearchScreenComponent extends ListingComponent<ISearchListItem> imp
|
||||
const assignee = this._routeAssignee;
|
||||
const assigneeToFilter = (userId: string) => {
|
||||
const checked = assignee === userId;
|
||||
return new NestedFilter({ id: userId, label: this._userService.getNameForId(userId), checked });
|
||||
return new NestedFilter({ id: userId, label: this._userService.getName(userId), checked });
|
||||
};
|
||||
const assigneeFilter: IFilterGroup = {
|
||||
slug: 'assignee',
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { Dossier, IDossierRequest } from '@red/domain';
|
||||
@ -135,7 +135,7 @@ export class EditDossierTeamComponent implements EditDossierSectionInterface, On
|
||||
|
||||
setMembersSelectOptions(value = this.searchQuery): void {
|
||||
this.membersSelectOptions = this.userService.eligibleUsers
|
||||
.filter(user => this.userService.getNameForId(user.id).toLowerCase().includes(value.toLowerCase()))
|
||||
.filter(user => this.userService.getName(user.id).toLowerCase().includes(value.toLowerCase()))
|
||||
.filter(user => this.selectedOwnerId !== user.id)
|
||||
.map(user => user.id);
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ export class InitialsAvatarComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
get disabled(): boolean {
|
||||
return this._user && !this._isSystemUser && !this._user.isActive;
|
||||
return this._user && !this._isSystemUser && !this._user.hasAnyRole;
|
||||
}
|
||||
|
||||
get isCurrentUser(): boolean {
|
||||
|
||||
@ -54,6 +54,6 @@ export class NamePipe implements PipeTransform {
|
||||
if (!user) {
|
||||
return undefined;
|
||||
}
|
||||
return typeof user === 'string' ? this._userService.getNameForId(user) : user.name;
|
||||
return typeof user === 'string' ? this._userService.getName(user) : user.name;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,55 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import packageInfo from '../../../../../package.json';
|
||||
import envConfig from '../../assets/config/config.json';
|
||||
import { CacheApiService, wipeAllCaches } from '@iqser/common-ui';
|
||||
import { Observable } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { BaseConfigService } from '@iqser/common-ui';
|
||||
import { AppConfig } from '@red/domain';
|
||||
|
||||
const version = packageInfo.version;
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ConfigService {
|
||||
constructor(
|
||||
private readonly _httpClient: HttpClient,
|
||||
private readonly _cacheApiService: CacheApiService,
|
||||
private readonly _titleService: Title,
|
||||
) {
|
||||
this._checkFrontendVersion();
|
||||
}
|
||||
|
||||
private _values: AppConfig = { ...envConfig, FRONTEND_APP_VERSION: version } as const;
|
||||
|
||||
get values() {
|
||||
return this._values;
|
||||
}
|
||||
|
||||
loadLocalConfig(): Observable<any> {
|
||||
return this._httpClient.get<any>('/assets/config/config.json').pipe(
|
||||
tap(config => {
|
||||
console.log('[REDACTION] Started with local config: ', config);
|
||||
this._values = config;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
updateDisplayName(name: string): void {
|
||||
this._values = { ...this._values, APP_NAME: name } as const;
|
||||
this._titleService.setTitle(this._values.APP_NAME || 'RedactManager');
|
||||
}
|
||||
|
||||
private _checkFrontendVersion(): void {
|
||||
this._cacheApiService.getCachedValue('FRONTEND_APP_VERSION').then(async lastVersion => {
|
||||
console.log('[REDACTION] Last app version: ', lastVersion, ' current version ', version);
|
||||
if (lastVersion !== version) {
|
||||
console.warn('[REDACTION] Version-mismatch - wiping caches!');
|
||||
await wipeAllCaches();
|
||||
}
|
||||
await this._cacheApiService.cacheValue('FRONTEND_APP_VERSION', version);
|
||||
});
|
||||
}
|
||||
}
|
||||
@Injectable()
|
||||
export class ConfigService extends BaseConfigService<AppConfig> {}
|
||||
|
||||
@ -71,7 +71,7 @@ export class TrashService extends EntitiesService<TrashItem, TrashItem> {
|
||||
this._systemPreferencesService.values.softDeleteCleanupTime,
|
||||
this._permissionsService.canRestoreDossier(dossier),
|
||||
this._permissionsService.canHardDeleteDossier(dossier),
|
||||
this._userService.getNameForId(dossier.ownerId),
|
||||
this._userService.getName(dossier.ownerId),
|
||||
),
|
||||
),
|
||||
switchMap(dossiers => this._dossierStatsService.getFor(dossiers.map(d => d.id)).pipe(map(() => dossiers))),
|
||||
@ -81,7 +81,7 @@ export class TrashService extends EntitiesService<TrashItem, TrashItem> {
|
||||
getFiles(dossierIds = this._activeDossiersService.all.map(d => d.id)): Observable<TrashFile[]> {
|
||||
return this._post<Record<string, IFile[]>>(dossierIds, 'status/softdeleted').pipe(
|
||||
map(res => flatMap(Object.values(res))),
|
||||
mapEach(file => new File(file, this._userService.getNameForId(file.assignee))),
|
||||
mapEach(file => new File(file, this._userService.getName(file.assignee))),
|
||||
mapEach(file => {
|
||||
const dossier = this._activeDossiersService.find(file.dossierId);
|
||||
return new TrashFile(
|
||||
@ -90,7 +90,7 @@ export class TrashService extends EntitiesService<TrashItem, TrashItem> {
|
||||
this._systemPreferencesService.values.softDeleteCleanupTime,
|
||||
this._permissionsService.canRestoreFile(file, dossier),
|
||||
this._permissionsService.canHardDeleteFile(file, dossier),
|
||||
this._userService.getNameForId(file.assignee),
|
||||
this._userService.getName(file.assignee),
|
||||
dossier.dossierName,
|
||||
);
|
||||
}),
|
||||
|
||||
@ -27,7 +27,7 @@ export class FilesService extends EntitiesService<IFile, File> {
|
||||
/** Reload dossier files + stats. */
|
||||
loadAll(dossierId: string) {
|
||||
const files$ = this.getFor(dossierId).pipe(
|
||||
mapEach(file => new File(file, this._userService.getNameForId(file.assignee))),
|
||||
mapEach(file => new File(file, this._userService.getName(file.assignee))),
|
||||
tap(() => this._logger.info('[FILE] Loaded')),
|
||||
);
|
||||
const loadStats$ = files$.pipe(switchMap(files => this._dossierStatsService.getFor([dossierId]).pipe(map(() => files))));
|
||||
@ -36,7 +36,7 @@ export class FilesService extends EntitiesService<IFile, File> {
|
||||
|
||||
reload(dossierId: string, file: File): Observable<boolean> {
|
||||
return super._getOne([dossierId, file.id]).pipe(
|
||||
map(_file => new File(_file, this._userService.getNameForId(_file.assignee))),
|
||||
map(_file => new File(_file, this._userService.getName(_file.assignee))),
|
||||
switchMap(_file => this._dossierStatsService.getFor([dossierId]).pipe(map(() => _file))),
|
||||
map(_file => this._filesMapService.replace([_file])),
|
||||
tap(() => this._logger.info('[FILE] Reloaded')),
|
||||
|
||||
@ -102,6 +102,6 @@ export class NotificationsService extends EntitiesService<INotification, Notific
|
||||
}
|
||||
|
||||
private _getUsername(userId: string | undefined) {
|
||||
return this._userService.getNameForId(userId) || this._translateService.instant(_('unknown'));
|
||||
return this._userService.getName(userId) || this._translateService.instant(_('unknown'));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,32 +1,13 @@
|
||||
import { inject, Inject, Injectable } from '@angular/core';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import jwt_decode from 'jwt-decode';
|
||||
import { ICreateUserRequest, IMyProfileUpdateRequest, IProfileUpdateRequest, IResetPasswordRequest, IUser, User } from '@red/domain';
|
||||
import { BASE_HREF, CacheApiService, EntitiesService, List, mapEach, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { BehaviorSubject, firstValueFrom, Observable } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { User } from '@red/domain';
|
||||
import { BaseUserService, IIqserUser } from '@iqser/common-ui';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class UserService extends EntitiesService<IUser, User> {
|
||||
readonly currentUser$: Observable<User>;
|
||||
export class UserService extends BaseUserService<IIqserUser, User> {
|
||||
protected readonly _defaultModelPath = 'user';
|
||||
protected readonly _entityClass = User;
|
||||
readonly #currentUser$ = new BehaviorSubject<User>(null);
|
||||
|
||||
constructor(
|
||||
@Inject(BASE_HREF) private readonly _baseHref: string,
|
||||
private readonly _keycloakService: KeycloakService,
|
||||
private readonly _cacheApiService: CacheApiService,
|
||||
) {
|
||||
super();
|
||||
this.currentUser$ = this.#currentUser$.asObservable();
|
||||
}
|
||||
|
||||
get currentUser(): User {
|
||||
return this.#currentUser$.value;
|
||||
}
|
||||
|
||||
get managerUsers(): User[] {
|
||||
return this.all.filter(user => user.isManager);
|
||||
@ -36,109 +17,18 @@ export class UserService extends EntitiesService<IUser, User> {
|
||||
return this.all.filter(user => user.isUser || user.isManager);
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
console.log('[Redaction] Initializing users for: ', this.currentUser);
|
||||
if (!this.currentUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.currentUser.isUserAdmin || this.currentUser.isUser || this.currentUser.isAdmin) {
|
||||
await firstValueFrom(this.loadAll());
|
||||
}
|
||||
}
|
||||
|
||||
logout() {
|
||||
this._cacheApiService.wipeCaches().then();
|
||||
this._keycloakService.logout().then();
|
||||
}
|
||||
|
||||
loadAll() {
|
||||
const all$ = this.currentUser.isUserAdmin ? this.getUsers() : this.getUsers(true);
|
||||
|
||||
return all$.pipe(
|
||||
mapEach(user => new User(user, user.roles, user.userId)),
|
||||
tap(users => this.setEntities(users)),
|
||||
);
|
||||
}
|
||||
|
||||
async loadCurrentUser(): Promise<User> {
|
||||
const token = await this._keycloakService.getToken();
|
||||
const decoded = jwt_decode(token);
|
||||
const userId = (<{ sub: string }>decoded).sub;
|
||||
|
||||
const roles = this._keycloakService.getUserRoles(true).filter(role => role.startsWith('RED_'));
|
||||
let profile;
|
||||
try {
|
||||
profile = await this._keycloakService.loadUserProfile(true);
|
||||
} catch (e) {
|
||||
await this._keycloakService.logout();
|
||||
console.log(e);
|
||||
if (this.currentUser.isUserAdmin || this.currentUser.isUser || this.currentUser.isAdmin) {
|
||||
return super.loadAll();
|
||||
}
|
||||
|
||||
const user = new User(profile, roles, userId);
|
||||
this.replace(user);
|
||||
|
||||
this.#currentUser$.next(this.find(userId));
|
||||
return user;
|
||||
}
|
||||
|
||||
getNameForId(userId: string): string | undefined {
|
||||
return this.find(userId)?.name;
|
||||
getAll() {
|
||||
const url = this.currentUser.isUserAdmin ? this._defaultModelPath : `${this._defaultModelPath}/red`;
|
||||
return super.getAll(url);
|
||||
}
|
||||
|
||||
hasAnyRole(requiredRoles: string[], user = this.currentUser): boolean {
|
||||
if (requiredRoles?.length > 0) {
|
||||
for (const role of requiredRoles) {
|
||||
if (user.hasRole(role)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getUsers(onlyRed = false, refreshCache = true): Observable<IUser[]> {
|
||||
const url = onlyRed ? `${this._defaultModelPath}/red` : this._defaultModelPath;
|
||||
return super.getAll(url, [{ key: 'refreshCache', value: refreshCache }]);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
updateProfile(@RequiredParam() body: IProfileUpdateRequest, @RequiredParam() userId: string) {
|
||||
return this._post<unknown>(body, `${this._defaultModelPath}/profile/${userId}`);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
updateMyProfile(@RequiredParam() body: IMyProfileUpdateRequest) {
|
||||
return this._post<unknown>(body, `${this._defaultModelPath}/my-profile`);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
resetPassword(@RequiredParam() body: IResetPasswordRequest, @RequiredParam() userId: string) {
|
||||
return this._post<unknown>(body, `${this._defaultModelPath}/${userId}/reset-password`);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
create(@RequiredParam() body: ICreateUserRequest) {
|
||||
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): User | undefined {
|
||||
if (id?.toLowerCase() === 'system') {
|
||||
return new User({ username: 'System' }, [], 'system');
|
||||
}
|
||||
if (!id) {
|
||||
return undefined;
|
||||
}
|
||||
return super.find(id) || new User({ username: 'Deleted User' }, [], 'deleted');
|
||||
}
|
||||
protected readonly _rolesFilter = (role: string) => role.startsWith('RED_');
|
||||
}
|
||||
|
||||
export function getCurrentUser() {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { catchError, filter, mergeMap, switchMap, take, tap } from 'rxjs/operators';
|
||||
import { catchError, filter, mergeMap, switchMap, tap } from 'rxjs/operators';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { firstValueFrom, from, map, of, throwError } from 'rxjs';
|
||||
import { firstValueFrom, map, of, throwError } from 'rxjs';
|
||||
import { KeycloakEventType, KeycloakService } from 'keycloak-angular';
|
||||
import { GeneralSettingsService } from '@services/general-settings.service';
|
||||
import { LanguageService } from '@iqser/common-ui';
|
||||
@ -35,10 +35,10 @@ export function configurationInitializer(
|
||||
const setup = keycloakService.keycloakEvents$.pipe(
|
||||
filter(event => event.type === KeycloakEventType.OnReady),
|
||||
map(() => featuresService.loadConfig()),
|
||||
switchMap(() => from(keycloakService.isLoggedIn())),
|
||||
switchMap(loggedIn => (!loggedIn ? throwError('Not Logged In') : of({}))),
|
||||
switchMap(() => from(userService.loadCurrentUser())),
|
||||
switchMap(user => (!user.hasAnyREDRoles ? throwError('Not user has no red roles') : of({}))),
|
||||
switchMap(() => keycloakService.isLoggedIn()),
|
||||
switchMap(loggedIn => (!loggedIn ? throwError(() => 'Not Logged In') : of({}))),
|
||||
switchMap(() => userService.initialize()),
|
||||
switchMap(() => (!userService.currentUser.hasAnyREDRoles ? throwError(() => 'Not user has no red roles') : of({}))),
|
||||
mergeMap(() => generalSettingsService.getGeneralConfigurations()),
|
||||
tap(configuration => configService.updateDisplayName(configuration.displayName)),
|
||||
switchMap(() => systemPreferencesService.loadPreferences()),
|
||||
@ -52,9 +52,7 @@ export function configurationInitializer(
|
||||
lastDossierTemplateRedirect(baseHref.replace('/', ''), userPreferenceService);
|
||||
}),
|
||||
switchMap(() => languageService.setInitialLanguage()),
|
||||
tap(() => userService.initialize()),
|
||||
switchMap(() => licenseService.loadLicense()),
|
||||
take(1),
|
||||
);
|
||||
return () => firstValueFrom(setup);
|
||||
}
|
||||
|
||||
@ -83,7 +83,7 @@ export const dossierStateChecker = (dw: Dossier, filter: INestedFilter) =>
|
||||
export const dossierApproverChecker = (dw: Dossier, filter: INestedFilter) => dw.approverIds.includes(filter.id);
|
||||
|
||||
export const userTypeFilters: { [key in UserType]: (user: User) => boolean } = {
|
||||
INACTIVE: (user: User) => !user.isActive,
|
||||
INACTIVE: (user: User) => !user.hasAnyRole,
|
||||
REGULAR: (user: User) => user.roles.length === 1 && user.roles[0] === 'RED_USER',
|
||||
RED_MANAGER: (user: User) => user.isManager && !user.isAdmin,
|
||||
MANAGER_ADMIN: (user: User) => user.isManager && user.isAdmin,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"ADMIN_CONTACT_NAME": null,
|
||||
"ADMIN_CONTACT_URL": null,
|
||||
"API_URL": "https://dev-05.iqser.cloud/redaction-gateway-v1",
|
||||
"API_URL": "https://dev-04.iqser.cloud/redaction-gateway-v1",
|
||||
"APP_NAME": "RedactManager",
|
||||
"AUTO_READ_TIME": 3,
|
||||
"BACKEND_APP_VERSION": "4.4.40",
|
||||
@ -11,7 +11,7 @@
|
||||
"MAX_RETRIES_ON_SERVER_ERROR": 3,
|
||||
"OAUTH_CLIENT_ID": "redaction",
|
||||
"OAUTH_IDP_HINT": null,
|
||||
"OAUTH_URL": "https://dev-05.iqser.cloud/auth/realms/redaction",
|
||||
"OAUTH_URL": "https://dev-04.iqser.cloud/auth/realms/redaction",
|
||||
"RECENT_PERIOD_IN_HOURS": 24,
|
||||
"SELECTION_MODE": "structural",
|
||||
"MANUAL_BASE_URL": "https://docs.redactmanager.com/preview"
|
||||
|
||||
@ -1,20 +0,0 @@
|
||||
import { List } from '@iqser/common-ui';
|
||||
|
||||
export interface ICreateUserRequest {
|
||||
/**
|
||||
* Email of user.
|
||||
*/
|
||||
email?: string;
|
||||
/**
|
||||
* First name of user.
|
||||
*/
|
||||
firstName?: string;
|
||||
/**
|
||||
* Last name of user.
|
||||
*/
|
||||
lastName?: string;
|
||||
/**
|
||||
* The list of RED_* roles.
|
||||
*/
|
||||
roles?: List;
|
||||
}
|
||||
@ -1,8 +1,3 @@
|
||||
export * from './user.model';
|
||||
export * from './user';
|
||||
export * from './my-profile-update.request';
|
||||
export * from './profile-update.request';
|
||||
export * from './profile';
|
||||
export * from './types';
|
||||
export * from './create-user.request';
|
||||
export * from './reset-password.request';
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
export interface IMyProfileUpdateRequest {
|
||||
/**
|
||||
* Email of user.
|
||||
*/
|
||||
email?: string;
|
||||
/**
|
||||
* First name of user.
|
||||
*/
|
||||
firstName?: string;
|
||||
/**
|
||||
* Last name of user.
|
||||
*/
|
||||
lastName?: string;
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
import { IMyProfileUpdateRequest } from './my-profile-update.request';
|
||||
import { List } from '@iqser/common-ui';
|
||||
|
||||
export interface IProfileUpdateRequest extends IMyProfileUpdateRequest {
|
||||
/**
|
||||
* Roles.
|
||||
*/
|
||||
roles?: List;
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
export interface IResetPasswordRequest {
|
||||
password?: string;
|
||||
temporary?: boolean;
|
||||
}
|
||||
@ -1,32 +1,14 @@
|
||||
import { IListable, List } from '@iqser/common-ui';
|
||||
import { IIqserUser, IqserUser, List } from '@iqser/common-ui';
|
||||
import { KeycloakProfile } from 'keycloak-js';
|
||||
import { IUser } from './user';
|
||||
|
||||
export class User implements IUser, IListable {
|
||||
readonly email?: string;
|
||||
readonly username?: string;
|
||||
readonly firstName?: string;
|
||||
readonly lastName?: string;
|
||||
readonly name?: string;
|
||||
readonly searchKey: string;
|
||||
|
||||
readonly isActive = this.roles.length > 0;
|
||||
readonly isManager = this.hasRole('RED_MANAGER');
|
||||
readonly isUserAdmin = this.hasRole('RED_USER_ADMIN');
|
||||
readonly isUser = this.hasRole('RED_USER');
|
||||
readonly isAdmin = this.hasRole('RED_ADMIN');
|
||||
export class User extends IqserUser {
|
||||
readonly isManager = this.has('RED_MANAGER');
|
||||
readonly isUserAdmin = this.has('RED_USER_ADMIN');
|
||||
readonly isUser = this.has('RED_USER');
|
||||
readonly isAdmin = this.has('RED_ADMIN');
|
||||
readonly hasAnyREDRoles = this.isUser || this.isManager || this.isAdmin || this.isUserAdmin;
|
||||
|
||||
constructor(user: KeycloakProfile | IUser, readonly roles: List, readonly id: string) {
|
||||
this.email = user.email;
|
||||
this.username = user.username || this.email;
|
||||
this.firstName = user.firstName;
|
||||
this.lastName = user.lastName;
|
||||
this.name = this.firstName && this.lastName ? `${this.firstName} ${this.lastName}` : this.username;
|
||||
this.searchKey = `${this.name || '-'}${this.username || '-'}${this.email || ''}`;
|
||||
}
|
||||
|
||||
hasRole(role: string): boolean {
|
||||
return this.roles.indexOf(role) >= 0;
|
||||
constructor(user: KeycloakProfile | IIqserUser, readonly roles: List, readonly userId: string) {
|
||||
super(user, roles, userId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
/**
|
||||
* Object containing information of user and roles.
|
||||
*/
|
||||
import { IProfileUpdateRequest } from './profile-update.request';
|
||||
|
||||
export interface IUser extends IProfileUpdateRequest {
|
||||
/**
|
||||
* Id of user.
|
||||
*/
|
||||
readonly userId?: string;
|
||||
/**
|
||||
* Username for login.
|
||||
*/
|
||||
readonly username?: string;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user