From e70277c66d1f46ea1aeb4ce0732c90f6f87c2836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20G=C3=BCnther?= Date: Sat, 6 Sep 2025 10:24:29 +0200 Subject: [PATCH] Feature/enable automatic data gathering for custom currencies added via currency management in admin control panel (#5434) * Enable automatic data gathering for custom currencies * Update changelog --- CHANGELOG.md | 4 +++ apps/api/src/app/admin/admin.service.ts | 25 ++++++++++++----- .../create-asset-profile-dialog.component.ts | 27 +++++++++++++++---- .../lib/interfaces/admin-data.interface.ts | 5 +++- 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20df270ef..f4d522abc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Enabled automatic data gathering for custom currencies added via the currency management in the admin control panel + ### Changed - Refactored the create or update access dialog component to standalone diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index d07e74013..2918d9d7d 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -136,11 +136,13 @@ export class AdminService { public async get(): Promise { const dataSources = Object.values(DataSource); - const [settings, transactionCount, userCount] = await Promise.all([ - this.propertyService.get(), - this.prismaService.order.count(), - this.countUsersWithAnalytics() - ]); + const [enabledDataSources, settings, transactionCount, userCount] = + await Promise.all([ + this.dataProviderService.getDataSources(), + this.propertyService.get(), + this.prismaService.order.count(), + this.countUsersWithAnalytics() + ]); const dataProviders = ( await Promise.all( @@ -152,14 +154,23 @@ export class AdminService { } }); - if (assetProfileCount > 0 || dataSource === 'GHOSTFOLIO') { + const isEnabled = enabledDataSources.includes(dataSource); + + if ( + assetProfileCount > 0 || + dataSource === 'GHOSTFOLIO' || + isEnabled + ) { const dataProviderInfo = this.dataProviderService .getDataProvider(dataSource) .getDataProviderInfo(); return { ...dataProviderInfo, - assetProfileCount + assetProfileCount, + useForExchangeRates: + dataSource === + this.dataProviderService.getDataSourceForExchangeRates() }; } 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 5be0fd478..94433e56c 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 @@ -1,6 +1,7 @@ import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { + DEFAULT_CURRENCY, ghostfolioPrefix, PROPERTY_CURRENCIES } from '@ghostfolio/common/config'; @@ -29,8 +30,9 @@ import { MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatRadioModule } from '@angular/material/radio'; +import { DataSource } from '@prisma/client'; import { isISO4217CurrencyCode } from 'class-validator'; -import { Subject, takeUntil } from 'rxjs'; +import { Subject, switchMap, takeUntil } from 'rxjs'; import { CreateAssetProfileDialogMode } from './interfaces/interfaces'; @@ -56,6 +58,7 @@ export class GfCreateAssetProfileDialogComponent implements OnInit, OnDestroy { public mode: CreateAssetProfileDialogMode; private customCurrencies: string[]; + private dataSourceForExchangeRates: DataSource; private unsubscribeSubject = new Subject(); public constructor( @@ -67,7 +70,7 @@ export class GfCreateAssetProfileDialogComponent implements OnInit, OnDestroy { ) {} public ngOnInit() { - this.initializeCustomCurrencies(); + this.initialize(); this.createAssetProfileForm = this.formBuilder.group( { @@ -115,7 +118,15 @@ export class GfCreateAssetProfileDialogComponent implements OnInit, OnDestroy { .putAdminSetting(PROPERTY_CURRENCIES, { value: JSON.stringify(currencies) }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe( + switchMap(() => { + return this.adminService.gatherSymbol({ + dataSource: this.dataSourceForExchangeRates, + symbol: `${DEFAULT_CURRENCY}${currency}` + }); + }), + takeUntil(this.unsubscribeSubject) + ) .subscribe(() => { this.dialogRef.close(); }); @@ -170,13 +181,19 @@ export class GfCreateAssetProfileDialogComponent implements OnInit, OnDestroy { return { atLeastOneValid: true }; } - private initializeCustomCurrencies() { + private initialize() { this.adminService .fetchAdminData() .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ settings }) => { + .subscribe(({ dataProviders, settings }) => { this.customCurrencies = settings[PROPERTY_CURRENCIES] as string[]; + const { dataSource } = dataProviders.find(({ useForExchangeRates }) => { + return useForExchangeRates; + }); + + this.dataSourceForExchangeRates = dataSource; + this.changeDetectorRef.markForCheck(); }); } diff --git a/libs/common/src/lib/interfaces/admin-data.interface.ts b/libs/common/src/lib/interfaces/admin-data.interface.ts index cef983902..23821a86b 100644 --- a/libs/common/src/lib/interfaces/admin-data.interface.ts +++ b/libs/common/src/lib/interfaces/admin-data.interface.ts @@ -1,7 +1,10 @@ import { DataProviderInfo } from './data-provider-info.interface'; export interface AdminData { - dataProviders: (DataProviderInfo & { assetProfileCount: number })[]; + dataProviders: (DataProviderInfo & { + assetProfileCount: number; + useForExchangeRates: boolean; + })[]; settings: { [key: string]: boolean | object | string | string[] }; transactionCount: number; userCount: number;