RED-6523 - multitenancy

This commit is contained in:
Timo Bejan 2023-03-31 12:31:42 +03:00
parent bf157215b1
commit 1d9b5b4d69
30 changed files with 185 additions and 135 deletions

View File

@ -6,6 +6,7 @@ import {
IqserAuthGuard, IqserAuthGuard,
IqserPermissionsGuard, IqserPermissionsGuard,
IqserRoutes, IqserRoutes,
TenantResolveComponent,
} from '@iqser/common-ui'; } from '@iqser/common-ui';
import { RedRoleGuard } from '@users/red-role.guard'; import { RedRoleGuard } from '@users/red-role.guard';
import { BaseScreenComponent } from '@components/base-screen/base-screen.component'; import { BaseScreenComponent } from '@components/base-screen/base-screen.component';
@ -96,11 +97,14 @@ const dossierTemplateIdRoutes: IqserRoutes = [
const routes: IqserRoutes = [ const routes: IqserRoutes = [
{ {
path: '', path: '',
redirectTo: 'main', component: TenantResolveComponent,
pathMatch: 'full',
}, },
{ {
path: 'main', path: ':tenant',
component: TenantResolveComponent,
},
{
path: ':tenant/main',
component: BaseScreenComponent, component: BaseScreenComponent,
children: [ children: [
{ {
@ -212,7 +216,7 @@ const routes: IqserRoutes = [
], ],
}, },
{ {
path: 'auth-error', path: ':tenant/auth-error',
component: AuthErrorComponent, component: AuthErrorComponent,
canActivate: [IqserAuthGuard], canActivate: [IqserAuthGuard],
}, },

View File

@ -12,6 +12,7 @@ import {
CommonUiModule, CommonUiModule,
EmptyStateComponent, EmptyStateComponent,
HiddenActionDirective, HiddenActionDirective,
IconButtonComponent,
InputWithActionComponent, InputWithActionComponent,
IqserAllowDirective, IqserAllowDirective,
IqserDenyDirective, IqserDenyDirective,
@ -21,6 +22,7 @@ import {
IqserPermissionsService, IqserPermissionsService,
IqserTranslateModule, IqserTranslateModule,
IqserUsersModule, IqserUsersModule,
KeycloakStatusService,
LanguageService, LanguageService,
LogoComponent, LogoComponent,
MAX_RETRIES_ON_SERVER_ERROR, MAX_RETRIES_ON_SERVER_ERROR,
@ -29,6 +31,7 @@ import {
ServerErrorInterceptor, ServerErrorInterceptor,
SkeletonComponent, SkeletonComponent,
StopPropagationDirective, StopPropagationDirective,
TenantsModule,
ToastComponent, ToastComponent,
} from '@iqser/common-ui'; } from '@iqser/common-ui';
import { ToastrModule } from 'ngx-toastr'; import { ToastrModule } from 'ngx-toastr';
@ -66,13 +69,11 @@ import { AppConfig, ILoggerConfig } from '@red/domain';
import { SystemPreferencesService } from '@services/system-preferences.service'; import { SystemPreferencesService } from '@services/system-preferences.service';
import { PdfViewerModule } from './modules/pdf-viewer/pdf-viewer.module'; import { PdfViewerModule } from './modules/pdf-viewer/pdf-viewer.module';
import { LicenseService } from '@services/license.service'; import { LicenseService } from '@services/license.service';
import { TenantIdInterceptor } from '@utils/tenant-id-interceptor';
import { UI_CACHES } from '@utils/constants'; import { UI_CACHES } from '@utils/constants';
import { RedRoleGuard } from '@users/red-role.guard'; import { RedRoleGuard } from '@users/red-role.guard';
import { SkeletonTopBarComponent } from '@components/skeleton/skeleton-top-bar/skeleton-top-bar.component'; import { SkeletonTopBarComponent } from '@components/skeleton/skeleton-top-bar/skeleton-top-bar.component';
import { DossierSkeletonComponent } from '@components/skeleton/dossier-skeleton/dossier-skeleton.component'; import { DossierSkeletonComponent } from '@components/skeleton/dossier-skeleton/dossier-skeleton.component';
import { SkeletonStatsComponent } from '@components/skeleton/skeleton-stats/skeleton-stats.component'; import { SkeletonStatsComponent } from '@components/skeleton/skeleton-stats/skeleton-stats.component';
import { TenantIdResponseInterceptor } from '@utils/tenant-id-response-interceptor';
const screens = [BaseScreenComponent, DownloadsListScreenComponent]; const screens = [BaseScreenComponent, DownloadsListScreenComponent];
@ -119,6 +120,7 @@ export const appModuleFactory = (config: AppConfig) => {
preventDuplicates: true, preventDuplicates: true,
resetTimeoutOnDuplicate: true, resetTimeoutOnDuplicate: true,
}), }),
TenantsModule.forRoot(),
IqserTranslateModule.forRoot({ pathPrefix: config.BASE_TRANSLATIONS_DIRECTORY || '/assets/i18n/redact/' }), IqserTranslateModule.forRoot({ pathPrefix: config.BASE_TRANSLATIONS_DIRECTORY || '/assets/i18n/redact/' }),
IqserLoadingModule.forRoot(), IqserLoadingModule.forRoot(),
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }), ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
@ -170,6 +172,7 @@ export const appModuleFactory = (config: AppConfig) => {
IqserAllowDirective, IqserAllowDirective,
IqserDenyDirective, IqserDenyDirective,
IqserListingModule, IqserListingModule,
IconButtonComponent,
], ],
providers: [ providers: [
{ {
@ -181,22 +184,13 @@ export const appModuleFactory = (config: AppConfig) => {
provide: ErrorHandler, provide: ErrorHandler,
useClass: GlobalErrorHandler, useClass: GlobalErrorHandler,
}, },
{
provide: HTTP_INTERCEPTORS,
multi: true,
useClass: TenantIdInterceptor,
},
{
provide: HTTP_INTERCEPTORS,
multi: true,
useClass: TenantIdResponseInterceptor,
},
{ {
provide: APP_INITIALIZER, provide: APP_INITIALIZER,
multi: true, multi: true,
useFactory: configurationInitializer, useFactory: configurationInitializer,
deps: [ deps: [
BASE_HREF, BASE_HREF,
KeycloakStatusService,
KeycloakService, KeycloakService,
ConfigService, ConfigService,
SystemPreferencesService, SystemPreferencesService,

View File

@ -43,6 +43,10 @@
</a> </a>
</ng-container> </ng-container>
<button (click)="selectTenant()" id="select-tenant" mat-menu-item *ngIf="tenantContext.hasMultipleTenants">
<span translate="top-bar.navigation-items.my-account.children.select-tenant"> </span>
</button>
<button (click)="userService.logout()" id="logout" mat-menu-item> <button (click)="userService.logout()" id="logout" mat-menu-item>
<mat-icon svgIcon="iqser:logout"></mat-icon> <mat-icon svgIcon="iqser:logout"></mat-icon>
<span translate="top-bar.navigation-items.my-account.children.logout"> </span> <span translate="top-bar.navigation-items.my-account.children.logout"> </span>

View File

@ -1,4 +1,4 @@
import { Component } from '@angular/core'; import { Component, inject } from '@angular/core';
import { UserService } from '@users/user.service'; import { UserService } from '@users/user.service';
import { UserPreferenceService } from '@users/user-preference.service'; import { UserPreferenceService } from '@users/user-preference.service';
import { NavigationStart, Router } from '@angular/router'; import { NavigationStart, Router } from '@angular/router';
@ -7,7 +7,7 @@ import { TranslateService } from '@ngx-translate/core';
import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action'; import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { filter, map, startWith } from 'rxjs/operators'; import { filter, map, startWith } from 'rxjs/operators';
import { IqserPermissionsService, shareDistinctLast } from '@iqser/common-ui'; import { BASE_HREF, IqserPermissionsService, shareDistinctLast, TenantContext, TenantContextHolder } from '@iqser/common-ui';
import { BreadcrumbsService } from '@services/breadcrumbs.service'; import { BreadcrumbsService } from '@services/breadcrumbs.service';
import { FeaturesService } from '@services/features.service'; import { FeaturesService } from '@services/features.service';
import { ARCHIVE_ROUTE, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE } from '@red/domain'; import { ARCHIVE_ROUTE, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE } from '@red/domain';
@ -31,6 +31,8 @@ const isSearchScreen: (url: string) => boolean = url => url.includes('/search');
}) })
export class BaseScreenComponent { export class BaseScreenComponent {
readonly roles = ROLES; readonly roles = ROLES;
private readonly _baseHref = inject(BASE_HREF);
readonly tenantContext = inject(TenantContext);
readonly currentUser = this.userService.currentUser; readonly currentUser = this.userService.currentUser;
readonly userMenuItems: readonly MenuItem[] = [ readonly userMenuItems: readonly MenuItem[] = [
{ {
@ -89,6 +91,7 @@ export class BaseScreenComponent {
private readonly _router: Router, private readonly _router: Router,
private readonly _translateService: TranslateService, private readonly _translateService: TranslateService,
private readonly _featuresService: FeaturesService, private readonly _featuresService: FeaturesService,
private readonly _tenantContextHolder: TenantContextHolder,
readonly permissionsService: IqserPermissionsService, readonly permissionsService: IqserPermissionsService,
readonly userService: UserService, readonly userService: UserService,
readonly userPreferenceService: UserPreferenceService, readonly userPreferenceService: UserPreferenceService,
@ -112,7 +115,7 @@ export class BaseScreenComponent {
private _search(query: string, dossierIds: string[], onlyActive = false) { private _search(query: string, dossierIds: string[], onlyActive = false) {
const queryParams = { query, dossierIds: dossierIds.join(','), onlyActive }; const queryParams = { query, dossierIds: dossierIds.join(','), onlyActive };
this._router.navigate(['main/search'], { queryParams }).then(); this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main/search`], { queryParams }).then();
} }
private _searchThisDossier(query: string) { private _searchThisDossier(query: string) {
@ -123,4 +126,8 @@ export class BaseScreenComponent {
const dossierId = routerLink[2]; const dossierId = routerLink[2];
return this._search(query, [dossierId]); return this._search(query, [dossierId]);
} }
selectTenant() {
window.open(window.location.origin + this._baseHref, '_blank');
}
} }

View File

@ -7,11 +7,13 @@ import { DOSSIER_ID, DOSSIER_TEMPLATE_ID } from '@red/domain';
import { DossiersService } from '@services/dossiers/dossiers.service'; import { DossiersService } from '@services/dossiers/dossiers.service';
import { DictionaryService } from '@services/entity-services/dictionary.service'; import { DictionaryService } from '@services/entity-services/dictionary.service';
import { DossierDictionariesMapService } from '@services/entity-services/dossier-dictionaries-map.service'; import { DossierDictionariesMapService } from '@services/entity-services/dossier-dictionaries-map.service';
import { TenantContextHolder } from '@iqser/common-ui';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class DossierFilesGuard implements CanActivate { export class DossierFilesGuard implements CanActivate {
constructor( constructor(
private readonly _injector: Injector, private readonly _injector: Injector,
private readonly _tenantContextHolder: TenantContextHolder,
private readonly _filesMapService: FilesMapService, private readonly _filesMapService: FilesMapService,
private readonly _filesService: FilesService, private readonly _filesService: FilesService,
private readonly _dictionaryService: DictionaryService, private readonly _dictionaryService: DictionaryService,
@ -32,7 +34,7 @@ export class DossierFilesGuard implements CanActivate {
} }
if (!dossiersService.has(dossierId)) { if (!dossiersService.has(dossierId)) {
await this._router.navigate(['/main', dossierTemplateId]); await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main`, dossierTemplateId]);
return false; return false;
} }

View File

@ -3,11 +3,13 @@ import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
import { DOSSIER_TEMPLATE_ID } from '@red/domain'; import { DOSSIER_TEMPLATE_ID } from '@red/domain';
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service'; import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { TenantContextHolder } from '@iqser/common-ui';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class DossierTemplateExistsGuard implements CanActivate { export class DossierTemplateExistsGuard implements CanActivate {
constructor( constructor(
private readonly _dashboardStatsService: DashboardStatsService, private readonly _dashboardStatsService: DashboardStatsService,
private readonly _tenantContextHolder: TenantContextHolder,
private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _dossierTemplatesService: DossierTemplatesService,
private readonly _router: Router, private readonly _router: Router,
) {} ) {}
@ -19,13 +21,13 @@ export class DossierTemplateExistsGuard implements CanActivate {
if (dossiersListView) { if (dossiersListView) {
const dossierTemplateStats = this._dashboardStatsService.find(dossierTemplateId); const dossierTemplateStats = this._dashboardStatsService.find(dossierTemplateId);
if (!dossierTemplateStats || dossierTemplateStats.isEmpty) { if (!dossierTemplateStats || dossierTemplateStats.isEmpty) {
await this._router.navigate(['']); await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main`]);
return false; return false;
} }
} else { } else {
const dossierTemplate = this._dossierTemplatesService.find(dossierTemplateId); const dossierTemplate = this._dossierTemplatesService.find(dossierTemplateId);
if (!dossierTemplate) { if (!dossierTemplate) {
await this._router.navigate(['main', 'admin', 'dossier-templates']); await this._router.navigate([this._tenantContextHolder.currentTenant, 'main', 'admin', 'dossier-templates']);
return false; return false;
} }
} }

View File

@ -7,12 +7,14 @@ import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.se
import { DossiersService } from '@services/dossiers/dossiers.service'; import { DossiersService } from '@services/dossiers/dossiers.service';
import { ARCHIVE_ROUTE, DOSSIER_TEMPLATE_ID } from '@red/domain'; import { ARCHIVE_ROUTE, DOSSIER_TEMPLATE_ID } from '@red/domain';
import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service'; import { DashboardStatsService } from '@services/dossier-templates/dashboard-stats.service';
import { TenantContextHolder } from '@iqser/common-ui';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class DossiersGuard implements CanActivate { export class DossiersGuard implements CanActivate {
constructor( constructor(
private readonly _injector: Injector, private readonly _injector: Injector,
private readonly _router: Router, private readonly _router: Router,
private readonly _tenantContextHolder: TenantContextHolder,
private readonly _dashboardStatsService: DashboardStatsService, private readonly _dashboardStatsService: DashboardStatsService,
private readonly _activeDossiersService: ActiveDossiersService, private readonly _activeDossiersService: ActiveDossiersService,
private readonly _archivedDossiersService: ArchivedDossiersService, private readonly _archivedDossiersService: ArchivedDossiersService,
@ -33,7 +35,7 @@ export class DossiersGuard implements CanActivate {
const dossierTemplateStats = this._dashboardStatsService.find(dossierTemplateId); const dossierTemplateStats = this._dashboardStatsService.find(dossierTemplateId);
if (isArchive && dossierTemplateStats?.numberOfArchivedDossiers === 0) { if (isArchive && dossierTemplateStats?.numberOfArchivedDossiers === 0) {
await this._router.navigate(['main', dossierTemplateId, 'dossiers']); await this._router.navigate([this._tenantContextHolder.currentTenant, 'main', dossierTemplateId, 'dossiers']);
return false; return false;
} }

View File

@ -3,6 +3,7 @@ import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@red/domain'; import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@red/domain';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { TenantContextHolder } from '@iqser/common-ui';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class EntityExistsGuard implements CanActivate { export class EntityExistsGuard implements CanActivate {
@ -10,6 +11,7 @@ export class EntityExistsGuard implements CanActivate {
private readonly _dictionariesMapService: DictionariesMapService, private readonly _dictionariesMapService: DictionariesMapService,
private readonly _router: Router, private readonly _router: Router,
private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _dossierTemplatesService: DossierTemplatesService,
private readonly _tenantContextHolder: TenantContextHolder,
) {} ) {}
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> { async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
@ -18,7 +20,7 @@ export class EntityExistsGuard implements CanActivate {
if (!this._dictionariesMapService.get(dossierTemplateId, type)) { if (!this._dictionariesMapService.get(dossierTemplateId, type)) {
const dossierTemplate = this._dossierTemplatesService.find(dossierTemplateId); const dossierTemplate = this._dossierTemplatesService.find(dossierTemplateId);
await this._router.navigate([`${dossierTemplate.routerLink}/entities`]); await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/${dossierTemplate.routerLink}/entities`]);
return false; return false;
} }

View File

@ -2,10 +2,15 @@ import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router'; import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
import { WatermarksMapService } from '@services/entity-services/watermarks-map.service'; import { WatermarksMapService } from '@services/entity-services/watermarks-map.service';
import { DOSSIER_TEMPLATE_ID, WATERMARK_ID } from '@red/domain'; import { DOSSIER_TEMPLATE_ID, WATERMARK_ID } from '@red/domain';
import { TenantContextHolder } from '@iqser/common-ui';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class WatermarkExistsGuard implements CanActivate { export class WatermarkExistsGuard implements CanActivate {
constructor(private readonly _watermarksMapService: WatermarksMapService, private readonly _router: Router) {} constructor(
private readonly _watermarksMapService: WatermarksMapService,
private readonly _tenantContextHolder: TenantContextHolder,
private readonly _router: Router,
) {}
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> { async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
const dossierTemplateId = route.parent.paramMap.get(DOSSIER_TEMPLATE_ID); const dossierTemplateId = route.parent.paramMap.get(DOSSIER_TEMPLATE_ID);
@ -15,7 +20,14 @@ export class WatermarkExistsGuard implements CanActivate {
return true; return true;
} }
await this._router.navigate(['main', 'admin', 'dossier-templates', dossierTemplateId, 'watermarks']); await this._router.navigate([
this._tenantContextHolder.currentTenant,
'main',
'admin',
'dossier-templates',
dossierTemplateId,
'watermarks',
]);
return false; return false;
} }
} }

View File

@ -4,7 +4,7 @@ import { Router } from '@angular/router';
import { firstValueFrom, Observable } from 'rxjs'; import { firstValueFrom, Observable } from 'rxjs';
import { AdminDialogService } from '../services/admin-dialog.service'; import { AdminDialogService } from '../services/admin-dialog.service';
import { DictionaryService } from '@services/entity-services/dictionary.service'; import { DictionaryService } from '@services/entity-services/dictionary.service';
import { getParam, LoadingService } from '@iqser/common-ui'; import { getParam, LoadingService, TenantContextHolder } from '@iqser/common-ui';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
@ -27,6 +27,7 @@ export class BaseEntityScreenComponent {
private readonly _dialogService: AdminDialogService, private readonly _dialogService: AdminDialogService,
private readonly _dictionaryService: DictionaryService, private readonly _dictionaryService: DictionaryService,
private readonly _permissionsService: PermissionsService, private readonly _permissionsService: PermissionsService,
private readonly _tenantContextHolder: TenantContextHolder,
private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _dossierTemplatesService: DossierTemplatesService,
) { ) {
const entity$ = dictionaryMapService.watch$(this.#dossierTemplateId, this.#entityType); const entity$ = dictionaryMapService.watch$(this.#dossierTemplateId, this.#entityType);
@ -41,7 +42,7 @@ export class BaseEntityScreenComponent {
this._loadingService.start(); this._loadingService.start();
const dossierTemplate = this._dossierTemplatesService.find(this.#dossierTemplateId); const dossierTemplate = this._dossierTemplatesService.find(this.#dossierTemplateId);
await firstValueFrom(this._dictionaryService.deleteDictionaries([this.#entityType], this.#dossierTemplateId)); await firstValueFrom(this._dictionaryService.deleteDictionaries([this.#entityType], this.#dossierTemplateId));
await this._router.navigate([`${dossierTemplate.routerLink}/entities`]); await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/${dossierTemplate.routerLink}/entities`]);
this._loadingService.stop(); this._loadingService.stop();
}); });
} }

View File

@ -12,6 +12,7 @@ import {
IconButtonTypes, IconButtonTypes,
IqserPermissionsService, IqserPermissionsService,
LoadingService, LoadingService,
TenantContextHolder,
Toaster, Toaster,
} from '@iqser/common-ui'; } from '@iqser/common-ui';
import { DOSSIER_TEMPLATE_ID, type IWatermark, type User, WATERMARK_ID, WatermarkOrientation, WatermarkOrientations } from '@red/domain'; import { DOSSIER_TEMPLATE_ID, type IWatermark, type User, WATERMARK_ID, WatermarkOrientation, WatermarkOrientations } from '@red/domain';
@ -75,6 +76,7 @@ export class WatermarkScreenComponent implements OnInit {
private readonly _formBuilder: FormBuilder, private readonly _formBuilder: FormBuilder,
readonly permissionsService: IqserPermissionsService, readonly permissionsService: IqserPermissionsService,
private readonly _loadingService: LoadingService, private readonly _loadingService: LoadingService,
private readonly _tenantContextHolder: TenantContextHolder,
private readonly _licenseService: LicenseService, private readonly _licenseService: LicenseService,
@Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn, @Inject(BASE_HREF_FN) private readonly _convertPath: BaseHrefFn,
private readonly _watermarkService: WatermarkService, private readonly _watermarkService: WatermarkService,
@ -146,7 +148,11 @@ export class WatermarkScreenComponent implements OnInit {
watermark.id ? _('watermark-screen.action.change-success') : _('watermark-screen.action.created-success'), watermark.id ? _('watermark-screen.action.change-success') : _('watermark-screen.action.created-success'),
); );
if (!watermark.id) { if (!watermark.id) {
await this._router.navigate([`/main/admin/dossier-templates/${this.#dossierTemplateId}/watermarks/${updatedWatermark.id}`]); await this._router.navigate([
`/${this._tenantContextHolder.currentTenant}/main/admin/dossier-templates/${this.#dossierTemplateId}/watermarks/${
updatedWatermark.id
}`,
]);
} }
} catch (error) { } catch (error) {
this._toaster.error(_('watermark-screen.action.error')); this._toaster.error(_('watermark-screen.action.error'));

View File

@ -1,7 +1,14 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { AdminDialogService } from '../../../services/admin-dialog.service'; import { AdminDialogService } from '../../../services/admin-dialog.service';
import { CircleButtonComponent, CircleButtonTypes, getCurrentUser, IqserHelpModeModule, LoadingService } from '@iqser/common-ui'; import {
CircleButtonComponent,
CircleButtonTypes,
getCurrentUser,
IqserHelpModeModule,
LoadingService,
TenantContextHolder,
} from '@iqser/common-ui';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { DOSSIER_TEMPLATE_ID, type User } from '@red/domain'; import { DOSSIER_TEMPLATE_ID, type User } from '@red/domain';
@ -26,6 +33,7 @@ export class DossierTemplateActionsComponent implements OnInit {
private readonly _route: ActivatedRoute, private readonly _route: ActivatedRoute,
private readonly _loadingService: LoadingService, private readonly _loadingService: LoadingService,
private readonly _dialogService: AdminDialogService, private readonly _dialogService: AdminDialogService,
private readonly _tenantContextHolder: TenantContextHolder,
private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _dossierTemplatesService: DossierTemplatesService,
) {} ) {}
@ -43,7 +51,7 @@ export class DossierTemplateActionsComponent implements OnInit {
const success = await firstValueFrom(this._dossierTemplatesService.delete([this.dossierTemplateId])); const success = await firstValueFrom(this._dossierTemplatesService.delete([this.dossierTemplateId]));
if (success) { if (success) {
await this._router.navigate(['main', 'admin']); await this._router.navigate([this._tenantContextHolder.currentTenant, 'main', 'admin']);
} }
this._loadingService.stop(); this._loadingService.stop();

View File

@ -31,6 +31,7 @@ import {
OnDetach, OnDetach,
processFilters, processFilters,
shareDistinctLast, shareDistinctLast,
TenantContextHolder,
Toaster, Toaster,
} from '@iqser/common-ui'; } from '@iqser/common-ui';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
@ -125,6 +126,7 @@ export class FilePreviewScreenComponent
private readonly _viewModeService: ViewModeService, private readonly _viewModeService: ViewModeService,
private readonly _documentViewer: REDDocumentViewer, private readonly _documentViewer: REDDocumentViewer,
private readonly _changeRef: ChangeDetectorRef, private readonly _changeRef: ChangeDetectorRef,
private readonly _tenantContextHolder: TenantContextHolder,
private readonly _dialogService: FilePreviewDialogService, private readonly _dialogService: FilePreviewDialogService,
private readonly _pageRotationService: PageRotationService, private readonly _pageRotationService: PageRotationService,
private readonly _viewerHeaderService: ViewerHeaderService, private readonly _viewerHeaderService: ViewerHeaderService,
@ -765,7 +767,7 @@ export class FilePreviewScreenComponent
private _navigateToDossier() { private _navigateToDossier() {
this._logger.info('Navigating to ', this.state.dossier.dossierName); this._logger.info('Navigating to ', this.state.dossier.dossierName);
return this._router.navigate([this.state.dossier.routerLink]); return this._router.navigate([`/${this._tenantContextHolder.currentTenant}${this.state.dossier.routerLink}`]);
} }
#highlightSelectedAnnotations(newAnnotations: AnnotationWrapper[]) { #highlightSelectedAnnotations(newAnnotations: AnnotationWrapper[]) {

View File

@ -13,6 +13,7 @@ import {
OverlappingElements, OverlappingElements,
ScrollableParentView, ScrollableParentView,
ScrollableParentViews, ScrollableParentViews,
TenantContextHolder,
Toaster, Toaster,
} from '@iqser/common-ui'; } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@ -90,6 +91,7 @@ export class FileActionsComponent implements OnChanges {
private readonly _changeRef: ChangeDetectorRef, private readonly _changeRef: ChangeDetectorRef,
private readonly _loadingService: LoadingService, private readonly _loadingService: LoadingService,
private readonly _dialogService: DossiersDialogService, private readonly _dialogService: DossiersDialogService,
private readonly _tenantContextHolder: TenantContextHolder,
private readonly _fileAssignService: FileAssignService, private readonly _fileAssignService: FileAssignService,
private readonly _reanalysisService: ReanalysisService, private readonly _reanalysisService: ReanalysisService,
private readonly _permissionsService: PermissionsService, private readonly _permissionsService: PermissionsService,
@ -343,7 +345,7 @@ export class FileActionsComponent implements OnChanges {
try { try {
const dossier = this._activeDossiersService.find(this.file.dossierId); const dossier = this._activeDossiersService.find(this.file.dossierId);
await firstValueFrom(this._fileManagementService.delete([this.file], this.file.dossierId)); await firstValueFrom(this._fileManagementService.delete([this.file], this.file.dossierId));
await this._injector.get(Router).navigate([dossier.routerLink]); await this._injector.get(Router).navigate([`/${this._tenantContextHolder.currentTenant}${dossier.routerLink}`]);
} catch (error) { } catch (error) {
this._injector.get(Toaster).error(_('error.http.generic'), { params: error }); this._injector.get(Toaster).error(_('error.http.generic'), { params: error });
} }

View File

@ -7,7 +7,15 @@ import { PermissionsService } from '@services/permissions.service';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { MatDialogRef } from '@angular/material/dialog'; import { MatDialogRef } from '@angular/material/dialog';
import { EditDossierDialogComponent } from '../edit-dossier-dialog.component'; import { EditDossierDialogComponent } from '../edit-dossier-dialog.component';
import { ConfirmOptions, IconButtonTypes, IConfirmationDialogData, LoadingService, TitleColors, Toaster } from '@iqser/common-ui'; import {
ConfirmOptions,
IconButtonTypes,
IConfirmationDialogData,
LoadingService,
TenantContextHolder,
TitleColors,
Toaster,
} from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { DossierStatsService } from '@services/dossiers/dossier-stats.service'; import { DossierStatsService } from '@services/dossiers/dossier-stats.service';
@ -47,6 +55,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
private readonly _formBuilder: UntypedFormBuilder, private readonly _formBuilder: UntypedFormBuilder,
private readonly _dialogService: DossiersDialogService, private readonly _dialogService: DossiersDialogService,
private readonly _router: Router, private readonly _router: Router,
private readonly _tenantContextHolder: TenantContextHolder,
private readonly _editDossierDialogRef: MatDialogRef<EditDossierDialogComponent>, private readonly _editDossierDialogRef: MatDialogRef<EditDossierDialogComponent>,
private readonly _toaster: Toaster, private readonly _toaster: Toaster,
private readonly _loadingService: LoadingService, private readonly _loadingService: LoadingService,
@ -144,7 +153,7 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
this._loadingService.start(); this._loadingService.start();
await firstValueFrom(this._trashService.deleteDossier(this.dossier)); await firstValueFrom(this._trashService.deleteDossier(this.dossier));
this._editDossierDialogRef.close(); this._editDossierDialogRef.close();
await this._router.navigate([this.dossier.dossiersListRouterLink]); await this._router.navigate([`/${this._tenantContextHolder.currentTenant}${this.dossier.dossiersListRouterLink}`]);
this._loadingService.stop(); this._loadingService.stop();
this._toaster.success(_('edit-dossier-dialog.delete-successful'), { this._toaster.success(_('edit-dossier-dialog.delete-successful'), {
params: { params: {

View File

@ -3,7 +3,7 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { DOSSIER_TEMPLATE_ID, DownloadFileType, IDossierRequest, IDossierTemplate, IReportTemplate } from '@red/domain'; import { DOSSIER_TEMPLATE_ID, DownloadFileType, IDossierRequest, IDossierTemplate, IReportTemplate } from '@red/domain';
import { UntypedFormGroup, Validators } from '@angular/forms'; import { UntypedFormGroup, Validators } from '@angular/forms';
import { downloadTypesTranslations } from '@translations/download-types-translations'; import { downloadTypesTranslations } from '@translations/download-types-translations';
import { BaseDialogComponent, IconButtonTypes, IqserPermissionsService, SaveOptions } from '@iqser/common-ui'; import { BaseDialogComponent, IconButtonTypes, IqserPermissionsService, SaveOptions, TenantContextHolder } from '@iqser/common-ui';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { ReportTemplateService } from '@services/report-template.service'; import { ReportTemplateService } from '@services/report-template.service';
@ -42,6 +42,7 @@ export class AddDossierDialogComponent extends BaseDialogComponent implements On
constructor( constructor(
readonly permissionsService: IqserPermissionsService, readonly permissionsService: IqserPermissionsService,
private readonly _tenantContextHolder: TenantContextHolder,
private readonly _activeDossiersService: ActiveDossiersService, private readonly _activeDossiersService: ActiveDossiersService,
private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _dossierTemplatesService: DossierTemplatesService,
private readonly _reportTemplateController: ReportTemplateService, private readonly _reportTemplateController: ReportTemplateService,
@ -82,7 +83,7 @@ export class AddDossierDialogComponent extends BaseDialogComponent implements On
this._loadingService.start(); this._loadingService.start();
const savedDossier = await firstValueFrom(this._activeDossiersService.createOrUpdate(this._formToObject())); const savedDossier = await firstValueFrom(this._activeDossiersService.createOrUpdate(this._formToObject()));
if (savedDossier) { if (savedDossier) {
await this._router.navigate([savedDossier.routerLink]); await this._router.navigate([`/${this._tenantContextHolder.currentTenant}${savedDossier.routerLink}`]);
if (options?.addMembers) { if (options?.addMembers) {
this._dialogService.openDialog('editDossier', { this._dialogService.openDialog('editDossier', {
dossierId: savedDossier.id, dossierId: savedDossier.id,

View File

@ -3,15 +3,14 @@ import { DownloadStatus, IDownloadStatus, IDownloadStatusResponse, IPrepareDownl
import { firstValueFrom, Observable } from 'rxjs'; import { firstValueFrom, Observable } from 'rxjs';
import { ConfigService } from '@services/config.service'; import { ConfigService } from '@services/config.service';
import { map, tap } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
import { EntitiesService, mapEach, RequiredParam, Validate } from '@iqser/common-ui'; import { EntitiesService, mapEach, RequiredParam, TenantContextHolder, Validate } from '@iqser/common-ui';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
import { TenantContext } from '@utils/tenant-context';
@Injectable() @Injectable()
export class FileDownloadService extends EntitiesService<IDownloadStatus, DownloadStatus> { export class FileDownloadService extends EntitiesService<IDownloadStatus, DownloadStatus> {
protected readonly _defaultModelPath = 'async/download'; protected readonly _defaultModelPath = 'async/download';
protected readonly _entityClass = DownloadStatus; protected readonly _entityClass = DownloadStatus;
protected readonly _tenantContext = inject(TenantContext); protected readonly _tenantContext = inject(TenantContextHolder);
constructor(private readonly _configService: ConfigService, private readonly _logger: NGXLogger) { constructor(private readonly _configService: ConfigService, private readonly _logger: NGXLogger) {
super(); super();
@ -37,7 +36,7 @@ export class FileDownloadService extends EntitiesService<IDownloadStatus, Downlo
const token = await this.generateToken(status.storageId); const token = await this.generateToken(status.storageId);
const anchor = document.createElement('a'); const anchor = document.createElement('a');
anchor.href = `${this._configService.values.API_URL}/async/download/with-ott/${token.value}?tenantId=${encodeURIComponent( anchor.href = `${this._configService.values.API_URL}/async/download/with-ott/${token.value}?tenantId=${encodeURIComponent(
this._tenantContext.getTenant(), this._tenantContext.currentTenant,
)}`; )}`;
anchor.download = status.filename; anchor.download = status.filename;
anchor.target = '_blank'; anchor.target = '_blank';

View File

@ -8,6 +8,7 @@ import { DossiersService } from './dossiers.service';
import { FilesMapService } from '../files/files-map.service'; import { FilesMapService } from '../files/files-map.service';
import { FeaturesService } from '../features.service'; import { FeaturesService } from '../features.service';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { TenantContextHolder } from '@iqser/common-ui';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class ArchivedDossiersService extends DossiersService { export class ArchivedDossiersService extends DossiersService {
@ -17,6 +18,7 @@ export class ArchivedDossiersService extends DossiersService {
readonly #activeDossiersService = inject(ActiveDossiersService); readonly #activeDossiersService = inject(ActiveDossiersService);
readonly #filesMapService = inject(FilesMapService); readonly #filesMapService = inject(FilesMapService);
readonly #featuresService = inject(FeaturesService); readonly #featuresService = inject(FeaturesService);
readonly #tenantContextHolder = inject(TenantContextHolder);
readonly #router = inject(Router); readonly #router = inject(Router);
archive(dossiers: Dossier[]): Observable<unknown> { archive(dossiers: Dossier[]): Observable<unknown> {
@ -40,7 +42,7 @@ export class ArchivedDossiersService extends DossiersService {
if (!this.#activeDossiersService.all.find(d => d.dossierTemplateId === dossierTemplateId)) { if (!this.#activeDossiersService.all.find(d => d.dossierTemplateId === dossierTemplateId)) {
route = route.replace(DOSSIERS_ROUTE, ARCHIVE_ROUTE); route = route.replace(DOSSIERS_ROUTE, ARCHIVE_ROUTE);
} }
await this.#router.navigate([route]); await this.#router.navigate([`/${this.#tenantContextHolder.currentTenant}${route}`]);
}), }),
catchError(showArchiveFailedToast), catchError(showArchiveFailedToast),
); );

View File

@ -1,6 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router'; import { NavigationEnd, Router } from '@angular/router';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import { TenantContextHolder } from '@iqser/common-ui';
const LAST_DOSSIERS_SCREEN = 'routerHistory_lastDossiersScreen'; const LAST_DOSSIERS_SCREEN = 'routerHistory_lastDossiersScreen';
@ -10,7 +11,8 @@ const LAST_DOSSIERS_SCREEN = 'routerHistory_lastDossiersScreen';
export class RouterHistoryService { export class RouterHistoryService {
private _lastDossiersScreen = localStorage.getItem(LAST_DOSSIERS_SCREEN); private _lastDossiersScreen = localStorage.getItem(LAST_DOSSIERS_SCREEN);
constructor(private readonly _router: Router) { constructor(private readonly _router: Router, private readonly _tenantContextHolder: TenantContextHolder) {
// eslint-disable-next-line rxjs/no-ignored-subscription
this._router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe((event: NavigationEnd) => { this._router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe((event: NavigationEnd) => {
if (event.url.includes('/dossiers') || event.url.includes('/archive')) { if (event.url.includes('/dossiers') || event.url.includes('/archive')) {
this._lastDossiersScreen = event.url; this._lastDossiersScreen = event.url;
@ -21,9 +23,10 @@ export class RouterHistoryService {
navigateToLastDossiersScreen(): void { navigateToLastDossiersScreen(): void {
if (this._router.url === decodeURI(this._lastDossiersScreen)) { if (this._router.url === decodeURI(this._lastDossiersScreen)) {
this._router.navigate(['/']); this._router.navigate(['/' + this._tenantContextHolder.currentTenant]);
} else { } else {
const url = decodeURI(this._lastDossiersScreen).split('?')[0]; const url = decodeURI(this._lastDossiersScreen).split('?')[0];
// todo links
this._router.navigate([url]); this._router.navigate([url]);
} }
} }

View File

@ -1,7 +1,7 @@
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { UserService } from './user.service'; import { UserService } from './user.service';
import { IqserPermissionsService, IqserRoleGuard } from '@iqser/common-ui'; import { IqserPermissionsService, IqserRoleGuard, TenantContextHolder } from '@iqser/common-ui';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -9,19 +9,20 @@ import { IqserPermissionsService, IqserRoleGuard } from '@iqser/common-ui';
export class RedRoleGuard extends IqserRoleGuard { export class RedRoleGuard extends IqserRoleGuard {
protected readonly _userService = inject(UserService); protected readonly _userService = inject(UserService);
protected readonly _permissionsService = inject(IqserPermissionsService); protected readonly _permissionsService = inject(IqserPermissionsService);
protected readonly _tenantContextHolder = inject(TenantContextHolder);
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> { async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
const currentUser = this._userService.currentUser; const currentUser = this._userService.currentUser;
if (!currentUser?.hasAnyRole) { if (!currentUser?.hasAnyRole) {
await this._router.navigate(['/auth-error']); await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/auth-error`]);
this._loadingService.stop(); this._loadingService.stop();
return false; return false;
} }
// we have at least 1 RED Role -> if it's not user he must be an admin // 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')) { if (currentUser.isUserAdmin && !currentUser.isAdmin && state.url.includes('admin') && !state.url.includes('users')) {
await this._router.navigate(['/main/admin/users']); await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main/admin/users`]);
return false; return false;
} }
@ -29,9 +30,9 @@ export class RedRoleGuard extends IqserRoleGuard {
currentUser.isUserAdmin && currentUser.isUserAdmin &&
!currentUser.isAdmin && !currentUser.isAdmin &&
!currentUser.isUser && !currentUser.isUser &&
!(state.url.startsWith('/main/admin/users') || state.url.startsWith('/main/account')) !(state.url.includes('/main/admin/users') || state.url.includes('/main/account'))
) { ) {
await this._router.navigate(['/main/admin/users']); await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main/admin/users`]);
return false; return false;
} }
@ -40,9 +41,9 @@ export class RedRoleGuard extends IqserRoleGuard {
return true; return true;
} }
if (!currentUser.isUser) { if (!currentUser.isUser) {
await this._router.navigate(['/main/admin']); await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main/admin`]);
} else { } else {
await this._router.navigate(['/']); await this._router.navigate([`/${this._tenantContextHolder.currentTenant}`]);
} }
return false; return false;
} }

View File

@ -1,9 +1,9 @@
import { catchError, filter, switchMap, tap } from 'rxjs/operators'; import { catchError, filter, switchMap, tap } from 'rxjs/operators';
import { ConfigService } from '@services/config.service'; import { ConfigService } from '@services/config.service';
import { firstValueFrom, map, of, throwError } from 'rxjs'; import { firstValueFrom, map, of, throwError } from 'rxjs';
import { KeycloakEventType, KeycloakService } from 'keycloak-angular'; import { KeycloakService } from 'keycloak-angular';
import { GeneralSettingsService } from '@services/general-settings.service'; import { GeneralSettingsService } from '@services/general-settings.service';
import { IqserPermissionsService, LanguageService } from '@iqser/common-ui'; import { IqserPermissionsService, KeycloakStatus, KeycloakStatusService, LanguageService } from '@iqser/common-ui';
import { UserPreferenceService } from '@users/user-preference.service'; import { UserPreferenceService } from '@users/user-preference.service';
import { UserService } from '@users/user.service'; import { UserService } from '@users/user.service';
import { FeaturesService } from '@services/features.service'; import { FeaturesService } from '@services/features.service';
@ -21,6 +21,7 @@ function lastDossierTemplateRedirect(baseHref: string, userPreferenceService: Us
export function configurationInitializer( export function configurationInitializer(
baseHref: string, baseHref: string,
keycloakStatusService: KeycloakStatusService,
keycloakService: KeycloakService, keycloakService: KeycloakService,
configService: ConfigService, configService: ConfigService,
systemPreferencesService: SystemPreferencesService, systemPreferencesService: SystemPreferencesService,
@ -32,8 +33,8 @@ export function configurationInitializer(
licenseService: LicenseService, licenseService: LicenseService,
permissionsService: IqserPermissionsService, permissionsService: IqserPermissionsService,
) { ) {
const setup = keycloakService.keycloakEvents$.pipe( const setup = keycloakStatusService.keycloakStatus$.pipe(
filter(event => event.type === KeycloakEventType.OnReady), filter(event => event === KeycloakStatus.READY || event === KeycloakStatus.NOT_ACTIVE),
map(() => featuresService.loadConfig()), map(() => featuresService.loadConfig()),
switchMap(() => keycloakService.isLoggedIn()), switchMap(() => keycloakService.isLoggedIn()),
switchMap(loggedIn => (!loggedIn ? throwError(() => 'Not Logged In') : of({}))), switchMap(loggedIn => (!loggedIn ? throwError(() => 'Not Logged In') : of({}))),
@ -43,8 +44,12 @@ export function configurationInitializer(
tap(configuration => configService.updateDisplayName(configuration.displayName)), tap(configuration => configService.updateDisplayName(configuration.displayName)),
switchMap(() => systemPreferencesService.loadPreferences()), switchMap(() => systemPreferencesService.loadPreferences()),
switchMap(() => userPreferenceService.reload()), switchMap(() => userPreferenceService.reload()),
catchError(e => { catchError((e: unknown) => {
console.log('[Redaction] Initialization error:', e); console.log('[Redaction] Initialization error:', e);
if (keycloakStatusService.keycloakStatus$.value === KeycloakStatus.READY) {
return of(userService.redirectToLogin());
}
return of({}); return of({});
}), }),
tap(() => { tap(() => {

View File

@ -1,8 +0,0 @@
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class TenantContext {
getTenant(): string {
return 'redaction';
}
}

View File

@ -1,16 +0,0 @@
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { TenantContext } from '@utils/tenant-context';
@Injectable()
export class TenantIdInterceptor implements HttpInterceptor {
protected readonly _tenantContext = inject(TenantContext);
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
const updatedRequest = req.clone({
setHeaders: { 'X-TENANT-ID': this._tenantContext.getTenant() },
});
return next.handle(updatedRequest);
}
}

View File

@ -1,22 +0,0 @@
import { Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
const VALID_TENANT_IDS = ['redaction'];
@Injectable()
export class TenantIdResponseInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
return next.handle(req).pipe(
tap(event => {
if (event instanceof HttpResponse) {
const xTenantId = event.headers.get('X-TENANT-ID');
if (VALID_TENANT_IDS.includes(xTenantId)) {
//TODO add logic to deny the response when backend will send the header
}
}
}),
);
}
}

View File

@ -1,7 +1,7 @@
{ {
"ADMIN_CONTACT_NAME": null, "ADMIN_CONTACT_NAME": null,
"ADMIN_CONTACT_URL": null, "ADMIN_CONTACT_URL": null,
"API_URL": "https://dev-08.iqser.cloud/redaction-gateway-v1", "API_URL": "https://dom1.iqser.cloud/redaction-gateway-v1",
"APP_NAME": "RedactManager", "APP_NAME": "RedactManager",
"AUTO_READ_TIME": 3, "AUTO_READ_TIME": 3,
"BACKEND_APP_VERSION": "4.4.40", "BACKEND_APP_VERSION": "4.4.40",
@ -11,7 +11,7 @@
"MAX_RETRIES_ON_SERVER_ERROR": 3, "MAX_RETRIES_ON_SERVER_ERROR": 3,
"OAUTH_CLIENT_ID": "redaction", "OAUTH_CLIENT_ID": "redaction",
"OAUTH_IDP_HINT": null, "OAUTH_IDP_HINT": null,
"OAUTH_URL": "https://dev-08.iqser.cloud/auth/realms/redaction", "OAUTH_URL": "https://dom1.iqser.cloud/auth",
"RECENT_PERIOD_IN_HOURS": 24, "RECENT_PERIOD_IN_HOURS": 24,
"SELECTION_MODE": "structural", "SELECTION_MODE": "structural",
"MANUAL_BASE_URL": "https://docs.redactmanager.com/preview", "MANUAL_BASE_URL": "https://docs.redactmanager.com/preview",

View File

@ -6,9 +6,6 @@
}, },
"multiple-values": "" "multiple-values": ""
}, },
"user-menu": {
"button-text": ""
},
"account-settings": "Account Einstellungen", "account-settings": "Account Einstellungen",
"actions": { "actions": {
"all": "Alle", "all": "Alle",
@ -1767,11 +1764,11 @@
"title": "Benachrichtigungseinstellungen" "title": "Benachrichtigungseinstellungen"
}, },
"notifications": { "notifications": {
"button-text": "",
"deleted-dossier": "", "deleted-dossier": "",
"label": "Benachrichtigungen", "label": "Benachrichtigungen",
"mark-all-as-read": "Alle als gelesen markieren", "mark-all-as-read": "Alle als gelesen markieren",
"mark-as": "", "mark-as": ""
"button-text": ""
}, },
"ocr": { "ocr": {
"confirmation-dialog": { "confirmation-dialog": {
@ -1819,11 +1816,6 @@
"previous": "Vorherige" "previous": "Vorherige"
}, },
"pdf-viewer": { "pdf-viewer": {
"text-popup": {
"actions": {
"search": "Nach Auswahl suchen"
}
},
"toggle-readable-redactions": "", "toggle-readable-redactions": "",
"toggle-tooltips": "{active, select, true{Disable} false{Enable} other{}} Kurzinfos für Anmerkungen" "toggle-tooltips": "{active, select, true{Disable} false{Enable} other{}} Kurzinfos für Anmerkungen"
}, },
@ -2055,6 +2047,15 @@
}, },
"title": "Authentifizierung aktivieren" "title": "Authentifizierung aktivieren"
}, },
"tenant-resolve": {
"actions": {
"save": "Proceed"
},
"form": {
"tenant-placeholder": "Select a tenant ..."
},
"header": "Select your Tenant"
},
"time": { "time": {
"days": "{days} {days, plural, one{Tag} other{Tage}}", "days": "{days} {days, plural, one{Tag} other{Tage}}",
"hours": "{hours} {hours, plural, one{Stunde} other{Stunden}}", "hours": "{hours} {hours, plural, one{Stunde} other{Stunden}}",
@ -2082,6 +2083,7 @@
"label": "Sprache" "label": "Sprache"
}, },
"logout": "Abmelden", "logout": "Abmelden",
"select-tenant": "Select Tenant",
"trash": "Papierkorb" "trash": "Papierkorb"
} }
} }
@ -2184,6 +2186,9 @@
} }
}, },
"user-management": "Benutzerverwaltung", "user-management": "Benutzerverwaltung",
"user-menu": {
"button-text": ""
},
"user-profile": "Mein Profil", "user-profile": "Mein Profil",
"user-profile-screen": { "user-profile-screen": {
"actions": { "actions": {

View File

@ -6,9 +6,6 @@
}, },
"multiple-values": "Multiple recommendations selected" "multiple-values": "Multiple recommendations selected"
}, },
"user-menu": {
"button-text": "User menu"
},
"account-settings": "Account Settings", "account-settings": "Account Settings",
"actions": { "actions": {
"all": "All", "all": "All",
@ -1767,11 +1764,11 @@
"title": "Notifications Preferences" "title": "Notifications Preferences"
}, },
"notifications": { "notifications": {
"button-text": "Notifications",
"deleted-dossier": "Deleted Dossier", "deleted-dossier": "Deleted Dossier",
"label": "Notifications", "label": "Notifications",
"mark-all-as-read": "Mark all as read", "mark-all-as-read": "Mark all as read",
"mark-as": "Mark as {type, select, read{read} unread{unread} other{}}", "mark-as": "Mark as {type, select, read{read} unread{unread} other{}}"
"button-text": "Notifications"
}, },
"ocr": { "ocr": {
"confirmation-dialog": { "confirmation-dialog": {
@ -1819,11 +1816,6 @@
"previous": "Prev" "previous": "Prev"
}, },
"pdf-viewer": { "pdf-viewer": {
"text-popup": {
"actions": {
"search": "Search for selection"
}
},
"toggle-readable-redactions": "Show redactions {active, select, true{as in final document} false{in preview color} other{}}", "toggle-readable-redactions": "Show redactions {active, select, true{as in final document} false{in preview color} other{}}",
"toggle-tooltips": "{active, select, true{Disable} false{Enable} other{}} annotation tooltips" "toggle-tooltips": "{active, select, true{Disable} false{Enable} other{}} annotation tooltips"
}, },
@ -2055,6 +2047,15 @@
}, },
"title": "Enable Authentication" "title": "Enable Authentication"
}, },
"tenant-resolve": {
"actions": {
"save": "Proceed"
},
"form": {
"tenant-placeholder": "Select a tenant ..."
},
"header": "Select your Tenant"
},
"time": { "time": {
"days": "{days} {days, plural, one{day} other{days}}", "days": "{days} {days, plural, one{day} other{days}}",
"hours": "{hours} {hours, plural, one{hour} other{hours}}", "hours": "{hours} {hours, plural, one{hour} other{hours}}",
@ -2082,6 +2083,7 @@
"label": "Language" "label": "Language"
}, },
"logout": "Logout", "logout": "Logout",
"select-tenant": "Select Tenant",
"trash": "Trash" "trash": "Trash"
} }
} }
@ -2184,6 +2186,9 @@
} }
}, },
"user-management": "User Management", "user-management": "User Management",
"user-menu": {
"button-text": "User menu"
},
"user-profile": "My Profile", "user-profile": "My Profile",
"user-profile-screen": { "user-profile-screen": {
"actions": { "actions": {

View File

@ -1764,6 +1764,7 @@
"title": "Benachrichtigungseinstellungen" "title": "Benachrichtigungseinstellungen"
}, },
"notifications": { "notifications": {
"button-text": "",
"deleted-dossier": "", "deleted-dossier": "",
"label": "Benachrichtigungen", "label": "Benachrichtigungen",
"mark-all-as-read": "Alle als gelesen markieren", "mark-all-as-read": "Alle als gelesen markieren",
@ -1815,11 +1816,6 @@
"previous": "Vorherige" "previous": "Vorherige"
}, },
"pdf-viewer": { "pdf-viewer": {
"text-popup": {
"actions": {
"search": "Nach Auswahl suchen"
}
},
"toggle-readable-redactions": "", "toggle-readable-redactions": "",
"toggle-tooltips": "{active, select, true{Disable} false{Enable} other{}} Kurzinfos für Anmerkungen" "toggle-tooltips": "{active, select, true{Disable} false{Enable} other{}} Kurzinfos für Anmerkungen"
}, },
@ -2051,6 +2047,15 @@
}, },
"title": "Authentifizierung aktivieren" "title": "Authentifizierung aktivieren"
}, },
"tenant-resolve": {
"actions": {
"save": "Proceed"
},
"form": {
"tenant-placeholder": "Select a tenant ..."
},
"header": "Select your Tenant"
},
"time": { "time": {
"days": "{days} {days, plural, one{Tag} other{Tage}}", "days": "{days} {days, plural, one{Tag} other{Tage}}",
"hours": "{hours} {hours, plural, one{Stunde} other{Stunden}}", "hours": "{hours} {hours, plural, one{Stunde} other{Stunden}}",
@ -2078,6 +2083,7 @@
"label": "Sprache" "label": "Sprache"
}, },
"logout": "Abmelden", "logout": "Abmelden",
"select-tenant": "Select Tenant",
"trash": "Papierkorb" "trash": "Papierkorb"
} }
} }
@ -2180,6 +2186,9 @@
} }
}, },
"user-management": "Benutzerverwaltung", "user-management": "Benutzerverwaltung",
"user-menu": {
"button-text": ""
},
"user-profile": "Mein Profil", "user-profile": "Mein Profil",
"user-profile-screen": { "user-profile-screen": {
"actions": { "actions": {

View File

@ -1764,6 +1764,7 @@
"title": "Notifications Preferences" "title": "Notifications Preferences"
}, },
"notifications": { "notifications": {
"button-text": "",
"deleted-dossier": "Deleted Dossier", "deleted-dossier": "Deleted Dossier",
"label": "Notifications", "label": "Notifications",
"mark-all-as-read": "Mark all as read", "mark-all-as-read": "Mark all as read",
@ -1815,11 +1816,6 @@
"previous": "Prev" "previous": "Prev"
}, },
"pdf-viewer": { "pdf-viewer": {
"text-popup": {
"actions": {
"search": "Search for selection"
}
},
"toggle-readable-redactions": "Show components {active, select, true{as in final document} false{in preview color} other{}}", "toggle-readable-redactions": "Show components {active, select, true{as in final document} false{in preview color} other{}}",
"toggle-tooltips": "{active, select, true{Disable} false{Enable} other{}} annotation tooltips" "toggle-tooltips": "{active, select, true{Disable} false{Enable} other{}} annotation tooltips"
}, },
@ -2051,6 +2047,15 @@
}, },
"title": "Enable Authentication" "title": "Enable Authentication"
}, },
"tenant-resolve": {
"actions": {
"save": "Proceed"
},
"form": {
"tenant-placeholder": "Select a tenant ..."
},
"header": "Select your Tenant"
},
"time": { "time": {
"days": "{days} {days, plural, one{day} other{days}}", "days": "{days} {days, plural, one{day} other{days}}",
"hours": "{hours} {hours, plural, one{hour} other{hours}}", "hours": "{hours} {hours, plural, one{hour} other{hours}}",
@ -2078,6 +2083,7 @@
"label": "Language" "label": "Language"
}, },
"logout": "Logout", "logout": "Logout",
"select-tenant": "Select Tenant",
"trash": "Trash" "trash": "Trash"
} }
} }
@ -2180,6 +2186,9 @@
} }
}, },
"user-management": "User Management", "user-management": "User Management",
"user-menu": {
"button-text": ""
},
"user-profile": "My Profile", "user-profile": "My Profile",
"user-profile-screen": { "user-profile-screen": {
"actions": { "actions": {

@ -1 +1 @@
Subproject commit bb562d63fd5ccc353c71b1ca026caa8de5a42926 Subproject commit 00d55e470f99fb1defa1ae7250af793d6fe70115