From 428c80a9b3000af6afc9a96dd232dce3117aa702 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 12 Oct 2025 17:47:39 +0200 Subject: [PATCH] Improve currency validation --- .../ghostfolio/ghostfolio.controller.ts | 10 +++++++++- .../ghostfolio/ghostfolio.service.ts | 20 +++++++++---------- .../data-provider/data-provider.service.ts | 14 ++++++++++--- .../errors/asset-profile-invalid.error.ts | 7 +++++++ .../financial-modeling-prep.service.ts | 4 ++++ 5 files changed, 41 insertions(+), 14 deletions(-) create mode 100644 apps/api/src/services/data-provider/errors/asset-profile-invalid.error.ts diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts index 7cb2520bb..04165e9a1 100644 --- a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts +++ b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts @@ -1,5 +1,6 @@ import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; +import { AssetProfileInvalidError } from '@ghostfolio/api/services/data-provider/errors/asset-profile-invalid.error'; import { parseDate } from '@ghostfolio/common/helper'; import { DataProviderGhostfolioAssetProfileResponse, @@ -66,7 +67,14 @@ export class GhostfolioController { }); return assetProfile; - } catch { + } catch (error) { + if (error instanceof AssetProfileInvalidError) { + throw new HttpException( + getReasonPhrase(StatusCodes.NOT_FOUND), + StatusCodes.NOT_FOUND + ); + } + throw new HttpException( getReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR), StatusCodes.INTERNAL_SERVER_ERROR diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts index cc92efa02..ac5881c4d 100644 --- a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts +++ b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts @@ -40,10 +40,7 @@ export class GhostfolioService { private readonly propertyService: PropertyService ) {} - public async getAssetProfile({ - requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), - symbol - }: GetAssetProfileParams) { + public async getAssetProfile({ symbol }: GetAssetProfileParams) { let result: DataProviderGhostfolioAssetProfileResponse = {}; try { @@ -51,12 +48,15 @@ export class GhostfolioService { for (const dataProviderService of this.getDataProviderServices()) { promises.push( - dataProviderService - .getAssetProfile({ - requestTimeout, - symbol - }) - .then(async (assetProfile) => { + this.dataProviderService + .getAssetProfiles([ + { + symbol, + dataSource: dataProviderService.getName() + } + ]) + .then(async (assetProfiles) => { + const assetProfile = assetProfiles[symbol]; const dataSourceOrigin = DataSource.GHOSTFOLIO; if (assetProfile) { diff --git a/apps/api/src/services/data-provider/data-provider.service.ts b/apps/api/src/services/data-provider/data-provider.service.ts index 8754c3537..7e78c503e 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -35,6 +35,8 @@ import { eachDayOfInterval, format, isValid } from 'date-fns'; import { groupBy, isEmpty, isNumber, uniqWith } from 'lodash'; import ms from 'ms'; +import { AssetProfileInvalidError } from './errors/asset-profile-invalid.error'; + @Injectable() export class DataProviderService implements OnModuleInit { private dataProviderMapping: { [dataProviderName: string]: string }; @@ -106,9 +108,9 @@ export class DataProviderService implements OnModuleInit { ); promises.push( - promise.then((symbolProfile) => { - if (symbolProfile) { - response[symbol] = symbolProfile; + promise.then((assetProfile) => { + if (isCurrency(assetProfile?.currency)) { + response[symbol] = assetProfile; } }) ); @@ -117,6 +119,12 @@ export class DataProviderService implements OnModuleInit { try { await Promise.all(promises); + + if (isEmpty(response)) { + throw new AssetProfileInvalidError( + 'No valid asset profiles have been found' + ); + } } catch (error) { Logger.error(error, 'DataProviderService'); diff --git a/apps/api/src/services/data-provider/errors/asset-profile-invalid.error.ts b/apps/api/src/services/data-provider/errors/asset-profile-invalid.error.ts new file mode 100644 index 000000000..bfbea6040 --- /dev/null +++ b/apps/api/src/services/data-provider/errors/asset-profile-invalid.error.ts @@ -0,0 +1,7 @@ +export class AssetProfileInvalidError extends Error { + public constructor(message: string) { + super(message); + + this.name = 'AssetProfileInvalidError'; + } +} diff --git a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts index 3b0d8ba72..5f08a74b5 100644 --- a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts +++ b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts @@ -102,6 +102,10 @@ export class FinancialModelingPrepService implements DataProviderInterface { } ).then((res) => res.json()); + if (!assetProfile) { + throw new Error(`${symbol} not found`); + } + const { assetClass, assetSubClass } = this.parseAssetClass(assetProfile);