Feature/improve validation of currency management in admin control panel (#4334)

* Improve validation of currency management

* Update changelog
pull/4342/head
Thomas Kaul 4 days ago committed by GitHub
parent da79cf406f
commit b2698fccbd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Reloaded the available tags after creating a custom tag in the holding detail dialog (experimental) - 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/client` components to control flow
- Migrated the `@ghostfolio/ui` components to control flow - Migrated the `@ghostfolio/ui` components to control flow

@ -1,6 +1,7 @@
import { AdminService } from '@ghostfolio/api/app/admin/admin.service'; import { AdminService } from '@ghostfolio/api/app/admin/admin.service';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.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 { MarketDataDetailsResponse } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { RequestWithUser } from '@ghostfolio/common/types'; import { RequestWithUser } from '@ghostfolio/common/types';
@ -42,7 +43,7 @@ export class MarketDataController {
{ dataSource, symbol } { dataSource, symbol }
]); ]);
if (!assetProfile) { if (!assetProfile && !isCurrency(getCurrencyFromSymbol(symbol))) {
throw new HttpException( throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND), getReasonPhrase(StatusCodes.NOT_FOUND),
StatusCodes.NOT_FOUND StatusCodes.NOT_FOUND
@ -55,7 +56,7 @@ export class MarketDataController {
); );
const canReadOwnAssetProfile = const canReadOwnAssetProfile =
assetProfile.userId === this.request.user.id && assetProfile?.userId === this.request.user.id &&
hasPermission( hasPermission(
this.request.user.permissions, this.request.user.permissions,
permissions.readMarketDataOfOwnAssetProfile permissions.readMarketDataOfOwnAssetProfile

@ -15,9 +15,11 @@ import {
FormControl, FormControl,
FormGroup, FormGroup,
ValidationErrors, ValidationErrors,
ValidatorFn,
Validators Validators
} from '@angular/forms'; } from '@angular/forms';
import { MatDialogRef } from '@angular/material/dialog'; import { MatDialogRef } from '@angular/material/dialog';
import { isISO4217CurrencyCode } from 'class-validator';
import { uniq } from 'lodash'; import { uniq } from 'lodash';
import { Subject, takeUntil } from 'rxjs'; import { Subject, takeUntil } from 'rxjs';
@ -52,9 +54,7 @@ export class CreateAssetProfileDialog implements OnInit, OnDestroy {
this.createAssetProfileForm = this.formBuilder.group( this.createAssetProfileForm = this.formBuilder.group(
{ {
addCurrency: new FormControl(null, [ addCurrency: new FormControl(null, [
Validators.maxLength(3), this.iso4217CurrencyCodeValidator()
Validators.minLength(3),
Validators.required
]), ]),
addSymbol: new FormControl(null, [Validators.required]), addSymbol: new FormControl(null, [Validators.required]),
searchSymbol: 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 symbol: this.createAssetProfileForm.get('searchSymbol').value.symbol
}); });
} else if (this.mode === 'currency') { } else if (this.mode === 'currency') {
const currency = this.createAssetProfileForm const currency = (
.get('addCurrency') this.createAssetProfileForm.get('addCurrency').value as string
.value.toUpperCase(); ).toUpperCase();
const currencies = uniq([...this.customCurrencies, currency]); const currencies = uniq([...this.customCurrencies, currency]).sort();
this.dataService this.dataService
.putAdminSetting(PROPERTY_CURRENCIES, { .putAdminSetting(PROPERTY_CURRENCIES, {
@ -109,10 +109,7 @@ export class CreateAssetProfileDialog implements OnInit, OnDestroy {
const addCurrencyFormControl = const addCurrencyFormControl =
this.createAssetProfileForm.get('addCurrency'); this.createAssetProfileForm.get('addCurrency');
if ( if (addCurrencyFormControl.hasError('invalidCurrency')) {
addCurrencyFormControl.hasError('maxlength') ||
addCurrencyFormControl.hasError('minlength')
) {
return true; return true;
} }
@ -161,4 +158,14 @@ export class CreateAssetProfileDialog implements OnInit, OnDestroy {
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
} }
private iso4217CurrencyCodeValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
if (!isISO4217CurrencyCode(control.value?.toUpperCase())) {
return { invalidCurrency: true };
}
return null;
};
}
} }

@ -28,7 +28,6 @@ import {
formatDistanceToNowStrict, formatDistanceToNowStrict,
parseISO parseISO
} from 'date-fns'; } from 'date-fns';
import { uniq } from 'lodash';
import { StringValue } from 'ms'; import { StringValue } from 'ms';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
@ -122,24 +121,6 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
this.putAdminSetting({ key: PROPERTY_COUPONS, value: coupons }); 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) { public onChangeCouponDuration(aCouponDuration: StringValue) {
this.couponDuration = aCouponDuration; this.couponDuration = aCouponDuration;
} }

@ -95,16 +95,6 @@
</tr> </tr>
} }
</table> </table>
<div class="mt-2">
<button
color="primary"
mat-flat-button
(click)="onAddCurrency()"
>
<ion-icon class="mr-1" name="add-outline" />
<span i18n>Add Currency</span>
</button>
</div>
</div> </div>
</div> </div>
<div class="d-flex my-3"> <div class="d-flex my-3">

@ -56,6 +56,11 @@
<li>Click on the <i>+</i> button</li> <li>Click on the <i>+</i> button</li>
<li>Switch to <i>Add Currency</i></li> <li>Switch to <i>Add Currency</i></li>
<li>Insert e.g. <code>EUR</code> for Euro</li> <li>Insert e.g. <code>EUR</code> for Euro</li>
<li>Select <i>Filter by Currencies</i></li>
<li>Find the entry <i>USDEUR</i></li>
<li>
Click the menu item <i>Gather Historical Data</i> in the dialog
</li>
</ol> </ol>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>

Loading…
Cancel
Save