RED-3800, refactor multitenancy
This commit is contained in:
parent
59fbd1f78f
commit
d1c0559099
@ -21,7 +21,6 @@ import { CircleButtonComponent, IconButtonComponent } from '../buttons';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { EmptyStateComponent } from '../empty-state';
|
||||
import { InputWithActionComponent, RoundCheckboxComponent } from '../inputs';
|
||||
import { TenantPipe } from '../tenants/tenant.pipe';
|
||||
|
||||
const matModules = [MatTooltipModule, MatIconModule];
|
||||
const components = [
|
||||
@ -51,7 +50,6 @@ const modules = [DragDropModule, TranslateModule, IqserFiltersModule, ScrollingM
|
||||
RoundCheckboxComponent,
|
||||
InputWithActionComponent,
|
||||
SyncWidthDirective,
|
||||
TenantPipe,
|
||||
],
|
||||
})
|
||||
export class IqserListingModule {}
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
[class.help-mode-active]="helpModeService?.isHelpModeActive$ | async"
|
||||
[id]="'item-' + entity.id"
|
||||
[ngClass]="getTableItemClasses(entity)"
|
||||
[routerLink]="entity.routerLink | tenant"
|
||||
[routerLink]="entity.routerLink"
|
||||
>
|
||||
<iqser-table-item
|
||||
(click)="multiSelect(entity, $event)"
|
||||
@ -31,7 +31,7 @@
|
||||
[class.help-mode-active]="helpModeService?.isHelpModeActive$ | async"
|
||||
[id]="'item-' + entity.id"
|
||||
[ngClass]="getTableItemClasses(entity)"
|
||||
[routerLink]="entity.routerLink | tenant"
|
||||
[routerLink]="entity.routerLink"
|
||||
>
|
||||
<iqser-table-item
|
||||
(click)="multiSelect(entity, $event)"
|
||||
|
||||
@ -2,12 +2,12 @@ import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/c
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { getConfig } from './iqser-config.service';
|
||||
import { BASE_HREF } from '../utils';
|
||||
import { UI_ROOT_PATH_FN } from '../utils';
|
||||
|
||||
@Injectable()
|
||||
export class ApiPathInterceptor implements HttpInterceptor {
|
||||
readonly #config = getConfig();
|
||||
readonly #baseHref = inject(BASE_HREF);
|
||||
readonly #convertPath = inject(UI_ROOT_PATH_FN);
|
||||
|
||||
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||
if (!req.url.startsWith('/assets')) {
|
||||
@ -15,7 +15,7 @@ export class ApiPathInterceptor implements HttpInterceptor {
|
||||
return next.handle(req.clone({ url: apiUrl }));
|
||||
}
|
||||
|
||||
const url = this.#baseHref + req.url;
|
||||
const url = this.#convertPath(req.url);
|
||||
return next.handle(req.clone({ url }));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { BASE_HREF, List } from '../utils';
|
||||
import { List } from '../utils';
|
||||
import { GenericService } from './generic.service';
|
||||
import { APP_BASE_HREF } from '@angular/common';
|
||||
|
||||
export type UserAttributes = Record<string, List>;
|
||||
|
||||
@ -12,9 +13,9 @@ const KEYS = {
|
||||
|
||||
@Injectable()
|
||||
export abstract class IqserUserPreferenceService extends GenericService<UserAttributes> {
|
||||
#userAttributes: UserAttributes = {};
|
||||
protected abstract readonly _devFeaturesEnabledKey: string;
|
||||
protected readonly _serviceName: string = 'tenant-user-management';
|
||||
#userAttributes: UserAttributes = {};
|
||||
|
||||
get userAttributes(): UserAttributes {
|
||||
return this.#userAttributes;
|
||||
@ -72,7 +73,7 @@ export abstract class IqserUserPreferenceService extends GenericService<UserAttr
|
||||
@Injectable()
|
||||
export class DefaultUserPreferenceService extends IqserUserPreferenceService {
|
||||
protected readonly _defaultModelPath = 'attributes';
|
||||
protected readonly _devFeaturesEnabledKey = inject(BASE_HREF) + '.enable-dev-features';
|
||||
protected readonly _devFeaturesEnabledKey = inject(APP_BASE_HREF) + '.enable-dev-features';
|
||||
}
|
||||
|
||||
export function isIqserDevMode() {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
export * from './keycloak-initializer';
|
||||
export * from './services';
|
||||
export * from './tenant.pipe';
|
||||
export * from './tenants.module';
|
||||
export * from './tenant-select/tenant-select.component';
|
||||
export * from './services/keycloak-status.service';
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { BASE_HREF, IqserAppConfig } from '../utils';
|
||||
import { IqserAppConfig } from '../utils';
|
||||
import { KeycloakOptions, KeycloakService } from 'keycloak-angular';
|
||||
import { KeycloakStatusService } from './services/keycloak-status.service';
|
||||
import { inject } from '@angular/core';
|
||||
import { getConfig } from '../services';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { Router } from '@angular/router';
|
||||
import { APP_BASE_HREF } from '@angular/common';
|
||||
|
||||
export function getKeycloakOptions(baseUrl: string, config: IqserAppConfig, tenant: string): KeycloakOptions {
|
||||
let oauthUrl = config.OAUTH_URL;
|
||||
@ -48,7 +49,7 @@ export async function keycloakInitializer(tenant: string) {
|
||||
const router = inject(Router);
|
||||
const keycloakService = inject(KeycloakService);
|
||||
const keycloakStatusService = inject(KeycloakStatusService);
|
||||
const baseHref = inject(BASE_HREF);
|
||||
const baseHref = inject(APP_BASE_HREF);
|
||||
const config = getConfig();
|
||||
|
||||
const keycloakOptions = getKeycloakOptions(baseHref, config, tenant);
|
||||
|
||||
@ -2,15 +2,15 @@ import { inject, Injectable } from '@angular/core';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import { getConfig } from '../../services';
|
||||
import { TenantsService } from '../index';
|
||||
import { BASE_HREF } from '../../utils';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { APP_BASE_HREF } from '@angular/common';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class KeycloakStatusService {
|
||||
readonly #keycloakService = inject(KeycloakService);
|
||||
readonly #config = getConfig();
|
||||
readonly #tenantsService = inject(TenantsService);
|
||||
readonly #baseHref = inject(BASE_HREF);
|
||||
readonly #baseHref = inject(APP_BASE_HREF);
|
||||
readonly #logger = inject(NGXLogger);
|
||||
|
||||
createLoginUrlAndExecute(username?: string | null) {
|
||||
@ -23,11 +23,10 @@ export class KeycloakStatusService {
|
||||
});
|
||||
|
||||
this.#logger.info('[KEYCLOAK] Redirect to login url: ', url);
|
||||
|
||||
window.location.href = url;
|
||||
window.location.assign(url);
|
||||
} else {
|
||||
this.#logger.error('[KEYCLOAK] Instance not found, redirect to tenant select');
|
||||
window.location.href = this.createLoginUrl();
|
||||
window.location.assign(window.origin);
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,14 +47,14 @@ export class KeycloakStatusService {
|
||||
|
||||
createLoginUrl(tenant?: string) {
|
||||
if (tenant && window.location.href.indexOf('/' + tenant + '/') > 0) {
|
||||
return window.location.href;
|
||||
return window.location.href + '/main';
|
||||
}
|
||||
|
||||
const url = window.location.origin + this.#baseHref;
|
||||
const origin = window.location.origin;
|
||||
if (tenant) {
|
||||
return url + '/' + tenant;
|
||||
return origin + '/ui/' + tenant + '/main';
|
||||
}
|
||||
|
||||
return url;
|
||||
return origin + '/ui';
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,10 +5,10 @@ import { KeycloakService } from 'keycloak-angular';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { LoadingService } from '../../loading';
|
||||
import { getConfig } from '../../services';
|
||||
import { BASE_HREF } from '../../utils';
|
||||
import { getKeycloakOptions } from '../keycloak-initializer';
|
||||
import { IStoredTenantId, TenantsService } from '../services';
|
||||
import { KeycloakStatusService } from '../services/keycloak-status.service';
|
||||
import { APP_BASE_HREF } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
templateUrl: './tenant-select.component.html',
|
||||
@ -16,7 +16,8 @@ import { KeycloakStatusService } from '../services/keycloak-status.service';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class TenantSelectComponent {
|
||||
protected readonly baseHref = inject(BASE_HREF);
|
||||
@Input() isLoggedOut = false;
|
||||
protected readonly baseHref = inject(APP_BASE_HREF);
|
||||
protected readonly logger = inject(NGXLogger);
|
||||
protected readonly tenantsService = inject(TenantsService);
|
||||
protected storedTenants: IStoredTenantId[] = [];
|
||||
@ -29,7 +30,6 @@ export class TenantSelectComponent {
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
tenantId: ['', Validators.required],
|
||||
});
|
||||
@Input() isLoggedOut = false;
|
||||
|
||||
constructor() {
|
||||
this.#loadStoredTenants();
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
import { inject, Pipe, PipeTransform } from '@angular/core';
|
||||
import { TenantsService } from './services';
|
||||
|
||||
@Pipe({
|
||||
name: 'tenant',
|
||||
pure: true,
|
||||
standalone: true,
|
||||
})
|
||||
export class TenantPipe implements PipeTransform {
|
||||
readonly #tenantsService = inject(TenantsService);
|
||||
|
||||
transform(value: string | string[]): string | undefined {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
const _value = Array.isArray(value) ? value.join('/') : value;
|
||||
return '/' + this.#tenantsService.activeTenantId + _value;
|
||||
}
|
||||
}
|
||||
@ -5,13 +5,12 @@ import { KeycloakService } from 'keycloak-angular';
|
||||
import { KeycloakProfile } from 'keycloak-js';
|
||||
import { BehaviorSubject, firstValueFrom, Observable, throwError } from 'rxjs';
|
||||
import { catchError, tap } from 'rxjs/operators';
|
||||
import { IProfile } from '../../../../../red-domain/src';
|
||||
import { CacheApiService } from '../../caching';
|
||||
import { EntitiesService } from '../../listing';
|
||||
import { IqserPermissionsService, IqserRolesService } from '../../permissions';
|
||||
import { QueryParam, Toaster } from '../../services';
|
||||
import { KeycloakStatusService } from '../../tenants';
|
||||
import { BASE_HREF, List, mapEach } from '../../utils';
|
||||
import { List, mapEach, UI_ROOT } from '../../utils';
|
||||
import { IqserUser } from '../iqser-user.model';
|
||||
import { ICreateUserRequest } from '../types/create-user.request';
|
||||
import { IMyProfileUpdateRequest } from '../types/my-profile-update.request';
|
||||
@ -24,6 +23,7 @@ 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;
|
||||
@ -35,9 +35,8 @@ export abstract class IqserUserService<
|
||||
protected readonly _keycloakStatusService = inject(KeycloakStatusService);
|
||||
protected readonly _permissionsService = inject(IqserPermissionsService, { optional: true });
|
||||
protected readonly _rolesService = inject(IqserRolesService, { optional: true });
|
||||
protected readonly _baseHref = inject(BASE_HREF);
|
||||
protected readonly _serviceName: string = 'tenant-user-management';
|
||||
readonly currentUser$: Observable<Class | undefined>;
|
||||
readonly #uiRoot = inject(UI_ROOT);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -63,7 +62,7 @@ export abstract class IqserUserService<
|
||||
try {
|
||||
await this._keycloakService.loadUserProfile(true);
|
||||
await this._cacheApiService.wipeCaches();
|
||||
const redirectUri = window.location.origin + this._baseHref + '/?isLoggedOut=true';
|
||||
const redirectUri = window.location.origin + this.#uiRoot + '/?isLoggedOut=true';
|
||||
await this._keycloakService.logout(redirectUri);
|
||||
} catch (e) {
|
||||
console.log('Logout failed: ', e);
|
||||
|
||||
@ -1,35 +1,16 @@
|
||||
import { inject, InjectionToken } from '@angular/core';
|
||||
import { PlatformLocation } from '@angular/common';
|
||||
|
||||
export const BASE_HREF = new InjectionToken<string>('BASE_HREF', {
|
||||
export const UI_ROOT = new InjectionToken<string>('UI path root - different from BASE_HREF');
|
||||
export const UI_ROOT_PATH_FN = new InjectionToken<(path: string) => string>('Append UI root to path', {
|
||||
factory: () => {
|
||||
const baseUrl = inject(PlatformLocation).getBaseHrefFromDOM();
|
||||
if (!baseUrl) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (baseUrl[baseUrl.length - 1] === '/') {
|
||||
return baseUrl.substring(0, baseUrl.length - 1);
|
||||
}
|
||||
|
||||
console.log('Base URL:', baseUrl);
|
||||
|
||||
return baseUrl;
|
||||
},
|
||||
});
|
||||
|
||||
export type BaseHrefFn = (path: string) => string;
|
||||
|
||||
export const BASE_HREF_FN = new InjectionToken<BaseHrefFn>('Convert path function', {
|
||||
factory: () => {
|
||||
const baseUrl = inject(BASE_HREF);
|
||||
const root = inject(UI_ROOT);
|
||||
|
||||
return (path: string) => {
|
||||
if (path[0] === '/') {
|
||||
return baseUrl + path;
|
||||
return root + path;
|
||||
}
|
||||
|
||||
return baseUrl + '/' + path;
|
||||
return root + '/' + path;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user