tenant configuration changes

This commit is contained in:
Efstratios Giannopoulos 2024-04-26 12:19:24 +03:00
parent 7ea32faa94
commit 8d20ff57e9
19 changed files with 706 additions and 10 deletions

View File

@ -9,7 +9,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<mat-card appearance="outlined"> <mat-card appearance="outlined">
<mat-card-header> <mat-card-header>
<mat-card-title>{{'TENANT-CONFIGURATION-EDITOR.TITLE' | translate}}</mat-card-title> <mat-card-title>{{'TENANT-CONFIGURATION-EDITOR.TITLE' | translate}}</mat-card-title>
@ -65,6 +65,15 @@
<app-tenant-configuration-logo-editor></app-tenant-configuration-logo-editor> <app-tenant-configuration-logo-editor></app-tenant-configuration-logo-editor>
</ng-template> </ng-template>
</mat-expansion-panel> </mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>{{'TENANT-CONFIGURATION-EDITOR.NOTIFIER-LIST.TITLE' | translate}}</mat-panel-title>
<mat-panel-description>{{'TENANT-CONFIGURATION-EDITOR.NOTIFIER-LIST.HINT' | translate}}</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<app-tenant-configuration-notifier-list-editor></app-tenant-configuration-notifier-list-editor>
</ng-template>
</mat-expansion-panel>
</mat-accordion> </mat-accordion>
</div> </div>

View File

@ -19,6 +19,7 @@ import { DepositEditorComponent } from './editor/deposit/deposit-editor.componen
import { FileTransformerEditorComponent } from './editor/file-transformer/file-transformer-editor.component'; import { FileTransformerEditorComponent } from './editor/file-transformer/file-transformer-editor.component';
import { LogoEditorComponent } from './editor/logo/logo-editor.component'; import { LogoEditorComponent } from './editor/logo/logo-editor.component';
import { NgxColorsModule } from 'ngx-colors'; import { NgxColorsModule } from 'ngx-colors';
import { NotifierListModule } from '@notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.module';
@NgModule({ @NgModule({
imports: [ imports: [
@ -35,7 +36,8 @@ import { NgxColorsModule } from 'ngx-colors';
UserSettingsModule, UserSettingsModule,
CommonFormattingModule, CommonFormattingModule,
RichTextEditorModule, RichTextEditorModule,
NgxColorsModule NgxColorsModule,
NotifierListModule
], ],
declarations: [ declarations: [
TenantConfigurationEditorComponent, TenantConfigurationEditorComponent,

View File

@ -316,6 +316,10 @@
"TITLE": "Extra Logo", "TITLE": "Extra Logo",
"HINT": "Add extra logo" "HINT": "Add extra logo"
}, },
"NOTIFIER-LIST":{
"TITLE": "Notification Preferences",
"HINT": "Select available notifiers for Notification Types"
},
"DEPOSIT-PLUGINS":{ "DEPOSIT-PLUGINS":{
"TITLE": "Deposit Plugins", "TITLE": "Deposit Plugins",
"HINT": "Change deposit plugins" "HINT": "Change deposit plugins"

View File

@ -0,0 +1,4 @@
export enum TenantConfigurationType {
NotifierList = 0,
DefaultUserLocale = 1,
}

View File

@ -0,0 +1,37 @@
import { BaseEntity, BaseEntityPersist } from "@common/base/base-entity.model";
import { TenantConfigurationType } from "@notification-service/core/enum/tenant-configuration-type";
import { NotificationContactType } from "@notification-service/core/enum/notification-contact-type";
export interface TenantConfiguration extends BaseEntity{
type?: TenantConfigurationType;
notifierList?: NotifierListTenantConfiguration;
defaultUserLocale?: DefaultUserLocaleTenantConfiguration;
}
export interface NotifierListTenantConfiguration{
notifiers: { [key: string]: NotificationContactType[] };
}
export interface DefaultUserLocaleTenantConfiguration{
timezone: string;
language: string;
culture: string;
}
//persist
export interface TenantConfigurationPersist extends BaseEntityPersist{
type: TenantConfigurationType;
notifierList?: NotifierListTenantConfigurationPersist;
defaultUserLocale?: DefaultUserLocaleTenantConfigurationPersist;
}
export interface NotifierListTenantConfigurationPersist{
notifiers: { [key: string]: NotificationContactType[] };
}
export interface DefaultUserLocaleTenantConfigurationPersist{
timezone: string;
language: string;
culture: string;
}

View File

@ -0,0 +1,26 @@
import { Lookup } from '@common/model/lookup';
import { Guid } from '@common/types/guid';
import { TenantConfigurationType } from '../enum/tenant-configuration-type';
import { IsActive } from '../enum/is-active.enum';
export class TenantConfigurationLookup extends Lookup implements TenantConfigurationFilter {
ids: Guid[];
excludedIds: Guid[];
types: TenantConfigurationType[];
isActive: IsActive[];
tenantIds: Guid[];
tenantIsSet: boolean;
constructor() {
super();
}
}
export interface TenantConfigurationFilter {
ids: Guid[];
excludedIds: Guid[];
types: TenantConfigurationType[];
tenantIds: Guid[];
tenantIsSet: boolean;
isActive: IsActive[];
}

View File

@ -0,0 +1,70 @@
import { Injectable } from '@angular/core';
import { QueryResult } from '@common/model/query-result';
import { FilterService } from '@common/modules/text-filter/filter-service';
import { Guid } from '@common/types/guid';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { BaseHttpV2Service } from '@app/core/services/http/base-http-v2.service';
import { ConfigurationService } from '@app/core/services/configuration/configuration.service';
import { TenantConfigurationLookup } from '@notification-service/core/query/tenant-configuration.lookup';
import { TenantConfiguration, TenantConfigurationPersist } from '@notification-service/core/model/tenant-configuration';
import { TenantConfigurationType } from '@notification-service/core/enum/tenant-configuration-type';
import { NotifierListLookup } from '@notification-service/core/query/notifier-list.lookup';
import { NotifierListConfigurationDataContainer } from '@notification-service/core/model/notifier-configuration.model';
@Injectable()
export class TenantConfigurationService {
constructor(private http: BaseHttpV2Service, private configurationService: ConfigurationService, private filterService: FilterService) {
}
private get apiBase(): string { return `${this.configurationService.notificationServiceAddress}api/notification/tenant-configuration`; }
query(q: TenantConfigurationLookup): Observable<QueryResult<TenantConfiguration>> {
const url = `${this.apiBase}/query`;
return this.http.post<QueryResult<TenantConfiguration>>(url, q).pipe(catchError((error: any) => throwError(error)));
}
getSingle(id: Guid, reqFields: string[] = []): Observable<TenantConfiguration> {
const url = `${this.apiBase}/${id}`;
const options = { params: { f: reqFields } };
return this.http
.get<TenantConfiguration>(url, options).pipe(
catchError((error: any) => throwError(error)));
}
getCurrentTenantType(type: TenantConfigurationType, reqFields: string[] = []): Observable<TenantConfiguration> {
const url = `${this.apiBase}/current-tenant/${type}`;
const options = { params: { f: reqFields } };
return this.http
.get<TenantConfiguration>(url, options).pipe(
catchError((error: any) => throwError(error)));
}
persist(item: TenantConfigurationPersist): Observable<TenantConfiguration> {
const url = `${this.apiBase}/persist`;
return this.http
.post<TenantConfiguration>(url, item).pipe(
catchError((error: any) => throwError(error)));
}
delete(id: Guid): Observable<void> {
const url = `${this.apiBase}/${id}`;
return this.http
.delete<void>(url).pipe(
catchError((error: any) => throwError(error)));
}
getNotifierList(q: NotifierListLookup): Observable<NotifierListConfigurationDataContainer> {
const url = `${this.apiBase}/notifier-list/available`;
return this.http
.post<NotifierListConfigurationDataContainer>(url, q).pipe(
catchError((error: any) => throwError(error)));
}
}

View File

@ -0,0 +1,34 @@
<div *ngIf="formGroup" class="container-fluid notifier-list ">
<div class="row">
<div class="col-12">
<div class="row">
<div class="col-12" *ngFor="let notificationType of availableNotifiersKeys">
<div class="row">
<mat-selection-list class="col-12" [formControl]="formGroup.get('notifierList')?.get('notifiers')?.get(notificationType)" cdkDropList (cdkDropListDropped)="dropped($event, notificationType)">
<h3 mat-subheader>{{notificationServiceEnumUtils.toNotificationTypeString(notificationType)}}</h3>
<mat-list-option *ngFor="let contactType of availableNotifiers[notificationType]" [value]="contactType" cdkDrag>
<div class="d-flex align-items-center">
<mat-icon matListIcon cdkDragHandle>unfold_more</mat-icon>
{{notificationServiceEnumUtils.toContactTypeString(contactType)}}
</div>
</mat-list-option>
</mat-selection-list>
</div>
</div>
</div>
</div>
<div class="col-12">
<div class="row actions-row">
<div class="ml-auto col-auto" *ngIf="editorModel.id"><button class="normal-btn-sm" (click)="delete()">
{{'TENANT-CONFIGURATION-EDITOR.ACTIONS.RESET-TO-DEFAULT' | translate}}
</button>
</div>
<div class="ml-auto col-auto"><button class="normal-btn-sm" (click)="formSubmit()">
{{'TENANT-CONFIGURATION-EDITOR.ACTIONS.SAVE' | translate}}
</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,255 @@
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { HttpErrorResponse } from '@angular/common/http';
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { FormGroup, UntypedFormGroup } from '@angular/forms';
import { AppPermission } from '@app/core/common/enum/permission.enum';
import { AuthService } from '@app/core/services/auth/auth.service';
import { LoggingService } from '@app/core/services/logging/logging-service';
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
import { BasePendingChangesComponent } from '@common/base/base-pending-changes.component';
import { FormService } from '@common/forms/form-service';
import { HttpError, HttpErrorHandlingService } from '@common/modules/errors/error-handling/http-error-handling.service';
import { TranslateService } from '@ngx-translate/core';
import { NotificationContactType } from '@notification-service/core/enum/notification-contact-type';
import { NotificationTrackingProcess } from '@notification-service/core/enum/notification-tracking-process.enum';
import { NotificationType } from '@notification-service/core/enum/notification-type.enum';
import { NotificationServiceEnumUtils } from '@notification-service/core/formatting/enum-utils.service';
import { UserNotificationPreference } from '@notification-service/core/model/user-notification-preference.model';
import { NotifierListLookup } from '@notification-service/core/query/notifier-list.lookup';
import { UserNotificationPreferenceService } from '@notification-service/services/http/user-notification-preference.service';
import { Observable } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof';
import { TenantConfigurationEditorModel } from './notifier-list-editor.model';
import { TenantConfiguration, TenantConfigurationPersist } from '@notification-service/core/model/tenant-configuration';
import { TenantConfigurationService } from '@notification-service/services/http/tenant-configuration.service';
import { TenantConfigurationType } from '@notification-service/core/enum/tenant-configuration-type';
import { NotifierListEditorResolver } from './notifier-list-editor.resolver';
import { ResponseErrorCode } from '@app/core/common/enum/respone-error-code';
import { NotifierListEditorService } from './notifier-list-editor.service';
import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component';
import { MatDialog } from '@angular/material/dialog';
@Component({
selector: 'app-tenant-configuration-notifier-list-editor',
templateUrl: './notifier-list-editor.component.html',
styleUrls: ['./notifier-list-editor.component.scss'],
providers: [NotifierListEditorService]
})
export class NotifierListEditorComponent extends BasePendingChangesComponent implements OnInit {
availableNotifiers: { [key: string]: NotificationContactType[] } = {};
availableNotifiersKeys: NotificationType[];
get editorModel(): TenantConfigurationEditorModel { return this._editorModel; }
set editorModel(value: TenantConfigurationEditorModel) { this._editorModel = value; }
private _editorModel: TenantConfigurationEditorModel;
notificationTrackingProcess: NotificationTrackingProcess = NotificationTrackingProcess.PENDING;
isNew = true;
formGroup: UntypedFormGroup = null;
protected get canDelete(): boolean {
return !this.isNew && this.hasPermission(this.authService.permissionEnum.DeleteTenantConfiguration);
}
protected get canSave(): boolean {
return this.hasPermission(this.authService.permissionEnum.EditTenantConfiguration);
}
private hasPermission(permission: AppPermission): boolean {
return this.authService.hasPermission(permission);
}
constructor(
protected dialog: MatDialog,
private uiNotificationService: UiNotificationService,
private language: TranslateService,
private httpErrorHandlingService: HttpErrorHandlingService,
private authService: AuthService,
protected formService: FormService,
private logger: LoggingService,
private tenantConfigurationService: TenantConfigurationService,
public notificationServiceEnumUtils: NotificationServiceEnumUtils,
public notifierListEditorService: NotifierListEditorService
) {
super();
}
canDeactivate(): boolean | Observable<boolean> {
return this.formGroup ? !this.formGroup.dirty : true;
}
ngOnInit(): void {
this.getConfiguration();
}
getConfiguration() {
this.formGroup = null;
this.tenantConfigurationService.getNotifierList(new NotifierListLookup())
.pipe(takeUntil(this._destroyed))
.subscribe(
data => {
try {
this.availableNotifiers = data.notifiers;
this.availableNotifiersKeys = Object.keys(this.availableNotifiers) as NotificationType[];
this.getExistingSelections();
} catch {
this.notificationTrackingProcess = NotificationTrackingProcess.ERROR;
this.logger.error('Could not parse Dataset: ' + data);
this.uiNotificationService.snackBarNotification(this.language.instant('COMMONS.ERRORS.DEFAULT'), SnackBarNotificationLevel.Error);
}
},
error => this.onCallbackError(error)
);
}
getExistingSelections() {
this.tenantConfigurationService.getCurrentTenantType(TenantConfigurationType.NotifierList, NotifierListEditorResolver.lookupFields())
.pipe(takeUntil(this._destroyed)).subscribe(
data => {
try {
if (data?.notifierList?.notifiers) {
this.orderAvailableItemsbasedOnExistingSelections(data);
}
this.editorModel = data?.notifierList?.notifiers ? new TenantConfigurationEditorModel().fromModel(data) : new TenantConfigurationEditorModel();
this.buildForm();
} catch {
this.notificationTrackingProcess = NotificationTrackingProcess.ERROR;
this.logger.error('Could not parse Dataset: ' + data);
this.uiNotificationService.snackBarNotification(this.language.instant('COMMONS.ERRORS.DEFAULT'), SnackBarNotificationLevel.Error);
}
},
error => this.onCallbackError(error)
);
}
orderAvailableItemsbasedOnExistingSelections(existingSelections: TenantConfiguration) {
if (!existingSelections?.notifierList?.notifiers) { return; }
this.availableNotifiersKeys.forEach(key => {
const orderedList = [];
orderedList.push(...(existingSelections.notifierList.notifiers[key] || []).filter(x => this.availableNotifiers[key].includes(x))); // First push the selected ordered values.
orderedList.push(...this.availableNotifiers[key].filter(x => !orderedList.includes(x))); //Then push the rest items.
this.availableNotifiers[key] = orderedList;
});
}
dropped(event: CdkDragDrop<string[]>, type: NotificationType) {
moveItemInArray(this.availableNotifiers[type], event.previousIndex, event.currentIndex);
}
onCallbackError(errorResponse: HttpErrorResponse) {
console.log("Error:", errorResponse);
const error: HttpError = this.httpErrorHandlingService.getError(errorResponse);
if (error.statusCode === 400) {
this.editorModel.validationErrorModel.fromJSONObject(errorResponse.error);
if(errorResponse.error.code === ResponseErrorCode.TenantConfigurationTypeCanNotChange){
this.uiNotificationService.snackBarNotification(errorResponse.error.error, SnackBarNotificationLevel.Error);
}
if(errorResponse.error.code === ResponseErrorCode.MultipleTenantConfigurationTypeNotAllowed){
this.uiNotificationService.snackBarNotification(errorResponse.error.error, SnackBarNotificationLevel.Error);
}
this.formService.validateAllFormFields(this.formGroup);
} else {
this.uiNotificationService.snackBarNotification(error.getMessagesString(), SnackBarNotificationLevel.Warning);
}
}
onCallbackSuccess(data?: any): void {
console.log("Success:", data);
this.uiNotificationService.snackBarNotification(this.isNew ? this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-CREATION') : this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-UPDATE'), SnackBarNotificationLevel.Success);
this.refreshData();
}
prepareForm(data: TenantConfiguration) {
try {
this.editorModel = data ? new TenantConfigurationEditorModel().fromModel(data) : new TenantConfigurationEditorModel();
this.buildForm();
} catch (error) {
this.logger.error('Could not parse TenantConfiguration item: ' + data + error);
this.uiNotificationService.snackBarNotification(this.language.instant('COMMONS.ERRORS.DEFAULT'), SnackBarNotificationLevel.Error);
}
}
buildForm() {
this.formGroup = this.editorModel.buildForm(this.availableNotifiersKeys, this.availableNotifiers, null, !this.authService.hasPermission(AppPermission.EditTenantConfiguration));
this.notifierListEditorService.setValidationErrorModel(this.editorModel.validationErrorModel);
}
refreshData(): void {
this.getConfiguration();
}
persistEntity(onSuccess?: (response) => void): void {
const formData = this.formService.getValue(this.formGroup.value) as TenantConfigurationPersist;
// Clear empty or null selections.
if (formData?.notifierList?.notifiers) {
Object.keys(formData.notifierList.notifiers).forEach(key => {
if (formData.notifierList.notifiers[key] == null || formData.notifierList.notifiers[key].length === 0) { delete formData.notifierList.notifiers[key]; }
});
}
this.tenantConfigurationService.persist(formData)
.pipe(takeUntil(this._destroyed)).subscribe(
complete => onSuccess ? onSuccess(complete) : this.onCallbackSuccess(complete),
error => this.onCallbackError(error)
);
}
formSubmit(): void {
this.clearErrorModel();
this.formService.removeAllBackEndErrors(this.formGroup);
this.formService.touchAllFormFields(this.formGroup);
if (!this.isFormValid()) {
return;
}
this.persistEntity();
}
public isFormValid() {
return this.formGroup.valid;
}
public delete() {
const value = this.formGroup.value;
if (value.id) {
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
maxWidth: '300px',
data: {
message: this.language.instant('TENANT-CONFIGURATION-EDITOR.RESET-TO-DEFAULT-DIALOG.RESET-TO-DEFAULT'),
confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'),
cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL')
}
});
dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => {
if (result) {
this.tenantConfigurationService.delete(value.id).pipe(takeUntil(this._destroyed))
.subscribe(
complete => this.onCallbackDeleteSuccessConfig(),
error => this.onCallbackError(error)
);
}
});
}
}
onCallbackDeleteSuccessConfig(data?: any): void {
console.log("Success Delete:", data);
this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-RESET'), SnackBarNotificationLevel.Success);
this.prepareForm(null)
}
clearErrorModel() {
this.editorModel.validationErrorModel.clear();
this.formService.validateAllFormFields(this.formGroup);
}
}

View File

@ -0,0 +1,117 @@
import { FormControl, UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms";
import { BaseEditorModel } from "@common/base/base-form-editor-model";
import { BackendErrorValidator } from "@common/forms/validation/custom-validator";
import { ValidationErrorModel } from "@common/forms/validation/error-model/validation-error-model";
import { Validation, ValidationContext } from "@common/forms/validation/validation-context";
import { NotificationContactType } from "@notification-service/core/enum/notification-contact-type";
import { NotificationType } from "@notification-service/core/enum/notification-type.enum";
import { TenantConfigurationType } from "@notification-service/core/enum/tenant-configuration-type";
import { NotifierListTenantConfiguration, NotifierListTenantConfigurationPersist, TenantConfiguration, TenantConfigurationPersist } from "@notification-service/core/model/tenant-configuration";
export class TenantConfigurationEditorModel extends BaseEditorModel implements TenantConfigurationPersist {
type: TenantConfigurationType;
notifierList: NotifierListTenantConfigurationEditorModel = new NotifierListTenantConfigurationEditorModel(this.validationErrorModel);
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel();
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
constructor() { super(); this.type = TenantConfigurationType.NotifierList; }
public fromModel(item: TenantConfiguration): TenantConfigurationEditorModel {
if (item) {
super.fromModel(item);
this.type = item.type;
if (item.notifierList) this.notifierList = new NotifierListTenantConfigurationEditorModel(this.validationErrorModel).fromModel(item.notifierList);
} else {
this.type = TenantConfigurationType.NotifierList;
}
return this;
}
buildForm(availableNotificationTypes: NotificationType[], availableNotifiers: { [key: string]: NotificationContactType[] }, context: ValidationContext = null, disabled: boolean = false): UntypedFormGroup {
if (context == null) { context = this.createValidationContext(); }
return this.formBuilder.group({
id: [{ value: this.id, disabled: disabled }, context.getValidation('id').validators],
type: [{ value: this.type, disabled: disabled }, context.getValidation('type').validators],
hash: [{ value: this.hash, disabled: disabled }, context.getValidation('hash').validators],
notifierList: this.notifierList.buildForm({
availableNotificationTypes: availableNotificationTypes,
availableNotifiers: availableNotifiers,
rootPath: `NotifierList.`,
}),
});
}
createValidationContext(): ValidationContext {
const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>();
baseValidationArray.push({ key: 'id', validators: [BackendErrorValidator(this.validationErrorModel, 'id')] });
baseValidationArray.push({ key: 'type', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'type')] });
baseValidationArray.push({ key: 'hash', validators: [] });
baseContext.validation = baseValidationArray;
return baseContext;
}
}
export class NotifierListTenantConfigurationEditorModel implements NotifierListTenantConfigurationPersist {
notifiers: { [key: string]: NotificationContactType[] };
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
constructor(
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()
) { }
public fromModel(item: NotifierListTenantConfiguration): NotifierListTenantConfigurationEditorModel {
if (item) {
this.notifiers = item.notifiers;
}
return this;
}
buildForm(params?: {
availableNotificationTypes: NotificationType[],
availableNotifiers: { [key: string]: NotificationContactType[] };
context?: ValidationContext,
disabled?: boolean,
rootPath?: string
}): UntypedFormGroup {
let { context = null, disabled = false, rootPath } = params ?? {}
if (context == null) {
context = NotifierListTenantConfigurationEditorModel.createValidationContext({
validationErrorModel: this.validationErrorModel,
rootPath
});
}
const notifiersFormGroup = this.formBuilder.group({});
if (params?.availableNotificationTypes) {
params.availableNotificationTypes.forEach(type => {
notifiersFormGroup.addControl(type, new FormControl(this.notifiers ? this.notifiers[type] : (params.availableNotifiers ? params.availableNotifiers[type] : undefined)));
});
}
const form: UntypedFormGroup = this.formBuilder.group({
notifiers: notifiersFormGroup,
});
return form;
}
static createValidationContext(params: {
rootPath?: string,
validationErrorModel: ValidationErrorModel
}): ValidationContext {
const { rootPath = '', validationErrorModel } = params;
const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>();
baseValidationArray.push({ key: 'notifiers', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}notifiers`)] });
baseContext.validation = baseValidationArray;
return baseContext;
}
}

View File

@ -0,0 +1,22 @@
import { DragDropModule } from '@angular/cdk/drag-drop';
import { NgModule } from '@angular/core';
import { CommonFormsModule } from '@common/forms/common-forms.module';
import { CommonUiModule } from '@common/ui/common-ui.module';
import { CoreNotificationServiceModule } from '@notification-service/services/core-service.module';
// import { TotpModule } from '@idp-service/ui/totp/totp.module';
import { NotifierListEditorComponent } from './notifier-list-editor.component';
@NgModule({
imports: [
CommonUiModule,
CommonFormsModule,
DragDropModule,
],
declarations: [
NotifierListEditorComponent
],
exports: [
NotifierListEditorComponent
]
})
export class NotifierListModule { }

View File

@ -0,0 +1,42 @@
import { Injectable } from '@angular/core';
import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service';
import { BaseEditorResolver } from '@common/base/base-editor.resolver';
import { TenantConfigurationType } from '@notification-service/core/enum/tenant-configuration-type';
import { NotifierListTenantConfiguration, TenantConfiguration } from '@notification-service/core/model/tenant-configuration';
import { TenantConfigurationService } from '@notification-service/services/http/tenant-configuration.service';
import { takeUntil, tap } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof';
@Injectable()
export class NotifierListEditorResolver extends BaseEditorResolver {
constructor(private tenantConfigurationService: TenantConfigurationService, private breadcrumbService: BreadcrumbService) {
super();
}
public static lookupFields(): string[] {
return [
...BaseEditorResolver.lookupFields(),
nameof<TenantConfiguration>(x => x.id),
nameof<TenantConfiguration>(x => x.type),
nameof<TenantConfiguration>(x => x.notifierList),
[nameof<TenantConfiguration>(x => x.notifierList), nameof<NotifierListTenantConfiguration>(x => x.notifiers)].join('.'),
nameof<TenantConfiguration>(x => x.createdAt),
nameof<TenantConfiguration>(x => x.updatedAt),
nameof<TenantConfiguration>(x => x.hash),
nameof<TenantConfiguration>(x => x.isActive)
]
}
resolve() {
const fields = [
...NotifierListEditorResolver.lookupFields()
];
return this.tenantConfigurationService.getCurrentTenantType(TenantConfigurationType.NotifierList, fields).pipe(takeUntil(this._destroyed));
}
}

View File

@ -0,0 +1,15 @@
import { Injectable } from "@angular/core";
import { ValidationErrorModel } from "@common/forms/validation/error-model/validation-error-model";
@Injectable()
export class NotifierListEditorService {
private validationErrorModel: ValidationErrorModel;
public setValidationErrorModel(validationErrorModel: ValidationErrorModel): void {
this.validationErrorModel = validationErrorModel;
}
public getValidationErrorModel(): ValidationErrorModel {
return this.validationErrorModel;
}
}

View File

@ -12,7 +12,6 @@ import gr.cite.notification.model.builder.tenantconfiguration.TenantConfiguratio
import gr.cite.notification.model.censorship.tenantconfiguration.TenantConfigurationCensor; import gr.cite.notification.model.censorship.tenantconfiguration.TenantConfigurationCensor;
import gr.cite.notification.model.persist.tenantconfiguration.TenantConfigurationPersist; import gr.cite.notification.model.persist.tenantconfiguration.TenantConfigurationPersist;
import gr.cite.notification.model.tenantconfiguration.TenantConfiguration; import gr.cite.notification.model.tenantconfiguration.TenantConfiguration;
import gr.cite.notification.model.persist.tenantconfiguration.NotifierListTenantConfigurationPersist;
import gr.cite.notification.query.TenantConfigurationQuery; import gr.cite.notification.query.TenantConfigurationQuery;
import gr.cite.notification.query.lookup.NotifierListLookup; import gr.cite.notification.query.lookup.NotifierListLookup;
import gr.cite.notification.query.lookup.TenantConfigurationLookup; import gr.cite.notification.query.lookup.TenantConfigurationLookup;
@ -30,7 +29,6 @@ import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.logging.MapLogEntry; import gr.cite.tools.logging.MapLogEntry;
import gr.cite.tools.validation.ValidationFilterAnnotation; import gr.cite.tools.validation.ValidationFilterAnnotation;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -40,11 +38,13 @@ import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException; import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException; import javax.crypto.NoSuchPaddingException;
import javax.management.InvalidApplicationException; import javax.management.InvalidApplicationException;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.*; import java.util.AbstractMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@RestController @RestController
@RequestMapping(path = "api/notification/tenant-configuration") @RequestMapping(path = "api/notification/tenant-configuration")
@ -161,4 +161,19 @@ public class TenantConfigurationController {
this.auditService.track(AuditableAction.Tenant_Configuration_Delete, "id", id); this.auditService.track(AuditableAction.Tenant_Configuration_Delete, "id", id);
} }
@PostMapping("notifier-list/available")
public NotifierListTenantConfigurationEntity getAvailableNotifiers(@RequestBody NotifierListLookup tenantNotifierListLookup)
{
logger.debug("querying available notifiers");
NotifierListTenantConfigurationEntity notifierListData = this.tenantConfigurationService.collectTenantAvailableNotifierList(tenantNotifierListLookup.getNotificationTypes());
this.auditService.track(AuditableAction.Tenant_Configuration_Notifiers_Query, Map.of(
"lookup", tenantNotifierListLookup
));
//this._auditService.TrackIdentity(AuditableAction.IdentityTracking_Action);
return notifierListData;
}
} }

View File

@ -38,6 +38,7 @@ public class AuditableAction {
public static final EventId TenantConfiguration_LookupByType = new EventId(210004, "TenantConfiguration_LookupByType"); public static final EventId TenantConfiguration_LookupByType = new EventId(210004, "TenantConfiguration_LookupByType");
public static final EventId Tenant_Configuration_DefaultUserLocale_Delete = new EventId(21005, "Tenant_Configuration_DefaultUserLocale_Delete"); public static final EventId Tenant_Configuration_DefaultUserLocale_Delete = new EventId(21005, "Tenant_Configuration_DefaultUserLocale_Delete");
public static final EventId Tenant_Configuration_DefaultUserLocale_Persist = new EventId(21006, "Tenant_Configuration_DefaultUserLocale_Persist"); public static final EventId Tenant_Configuration_DefaultUserLocale_Persist = new EventId(21006, "Tenant_Configuration_DefaultUserLocale_Persist");
public static final EventId Tenant_Configuration_Notifiers_Query = new EventId(21007, "Tenant_Configuration_Notifiers_Query");
public static final EventId User_Notification_Preference_Query = new EventId(22000, "User_Notification_Preference_Query"); public static final EventId User_Notification_Preference_Query = new EventId(22000, "User_Notification_Preference_Query");
public static final EventId User_Notification_Preference_Lookup = new EventId(22001, "User_Notification_Preference_Lookup"); public static final EventId User_Notification_Preference_Lookup = new EventId(22001, "User_Notification_Preference_Lookup");

View File

@ -20,6 +20,7 @@ import javax.management.InvalidApplicationException;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
public interface TenantConfigurationService { public interface TenantConfigurationService {
@ -32,4 +33,6 @@ public interface TenantConfigurationService {
DefaultUserLocaleTenantConfigurationEntity collectTenantUserLocale(); DefaultUserLocaleTenantConfigurationEntity collectTenantUserLocale();
TenantConfigurationEntity getTenantConfigurationEntityForType(TenantConfigurationType type); TenantConfigurationEntity getTenantConfigurationEntityForType(TenantConfigurationType type);
NotifierListTenantConfigurationEntity collectTenantAvailableNotifierList(Set<UUID> notificationTypes);
} }

View File

@ -5,6 +5,7 @@ import gr.cite.commons.web.authz.service.AuthorizationService;
import gr.cite.notification.authorization.Permission; import gr.cite.notification.authorization.Permission;
import gr.cite.notification.common.JsonHandlingService; import gr.cite.notification.common.JsonHandlingService;
import gr.cite.notification.common.enums.IsActive; import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.common.enums.NotificationContactType;
import gr.cite.notification.common.enums.TenantConfigurationType; import gr.cite.notification.common.enums.TenantConfigurationType;
import gr.cite.notification.common.scope.tenant.TenantScope; import gr.cite.notification.common.scope.tenant.TenantScope;
import gr.cite.notification.common.types.tenantconfiguration.DefaultUserLocaleTenantConfigurationEntity; import gr.cite.notification.common.types.tenantconfiguration.DefaultUserLocaleTenantConfigurationEntity;
@ -51,7 +52,10 @@ import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.time.Instant; import java.time.Instant;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
@Service @Service
public class TenantConfigurationServiceImpl implements TenantConfigurationService { public class TenantConfigurationServiceImpl implements TenantConfigurationService {
@ -77,6 +81,7 @@ public class TenantConfigurationServiceImpl implements TenantConfigurationServic
private final QueryFactory queryFactory; private final QueryFactory queryFactory;
private final EventBroker eventBroker; private final EventBroker eventBroker;
private final TenantScope tenantScope; private final TenantScope tenantScope;
private final Map<UUID, List<NotificationContactType>> globalPoliciesMap;
@Autowired @Autowired
public TenantConfigurationServiceImpl( public TenantConfigurationServiceImpl(
TenantEntityManager entityManager, TenantEntityManager entityManager,
@ -85,7 +90,7 @@ public class TenantConfigurationServiceImpl implements TenantConfigurationServic
BuilderFactory builderFactory, BuilderFactory builderFactory,
ConventionService conventionService, ConventionService conventionService,
ErrorThesaurusProperties errors, ErrorThesaurusProperties errors,
MessageSource messageSource, JsonHandlingService jsonHandlingService, QueryFactory queryFactory, EventBroker eventBroker, TenantScope tenantScope) { MessageSource messageSource, JsonHandlingService jsonHandlingService, QueryFactory queryFactory, EventBroker eventBroker, TenantScope tenantScope, Map<UUID, List<NotificationContactType>> globalPoliciesMap) {
this.entityManager = entityManager; this.entityManager = entityManager;
this.authorizationService = authorizationService; this.authorizationService = authorizationService;
this.deleterFactory = deleterFactory; this.deleterFactory = deleterFactory;
@ -97,6 +102,7 @@ public class TenantConfigurationServiceImpl implements TenantConfigurationServic
this.queryFactory = queryFactory; this.queryFactory = queryFactory;
this.eventBroker = eventBroker; this.eventBroker = eventBroker;
this.tenantScope = tenantScope; this.tenantScope = tenantScope;
this.globalPoliciesMap = globalPoliciesMap;
} }
public TenantConfiguration persist(TenantConfigurationPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JsonProcessingException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { public TenantConfiguration persist(TenantConfigurationPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JsonProcessingException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
@ -214,4 +220,19 @@ public class TenantConfigurationServiceImpl implements TenantConfigurationServic
return query.first(); return query.first();
} }
@Override
public NotifierListTenantConfigurationEntity collectTenantAvailableNotifierList(Set<UUID> notificationTypes) {
this.authorizationService.authorizeForce(Permission.BrowseTenantConfiguration);
Map<UUID, List<NotificationContactType>> currentNotificationListPolicies = this.globalPoliciesMap;
if (notificationTypes != null && !notificationTypes.isEmpty())
{
return new NotifierListTenantConfigurationEntity(currentNotificationListPolicies
.entrySet().stream()
.filter(x -> notificationTypes.contains(x.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
}
else return new NotifierListTenantConfigurationEntity(currentNotificationListPolicies);
}
} }

View File

@ -5,6 +5,7 @@ import gr.cite.notification.authorization.OwnedResource;
import gr.cite.notification.authorization.Permission; import gr.cite.notification.authorization.Permission;
import gr.cite.notification.common.enums.IsActive; import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.common.enums.NotificationContactType; import gr.cite.notification.common.enums.NotificationContactType;
import gr.cite.notification.common.scope.tenant.TenantScope;
import gr.cite.notification.common.types.tenantconfiguration.NotifierListTenantConfigurationEntity; import gr.cite.notification.common.types.tenantconfiguration.NotifierListTenantConfigurationEntity;
import gr.cite.notification.config.notification.NotificationConfig; import gr.cite.notification.config.notification.NotificationConfig;
import gr.cite.notification.data.TenantEntityManager; import gr.cite.notification.data.TenantEntityManager;
@ -46,6 +47,7 @@ public class UserNotificationPreferenceServiceImpl implements UserNotificationPr
private final Map<UUID, List<NotificationContactType>> globalPoliciesMap; private final Map<UUID, List<NotificationContactType>> globalPoliciesMap;
private final ErrorThesaurusProperties errors; private final ErrorThesaurusProperties errors;
private final TenantEntityManager entityManager; private final TenantEntityManager entityManager;
private final TenantScope tenantScope;
@Autowired @Autowired
public UserNotificationPreferenceServiceImpl(QueryFactory queryFactory, public UserNotificationPreferenceServiceImpl(QueryFactory queryFactory,
@ -54,7 +56,7 @@ public class UserNotificationPreferenceServiceImpl implements UserNotificationPr
TenantConfigurationService tenantConfigurationService, TenantConfigurationService tenantConfigurationService,
@Qualifier(NotificationConfig.BeanQualifier.GLOBAL_POLICIES_MAP) @Qualifier(NotificationConfig.BeanQualifier.GLOBAL_POLICIES_MAP)
Map<UUID, List<NotificationContactType>> globalPoliciesMap, Map<UUID, List<NotificationContactType>> globalPoliciesMap,
ErrorThesaurusProperties errors, TenantEntityManager entityManager) { ErrorThesaurusProperties errors, TenantEntityManager entityManager, TenantScope tenantScope) {
this.queryFactory = queryFactory; this.queryFactory = queryFactory;
this.builderFactory = builderFactory; this.builderFactory = builderFactory;
this.authService = authService; this.authService = authService;
@ -62,6 +64,7 @@ public class UserNotificationPreferenceServiceImpl implements UserNotificationPr
this.globalPoliciesMap = globalPoliciesMap; this.globalPoliciesMap = globalPoliciesMap;
this.errors = errors; this.errors = errors;
this.entityManager = entityManager; this.entityManager = entityManager;
this.tenantScope = tenantScope;
} }
@Override @Override
@ -129,7 +132,14 @@ public class UserNotificationPreferenceServiceImpl implements UserNotificationPr
.build(new BaseFieldSet(UserNotificationPreference._userId, UserNotificationPreference._type, .build(new BaseFieldSet(UserNotificationPreference._userId, UserNotificationPreference._type,
UserNotificationPreference._channel, UserNotificationPreference._ordinal), this.queryFactory UserNotificationPreference._channel, UserNotificationPreference._ordinal), this.queryFactory
.query(UserNotificationPreferenceQuery.class) .query(UserNotificationPreferenceQuery.class)
.userId(ids).collect()).stream().collect(Collectors.groupingBy(UserNotificationPreference::getUserId)); //GK: Yep that exist on JAVA Streams .userId(ids).collect()).stream().filter(x -> {
try {
return !this.tenantScope.isMultitenant() || this.tenantScope.isDefaultTenant() ? x.getTenantId() == null : x.getTenantId() == this.tenantScope.getTenant();
} catch (InvalidApplicationException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.groupingBy(UserNotificationPreference::getUserId)); //GK: Yep that exist on JAVA Streams
} }
private Map<UUID, List<NotificationContactType>> mergeNotifierPolicies(Map<UUID, List<NotificationContactType>> overrides, Map<UUID, List<NotificationContactType>> bases) private Map<UUID, List<NotificationContactType>> mergeNotifierPolicies(Map<UUID, List<NotificationContactType>> overrides, Map<UUID, List<NotificationContactType>> bases)
@ -177,7 +187,13 @@ public class UserNotificationPreferenceServiceImpl implements UserNotificationPr
List<UserNotificationPreferenceEntity> updatedPreferences = new ArrayList<>(); List<UserNotificationPreferenceEntity> updatedPreferences = new ArrayList<>();
for (NotificationContactType contactType : contactTypes) { for (NotificationContactType contactType : contactTypes) {
UserNotificationPreferenceEntity preference = preferences.stream().filter(x -> x.getChannel() == contactType).findFirst().orElse(null); UserNotificationPreferenceEntity preference = preferences.stream().filter(x -> {
try {
return x.getChannel() == contactType && (!this.tenantScope.isMultitenant() ||this.tenantScope.isDefaultTenant() ? x.getTenantId() == null : x.getTenantId() == this.tenantScope.getTenant());
} catch (InvalidApplicationException e) {
throw new RuntimeException(e);
}
}).findFirst().orElse(null);
boolean isUpdate = preference != null; boolean isUpdate = preference != null;
if (preference != null) { if (preference != null) {