From b2698fccbd4ff1f90f9ae554da6c0dd15eaf6005 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 19 Feb 2025 17:25:54 +0100 Subject: [PATCH] Feature/improve validation of currency management in admin control panel (#4334) * Improve validation of currency management * Update changelog --- CHANGELOG.md | 1 + .../market-data/market-data.controller.ts | 5 ++-- .../create-asset-profile-dialog.component.ts | 29 ++++++++++++------- .../admin-overview.component.ts | 19 ------------ .../admin-overview/admin-overview.html | 10 ------- .../faq/self-hosting/self-hosting-page.html | 5 ++++ 6 files changed, 27 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5af20d2f..d6580eb33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Reloaded the available tags after creating a custom tag in the holding detail dialog (experimental) +- Improved the validation of the currency management in the admin control panel - Migrated the `@ghostfolio/client` components to control flow - Migrated the `@ghostfolio/ui` components to control flow diff --git a/apps/api/src/app/endpoints/market-data/market-data.controller.ts b/apps/api/src/app/endpoints/market-data/market-data.controller.ts index b4aef807a..933e70e9d 100644 --- a/apps/api/src/app/endpoints/market-data/market-data.controller.ts +++ b/apps/api/src/app/endpoints/market-data/market-data.controller.ts @@ -1,6 +1,7 @@ import { AdminService } from '@ghostfolio/api/app/admin/admin.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; +import { getCurrencyFromSymbol, isCurrency } from '@ghostfolio/common/helper'; import { MarketDataDetailsResponse } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { RequestWithUser } from '@ghostfolio/common/types'; @@ -42,7 +43,7 @@ export class MarketDataController { { dataSource, symbol } ]); - if (!assetProfile) { + if (!assetProfile && !isCurrency(getCurrencyFromSymbol(symbol))) { throw new HttpException( getReasonPhrase(StatusCodes.NOT_FOUND), StatusCodes.NOT_FOUND @@ -55,7 +56,7 @@ export class MarketDataController { ); const canReadOwnAssetProfile = - assetProfile.userId === this.request.user.id && + assetProfile?.userId === this.request.user.id && hasPermission( this.request.user.permissions, permissions.readMarketDataOfOwnAssetProfile diff --git a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts index fa5e33f10..b0f69fa5c 100644 --- a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts @@ -15,9 +15,11 @@ import { FormControl, FormGroup, ValidationErrors, + ValidatorFn, Validators } from '@angular/forms'; import { MatDialogRef } from '@angular/material/dialog'; +import { isISO4217CurrencyCode } from 'class-validator'; import { uniq } from 'lodash'; import { Subject, takeUntil } from 'rxjs'; @@ -52,9 +54,7 @@ export class CreateAssetProfileDialog implements OnInit, OnDestroy { this.createAssetProfileForm = this.formBuilder.group( { addCurrency: new FormControl(null, [ - Validators.maxLength(3), - Validators.minLength(3), - Validators.required + this.iso4217CurrencyCodeValidator() ]), addSymbol: new FormControl(null, [Validators.required]), searchSymbol: new FormControl(null, [Validators.required]) @@ -83,11 +83,11 @@ export class CreateAssetProfileDialog implements OnInit, OnDestroy { symbol: this.createAssetProfileForm.get('searchSymbol').value.symbol }); } else if (this.mode === 'currency') { - const currency = this.createAssetProfileForm - .get('addCurrency') - .value.toUpperCase(); + const currency = ( + this.createAssetProfileForm.get('addCurrency').value as string + ).toUpperCase(); - const currencies = uniq([...this.customCurrencies, currency]); + const currencies = uniq([...this.customCurrencies, currency]).sort(); this.dataService .putAdminSetting(PROPERTY_CURRENCIES, { @@ -109,10 +109,7 @@ export class CreateAssetProfileDialog implements OnInit, OnDestroy { const addCurrencyFormControl = this.createAssetProfileForm.get('addCurrency'); - if ( - addCurrencyFormControl.hasError('maxlength') || - addCurrencyFormControl.hasError('minlength') - ) { + if (addCurrencyFormControl.hasError('invalidCurrency')) { return true; } @@ -161,4 +158,14 @@ export class CreateAssetProfileDialog implements OnInit, OnDestroy { this.changeDetectorRef.markForCheck(); }); } + + private iso4217CurrencyCodeValidator(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (!isISO4217CurrencyCode(control.value?.toUpperCase())) { + return { invalidCurrency: true }; + } + + return null; + }; + } } diff --git a/apps/client/src/app/components/admin-overview/admin-overview.component.ts b/apps/client/src/app/components/admin-overview/admin-overview.component.ts index d217f871d..f54af4174 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.component.ts +++ b/apps/client/src/app/components/admin-overview/admin-overview.component.ts @@ -28,7 +28,6 @@ import { formatDistanceToNowStrict, parseISO } from 'date-fns'; -import { uniq } from 'lodash'; import { StringValue } from 'ms'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -122,24 +121,6 @@ export class AdminOverviewComponent implements OnDestroy, OnInit { this.putAdminSetting({ key: PROPERTY_COUPONS, value: coupons }); } - public onAddCurrency() { - const currency = prompt($localize`Please add a currency:`); - - if (currency) { - if (currency.length === 3) { - const currencies = uniq([ - ...this.customCurrencies, - currency.toUpperCase() - ]); - this.putAdminSetting({ key: PROPERTY_CURRENCIES, value: currencies }); - } else { - this.notificationService.alert({ - title: $localize`${currency} is an invalid currency!` - }); - } - } - } - public onChangeCouponDuration(aCouponDuration: StringValue) { this.couponDuration = aCouponDuration; } diff --git a/apps/client/src/app/components/admin-overview/admin-overview.html b/apps/client/src/app/components/admin-overview/admin-overview.html index ba8545d16..a85c32d43 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.html +++ b/apps/client/src/app/components/admin-overview/admin-overview.html @@ -95,16 +95,6 @@ } -
EUR
for Euro