remove emails from stored tenants

This commit is contained in:
Dan Percic 2023-08-08 11:28:38 +03:00
parent c851ab1394
commit 04a69f5e68
5 changed files with 76 additions and 47 deletions

View File

@ -1,11 +1,10 @@
import { inject, Injectable, signal } from '@angular/core'; import { inject, Injectable, signal } from '@angular/core';
import dayjs from 'dayjs';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
import { List } from '../../utils'; import { List } from '../../utils';
import dayjs from 'dayjs';
export interface IStoredTenantId { export interface IStoredTenantId {
readonly tenantId: string; readonly tenantId: string;
readonly email: string;
readonly created: string; readonly created: string;
} }
@ -24,7 +23,7 @@ export class TenantsService {
removeItem: localStorage.removeItem.bind(localStorage), removeItem: localStorage.removeItem.bind(localStorage),
key: localStorage.key.bind(localStorage), key: localStorage.key.bind(localStorage),
}; };
readonly #activeTenantId = signal<string>(''); readonly #activeTenantId = signal('');
protected readonly _serviceName: string = 'tenant-user-management'; protected readonly _serviceName: string = 'tenant-user-management';
get activeTenantId() { get activeTenantId() {
@ -37,15 +36,10 @@ export class TenantsService {
return true; return true;
} }
storeTenant(emailOrUsername: string) { storeTenant() {
if (!emailOrUsername) {
this.#logger.warn('[TENANTS] Email or username is null, skip storing');
return;
}
const storedTenants = this.getStoredTenants(); const storedTenants = this.getStoredTenants();
const activeTenantId = this.#activeTenantId(); const activeTenantId = this.#activeTenantId();
const existing = storedTenants.find(s => s.email === emailOrUsername && s.tenantId === activeTenantId); const existing = storedTenants.find(s => s.tenantId === activeTenantId);
if (existing) { if (existing) {
this.#logger.info('[TENANTS] Stored tenant exists: ', storedTenants); this.#logger.info('[TENANTS] Stored tenant exists: ', storedTenants);
return; return;
@ -56,7 +50,7 @@ export class TenantsService {
return; return;
} }
storedTenants.push({ tenantId: activeTenantId, email: emailOrUsername, created: new Date().toISOString() }); storedTenants.push({ tenantId: activeTenantId, created: new Date().toISOString() });
this.#storageReference.setItem(STORED_TENANTS_KEY, JSON.stringify(storedTenants)); this.#storageReference.setItem(STORED_TENANTS_KEY, JSON.stringify(storedTenants));
this.#logger.info('[TENANTS] Stored tenants: ', storedTenants); this.#logger.info('[TENANTS] Stored tenants: ', storedTenants);
} }
@ -72,8 +66,8 @@ export class TenantsService {
const diff = date2.diff(date1, 'days'); const diff = date2.diff(date1, 'days');
const is90DaysOld = diff >= 90; const is90DaysOld = diff >= 90;
if (is90DaysOld) { if (is90DaysOld) {
this.#logger.warn(`[TENANTS] Saved tenant ${s.tenantId} - ${s.email} is 90 days old, delete it`); this.#logger.warn(`[TENANTS] Saved tenant ${s.tenantId} is 90 days old, delete it`);
this.removeStored(s.email); this.removeStored(s.tenantId);
continue; continue;
} }
@ -83,17 +77,16 @@ export class TenantsService {
return validStoredTenants; return validStoredTenants;
} }
removeStored(email: string) { removeStored(tenantId: string) {
if (!email) { if (!tenantId) {
this.#logger.warn('[TENANTS] Email is null, skip storing'); this.#logger.warn('[TENANTS] Tenant Id is null, skip removing');
return; return;
} }
const storedTenants = this.getStoredTenants(); const storedTenants = this.getStoredTenants();
const activeTenantId = this.#activeTenantId(); const existing = storedTenants.find(s => s.tenantId === tenantId);
const existing = storedTenants.find(s => s.email === email && s.tenantId === activeTenantId);
if (!existing) { if (!existing) {
this.#logger.info('[TENANTS] No stored tenant for ', email); this.#logger.info('[TENANTS] No stored tenant for ', tenantId);
return; return;
} }

View File

@ -17,7 +17,7 @@
<div *ngIf="storedTenants.length"> <div *ngIf="storedTenants.length">
<div <div
(click)="select(stored.tenantId, stored.email)" (click)="select(stored.tenantId)"
*ngFor="let stored of storedTenants" *ngFor="let stored of storedTenants"
class="d-flex pointer mat-elevation-z2 card stored-tenant-card mt-10" class="d-flex pointer mat-elevation-z2 card stored-tenant-card mt-10"
> >
@ -25,10 +25,12 @@
<div class="card-content flex-column"> <div class="card-content flex-column">
<span class="heading">{{ stored.tenantId }}</span> <span class="heading">{{ stored.tenantId }}</span>
<span>{{ stored.email }}</span>
</div> </div>
<mat-icon class="card-icon upside-down" svgIcon="iqser:expand"></mat-icon> <mat-icon class="card-icon upside-down" svgIcon="iqser:expand"></mat-icon>
<div class="remove" iqserStopPropagation>
<mat-icon (click)="removeStored(stored.tenantId)" svgIcon="iqser:close"></mat-icon>
</div>
</div> </div>
</div> </div>

View File

@ -23,6 +23,13 @@
.stored-tenant-card { .stored-tenant-card {
width: 450px; width: 450px;
height: 90px; height: 90px;
position: relative;
&:hover {
.remove {
display: flex;
}
}
} }
.card { .card {
@ -66,3 +73,24 @@
line-height: 29px; line-height: 29px;
font-family: 'Inter', sans-serif; font-family: 'Inter', sans-serif;
} }
.remove {
display: none;
width: 16px;
height: 16px;
border-radius: 50%;
background-color: var(--iqser-accent);
color: var(--iqser-white);
position: absolute;
right: -8px;
top: -8px;
line-height: 6px;
justify-content: center;
align-items: center;
mat-icon {
width: 6px;
height: 8px;
}
}

View File

@ -1,14 +1,14 @@
import { ChangeDetectionStrategy, Component, inject, Input } from '@angular/core'; import { ChangeDetectionStrategy, Component, inject, Input } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms'; import { FormBuilder, Validators } from '@angular/forms';
import { TenantsService } from '../services';
import { LoadingService } from '../../loading';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { KeycloakService } from 'keycloak-angular'; import { KeycloakService } from 'keycloak-angular';
import { NGXLogger } from 'ngx-logger';
import { LoadingService } from '../../loading';
import { getConfig } from '../../services';
import { BASE_HREF } from '../../utils'; import { BASE_HREF } from '../../utils';
import { getKeycloakOptions } from '../keycloak-initializer'; import { getKeycloakOptions } from '../keycloak-initializer';
import { IStoredTenantId, TenantsService } from '../services';
import { KeycloakStatusService } from '../services/keycloak-status.service'; import { KeycloakStatusService } from '../services/keycloak-status.service';
import { NGXLogger } from 'ngx-logger';
import { getConfig } from '../../services';
@Component({ @Component({
templateUrl: './tenant-select.component.html', templateUrl: './tenant-select.component.html',
@ -18,9 +18,8 @@ import { getConfig } from '../../services';
export class TenantSelectComponent { export class TenantSelectComponent {
protected readonly baseHref = inject(BASE_HREF); protected readonly baseHref = inject(BASE_HREF);
protected readonly logger = inject(NGXLogger); protected readonly logger = inject(NGXLogger);
protected readonly storedTenants = inject(TenantsService) protected readonly tenantsService = inject(TenantsService);
.getStoredTenants() protected storedTenants: IStoredTenantId[] = [];
.sort((a, b) => a.tenantId.localeCompare(b.tenantId));
protected readonly titleService = inject(Title); protected readonly titleService = inject(Title);
protected readonly config = getConfig(); protected readonly config = getConfig();
protected readonly loadingService = inject(LoadingService); protected readonly loadingService = inject(LoadingService);
@ -32,6 +31,10 @@ export class TenantSelectComponent {
}); });
@Input() isLoggedOut = false; @Input() isLoggedOut = false;
constructor() {
this.#loadStoredTenants();
}
updateTenantSelection() { updateTenantSelection() {
const tenantId = this.form.controls.tenantId.value; const tenantId = this.form.controls.tenantId.value;
if (!tenantId) { if (!tenantId) {
@ -39,12 +42,10 @@ export class TenantSelectComponent {
} }
this.loadingService.start(); this.loadingService.start();
return this.select(tenantId);
const email = this.#getEmail(tenantId);
return this.select(tenantId, email);
} }
async select(tenantId: string, email?: string) { async select(tenantId: string) {
try { try {
this.logger.info('[KEYCLOAK] Initializing keycloak for tenant', tenantId); this.logger.info('[KEYCLOAK] Initializing keycloak for tenant', tenantId);
await this.keycloakService.init(getKeycloakOptions(this.baseHref, this.config, tenantId)); await this.keycloakService.init(getKeycloakOptions(this.baseHref, this.config, tenantId));
@ -55,15 +56,18 @@ export class TenantSelectComponent {
const url = this.keycloakService.getKeycloakInstance().createLoginUrl({ const url = this.keycloakService.getKeycloakInstance().createLoginUrl({
redirectUri: this.keycloakStatusService.createLoginUrl(tenantId), redirectUri: this.keycloakStatusService.createLoginUrl(tenantId),
idpHint: this.config.OAUTH_IDP_HINT, idpHint: this.config.OAUTH_IDP_HINT,
loginHint: email ?? undefined,
}); });
this.logger.info('[KEYCLOAK] Init succeeded. Logout and redirect to', url); this.logger.info('[KEYCLOAK] Init succeeded. Logout and redirect to', url);
return this.keycloakService.logout(url); return this.keycloakService.logout(url);
} }
#getEmail(tenantId: string) { removeStored(tenantId: string) {
const existingStored = this.storedTenants.filter(s => s.tenantId === tenantId); this.tenantsService.removeStored(tenantId);
return existingStored.length === 1 ? existingStored[0].email : undefined; this.#loadStoredTenants();
}
#loadStoredTenants() {
this.storedTenants = this.tenantsService.getStoredTenants().sort((a, b) => a.tenantId.localeCompare(b.tenantId));
} }
} }

View File

@ -1,20 +1,21 @@
import { ModuleWithProviders, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core'; import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
import { CircleButtonComponent, IconButtonComponent } from '../buttons'; import { MatIconModule } from '@angular/material/icon';
import { TenantSelectComponent } from './tenant-select/tenant-select.component';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { ReactiveFormsModule } from '@angular/forms'; import { RouterLink } from '@angular/router';
import { TenantIdInterceptor, TenantIdResponseInterceptor } from './services'; import { TranslateModule } from '@ngx-translate/core';
import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { CircleButtonComponent, IconButtonComponent } from '../buttons';
import { MatCardModule } from '@angular/material/card'; import { StopPropagationDirective } from '../directives';
import { LogoComponent } from '../shared'; import { LogoComponent } from '../shared';
import { SpacerComponent } from '../shared/spacer/spacer.component'; import { SpacerComponent } from '../shared/spacer/spacer.component';
import { MatIconModule } from '@angular/material/icon'; import { TenantIdInterceptor, TenantIdResponseInterceptor } from './services';
import { MatButtonModule } from '@angular/material/button'; import { TenantSelectComponent } from './tenant-select/tenant-select.component';
import { RouterLink } from '@angular/router';
@NgModule({ @NgModule({
declarations: [TenantSelectComponent], declarations: [TenantSelectComponent],
@ -33,6 +34,7 @@ import { RouterLink } from '@angular/router';
MatIconModule, MatIconModule,
MatButtonModule, MatButtonModule,
RouterLink, RouterLink,
StopPropagationDirective,
], ],
exports: [TenantSelectComponent], exports: [TenantSelectComponent],
}) })