From 8d20ff57e9c78e4e8df9b609de65c240c178a31d Mon Sep 17 00:00:00 2001 From: sgiannopoulos Date: Fri, 26 Apr 2024 12:19:24 +0300 Subject: [PATCH] tenant configuration changes --- ...tenant-configuration-editor.component.html | 11 +- .../tenant-configuration.module.ts | 4 +- dmp-frontend/src/assets/i18n/en.json | 4 + .../core/enum/tenant-configuration-type.ts | 4 + .../core/model/tenant-configuration.ts | 37 +++ .../core/query/tenant-configuration.lookup.ts | 26 ++ .../http/tenant-configuration.service.ts | 70 +++++ .../notifier-list-editor.component.html | 34 +++ .../notifier-list-editor.component.scss | 3 + .../notifier-list-editor.component.ts | 255 ++++++++++++++++++ .../notifier-list-editor.model.ts | 117 ++++++++ .../notifier-list-editor.module.ts | 22 ++ .../notifier-list-editor.resolver.ts | 42 +++ .../notifier-list-editor.service.ts | 15 ++ .../TenantConfigurationController.java | 23 +- .../notification/audit/AuditableAction.java | 1 + .../TenantConfigurationService.java | 3 + .../TenantConfigurationServiceImpl.java | 23 +- ...UserNotificationPreferenceServiceImpl.java | 22 +- 19 files changed, 706 insertions(+), 10 deletions(-) create mode 100644 dmp-frontend/src/notification-service/core/enum/tenant-configuration-type.ts create mode 100644 dmp-frontend/src/notification-service/core/model/tenant-configuration.ts create mode 100644 dmp-frontend/src/notification-service/core/query/tenant-configuration.lookup.ts create mode 100644 dmp-frontend/src/notification-service/services/http/tenant-configuration.service.ts create mode 100644 dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.component.html create mode 100644 dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.component.scss create mode 100644 dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.component.ts create mode 100644 dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.model.ts create mode 100644 dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.module.ts create mode 100644 dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.resolver.ts create mode 100644 dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.service.ts diff --git a/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/tenant-configuration-editor.component.html b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/tenant-configuration-editor.component.html index 25c9dd65d..0bcc1e28e 100644 --- a/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/tenant-configuration-editor.component.html +++ b/dmp-frontend/src/app/ui/admin/tenant-configuration/editor/tenant-configuration-editor.component.html @@ -9,7 +9,7 @@
-
+
{{'TENANT-CONFIGURATION-EDITOR.TITLE' | translate}} @@ -65,6 +65,15 @@ + + + {{'TENANT-CONFIGURATION-EDITOR.NOTIFIER-LIST.TITLE' | translate}} + {{'TENANT-CONFIGURATION-EDITOR.NOTIFIER-LIST.HINT' | translate}} + + + + +
diff --git a/dmp-frontend/src/app/ui/admin/tenant-configuration/tenant-configuration.module.ts b/dmp-frontend/src/app/ui/admin/tenant-configuration/tenant-configuration.module.ts index 94bab108b..c3027a6cb 100644 --- a/dmp-frontend/src/app/ui/admin/tenant-configuration/tenant-configuration.module.ts +++ b/dmp-frontend/src/app/ui/admin/tenant-configuration/tenant-configuration.module.ts @@ -19,6 +19,7 @@ import { DepositEditorComponent } from './editor/deposit/deposit-editor.componen import { FileTransformerEditorComponent } from './editor/file-transformer/file-transformer-editor.component'; import { LogoEditorComponent } from './editor/logo/logo-editor.component'; import { NgxColorsModule } from 'ngx-colors'; +import { NotifierListModule } from '@notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.module'; @NgModule({ imports: [ @@ -35,7 +36,8 @@ import { NgxColorsModule } from 'ngx-colors'; UserSettingsModule, CommonFormattingModule, RichTextEditorModule, - NgxColorsModule + NgxColorsModule, + NotifierListModule ], declarations: [ TenantConfigurationEditorComponent, diff --git a/dmp-frontend/src/assets/i18n/en.json b/dmp-frontend/src/assets/i18n/en.json index 6ea037d4f..2ef3e4ae0 100644 --- a/dmp-frontend/src/assets/i18n/en.json +++ b/dmp-frontend/src/assets/i18n/en.json @@ -316,6 +316,10 @@ "TITLE": "Extra Logo", "HINT": "Add extra logo" }, + "NOTIFIER-LIST":{ + "TITLE": "Notification Preferences", + "HINT": "Select available notifiers for Notification Types" + }, "DEPOSIT-PLUGINS":{ "TITLE": "Deposit Plugins", "HINT": "Change deposit plugins" diff --git a/dmp-frontend/src/notification-service/core/enum/tenant-configuration-type.ts b/dmp-frontend/src/notification-service/core/enum/tenant-configuration-type.ts new file mode 100644 index 000000000..2dd2eeb73 --- /dev/null +++ b/dmp-frontend/src/notification-service/core/enum/tenant-configuration-type.ts @@ -0,0 +1,4 @@ +export enum TenantConfigurationType { + NotifierList = 0, + DefaultUserLocale = 1, +} diff --git a/dmp-frontend/src/notification-service/core/model/tenant-configuration.ts b/dmp-frontend/src/notification-service/core/model/tenant-configuration.ts new file mode 100644 index 000000000..8a1e79245 --- /dev/null +++ b/dmp-frontend/src/notification-service/core/model/tenant-configuration.ts @@ -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; +} diff --git a/dmp-frontend/src/notification-service/core/query/tenant-configuration.lookup.ts b/dmp-frontend/src/notification-service/core/query/tenant-configuration.lookup.ts new file mode 100644 index 000000000..8fd2a2da4 --- /dev/null +++ b/dmp-frontend/src/notification-service/core/query/tenant-configuration.lookup.ts @@ -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[]; +} diff --git a/dmp-frontend/src/notification-service/services/http/tenant-configuration.service.ts b/dmp-frontend/src/notification-service/services/http/tenant-configuration.service.ts new file mode 100644 index 000000000..3d4fcb9f0 --- /dev/null +++ b/dmp-frontend/src/notification-service/services/http/tenant-configuration.service.ts @@ -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> { + const url = `${this.apiBase}/query`; + return this.http.post>(url, q).pipe(catchError((error: any) => throwError(error))); + } + + getSingle(id: Guid, reqFields: string[] = []): Observable { + const url = `${this.apiBase}/${id}`; + const options = { params: { f: reqFields } }; + + return this.http + .get(url, options).pipe( + catchError((error: any) => throwError(error))); + } + + getCurrentTenantType(type: TenantConfigurationType, reqFields: string[] = []): Observable { + const url = `${this.apiBase}/current-tenant/${type}`; + const options = { params: { f: reqFields } }; + + return this.http + .get(url, options).pipe( + catchError((error: any) => throwError(error))); + } + + + persist(item: TenantConfigurationPersist): Observable { + const url = `${this.apiBase}/persist`; + + return this.http + .post(url, item).pipe( + catchError((error: any) => throwError(error))); + } + + delete(id: Guid): Observable { + const url = `${this.apiBase}/${id}`; + + return this.http + .delete(url).pipe( + catchError((error: any) => throwError(error))); + } + + getNotifierList(q: NotifierListLookup): Observable { + const url = `${this.apiBase}/notifier-list/available`; + + return this.http + .post(url, q).pipe( + catchError((error: any) => throwError(error))); + } +} diff --git a/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.component.html b/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.component.html new file mode 100644 index 000000000..b6f75150b --- /dev/null +++ b/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.component.html @@ -0,0 +1,34 @@ +
+
+
+
+
+
+ +

{{notificationServiceEnumUtils.toNotificationTypeString(notificationType)}}

+ +
+ unfold_more + {{notificationServiceEnumUtils.toContactTypeString(contactType)}} +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ diff --git a/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.component.scss b/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.component.scss new file mode 100644 index 000000000..8900a03c9 --- /dev/null +++ b/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.component.scss @@ -0,0 +1,3 @@ +.notifier-list { + +} diff --git a/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.component.ts b/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.component.ts new file mode 100644 index 000000000..85d45f03a --- /dev/null +++ b/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.component.ts @@ -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 { + 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, 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); + } +} diff --git a/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.model.ts b/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.model.ts new file mode 100644 index 000000000..5311ca0c3 --- /dev/null +++ b/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.model.ts @@ -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(); + 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(); + baseValidationArray.push({ key: 'notifiers', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}notifiers`)] }); + + baseContext.validation = baseValidationArray; + return baseContext; + } +} diff --git a/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.module.ts b/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.module.ts new file mode 100644 index 000000000..e7f708751 --- /dev/null +++ b/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.module.ts @@ -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 { } diff --git a/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.resolver.ts b/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.resolver.ts new file mode 100644 index 000000000..4575f71d6 --- /dev/null +++ b/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.resolver.ts @@ -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(x => x.id), + nameof(x => x.type), + nameof(x => x.notifierList), + + [nameof(x => x.notifierList), nameof(x => x.notifiers)].join('.'), + + + nameof(x => x.createdAt), + nameof(x => x.updatedAt), + nameof(x => x.hash), + nameof(x => x.isActive) + ] + } + + resolve() { + + const fields = [ + ...NotifierListEditorResolver.lookupFields() + ]; + + return this.tenantConfigurationService.getCurrentTenantType(TenantConfigurationType.NotifierList, fields).pipe(takeUntil(this._destroyed)); + } +} diff --git a/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.service.ts b/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.service.ts new file mode 100644 index 000000000..141c667b9 --- /dev/null +++ b/dmp-frontend/src/notification-service/ui/admin/tenant-configuration/notifier-list/notifier-list-editor.service.ts @@ -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; + } +} diff --git a/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/TenantConfigurationController.java b/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/TenantConfigurationController.java index dc71dca47..dd3ac093e 100644 --- a/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/TenantConfigurationController.java +++ b/notification-service/notification-web/src/main/java/gr/cite/notification/web/controllers/TenantConfigurationController.java @@ -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.persist.tenantconfiguration.TenantConfigurationPersist; 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.lookup.NotifierListLookup; 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.validation.ValidationFilterAnnotation; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.transaction.annotation.Transactional; @@ -40,11 +38,13 @@ import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.management.InvalidApplicationException; - import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.util.*; +import java.util.AbstractMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; @RestController @RequestMapping(path = "api/notification/tenant-configuration") @@ -161,4 +161,19 @@ public class TenantConfigurationController { 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; + } + } diff --git a/notification-service/notification/src/main/java/gr/cite/notification/audit/AuditableAction.java b/notification-service/notification/src/main/java/gr/cite/notification/audit/AuditableAction.java index 2d8c92f7b..8fdb53f81 100644 --- a/notification-service/notification/src/main/java/gr/cite/notification/audit/AuditableAction.java +++ b/notification-service/notification/src/main/java/gr/cite/notification/audit/AuditableAction.java @@ -38,6 +38,7 @@ public class AuditableAction { 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_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_Lookup = new EventId(22001, "User_Notification_Preference_Lookup"); diff --git a/notification-service/notification/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationService.java b/notification-service/notification/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationService.java index 611ae8ede..b83d943e5 100644 --- a/notification-service/notification/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationService.java +++ b/notification-service/notification/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationService.java @@ -20,6 +20,7 @@ import javax.management.InvalidApplicationException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.util.Set; import java.util.UUID; public interface TenantConfigurationService { @@ -32,4 +33,6 @@ public interface TenantConfigurationService { DefaultUserLocaleTenantConfigurationEntity collectTenantUserLocale(); TenantConfigurationEntity getTenantConfigurationEntityForType(TenantConfigurationType type); + + NotifierListTenantConfigurationEntity collectTenantAvailableNotifierList(Set notificationTypes); } diff --git a/notification-service/notification/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationServiceImpl.java b/notification-service/notification/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationServiceImpl.java index 65e9b74aa..85c401050 100644 --- a/notification-service/notification/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationServiceImpl.java +++ b/notification-service/notification/src/main/java/gr/cite/notification/service/tenantconfiguration/TenantConfigurationServiceImpl.java @@ -5,6 +5,7 @@ import gr.cite.commons.web.authz.service.AuthorizationService; import gr.cite.notification.authorization.Permission; import gr.cite.notification.common.JsonHandlingService; 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.scope.tenant.TenantScope; import gr.cite.notification.common.types.tenantconfiguration.DefaultUserLocaleTenantConfigurationEntity; @@ -51,7 +52,10 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.time.Instant; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; @Service public class TenantConfigurationServiceImpl implements TenantConfigurationService { @@ -77,6 +81,7 @@ public class TenantConfigurationServiceImpl implements TenantConfigurationServic private final QueryFactory queryFactory; private final EventBroker eventBroker; private final TenantScope tenantScope; + private final Map> globalPoliciesMap; @Autowired public TenantConfigurationServiceImpl( TenantEntityManager entityManager, @@ -85,7 +90,7 @@ public class TenantConfigurationServiceImpl implements TenantConfigurationServic BuilderFactory builderFactory, ConventionService conventionService, ErrorThesaurusProperties errors, - MessageSource messageSource, JsonHandlingService jsonHandlingService, QueryFactory queryFactory, EventBroker eventBroker, TenantScope tenantScope) { + MessageSource messageSource, JsonHandlingService jsonHandlingService, QueryFactory queryFactory, EventBroker eventBroker, TenantScope tenantScope, Map> globalPoliciesMap) { this.entityManager = entityManager; this.authorizationService = authorizationService; this.deleterFactory = deleterFactory; @@ -97,6 +102,7 @@ public class TenantConfigurationServiceImpl implements TenantConfigurationServic this.queryFactory = queryFactory; this.eventBroker = eventBroker; 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 { @@ -214,4 +220,19 @@ public class TenantConfigurationServiceImpl implements TenantConfigurationServic return query.first(); } + @Override + public NotifierListTenantConfigurationEntity collectTenantAvailableNotifierList(Set notificationTypes) { + this.authorizationService.authorizeForce(Permission.BrowseTenantConfiguration); + Map> 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); + } + } diff --git a/notification-service/notification/src/main/java/gr/cite/notification/service/userNotificationPreference/UserNotificationPreferenceServiceImpl.java b/notification-service/notification/src/main/java/gr/cite/notification/service/userNotificationPreference/UserNotificationPreferenceServiceImpl.java index 3b14107a5..1fc9e67d1 100644 --- a/notification-service/notification/src/main/java/gr/cite/notification/service/userNotificationPreference/UserNotificationPreferenceServiceImpl.java +++ b/notification-service/notification/src/main/java/gr/cite/notification/service/userNotificationPreference/UserNotificationPreferenceServiceImpl.java @@ -5,6 +5,7 @@ import gr.cite.notification.authorization.OwnedResource; import gr.cite.notification.authorization.Permission; import gr.cite.notification.common.enums.IsActive; 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.config.notification.NotificationConfig; import gr.cite.notification.data.TenantEntityManager; @@ -46,6 +47,7 @@ public class UserNotificationPreferenceServiceImpl implements UserNotificationPr private final Map> globalPoliciesMap; private final ErrorThesaurusProperties errors; private final TenantEntityManager entityManager; + private final TenantScope tenantScope; @Autowired public UserNotificationPreferenceServiceImpl(QueryFactory queryFactory, @@ -54,7 +56,7 @@ public class UserNotificationPreferenceServiceImpl implements UserNotificationPr TenantConfigurationService tenantConfigurationService, @Qualifier(NotificationConfig.BeanQualifier.GLOBAL_POLICIES_MAP) Map> globalPoliciesMap, - ErrorThesaurusProperties errors, TenantEntityManager entityManager) { + ErrorThesaurusProperties errors, TenantEntityManager entityManager, TenantScope tenantScope) { this.queryFactory = queryFactory; this.builderFactory = builderFactory; this.authService = authService; @@ -62,6 +64,7 @@ public class UserNotificationPreferenceServiceImpl implements UserNotificationPr this.globalPoliciesMap = globalPoliciesMap; this.errors = errors; this.entityManager = entityManager; + this.tenantScope = tenantScope; } @Override @@ -129,7 +132,14 @@ public class UserNotificationPreferenceServiceImpl implements UserNotificationPr .build(new BaseFieldSet(UserNotificationPreference._userId, UserNotificationPreference._type, UserNotificationPreference._channel, UserNotificationPreference._ordinal), this.queryFactory .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> mergeNotifierPolicies(Map> overrides, Map> bases) @@ -177,7 +187,13 @@ public class UserNotificationPreferenceServiceImpl implements UserNotificationPr List updatedPreferences = new ArrayList<>(); 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; if (preference != null) {