From 70cd1a89b513bb58441d1b9f9c23a25cdf0b4816 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Mon, 10 Mar 2025 20:45:41 +0100
Subject: [PATCH] Feature/extend Ghostfolio data provider (#4420)

* Extend Ghostfolio data provider
---
 .../ghostfolio/ghostfolio.controller.ts       | 36 ++++++++++
 .../ghostfolio/ghostfolio.service.ts          | 43 +++++++++++-
 .../alpha-vantage/alpha-vantage.service.ts    |  5 +-
 .../coingecko/coingecko.service.ts            |  5 +-
 .../eod-historical-data.service.ts            |  5 +-
 .../financial-modeling-prep.service.ts        | 38 ++++-------
 .../ghostfolio/ghostfolio.service.ts          | 68 +++++++++++++------
 .../google-sheets/google-sheets.service.ts    |  5 +-
 .../interfaces/data-provider.interface.ts     | 10 ++-
 .../data-provider/manual/manual.service.ts    |  5 +-
 .../rapid-api/rapid-api.service.ts            |  5 +-
 .../yahoo-finance/yahoo-finance.service.ts    |  5 +-
 libs/common/src/lib/interfaces/index.ts       |  2 +
 ...tfolio-asset-profile-response.interface.ts |  4 ++
 14 files changed, 165 insertions(+), 71 deletions(-)
 create mode 100644 libs/common/src/lib/interfaces/responses/data-provider-ghostfolio-asset-profile-response.interface.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 f3386f8a7..83c7317f0 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
@@ -2,6 +2,7 @@ import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorat
 import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
 import { parseDate } from '@ghostfolio/common/helper';
 import {
+  DataProviderGhostfolioAssetProfileResponse,
   DataProviderGhostfolioStatusResponse,
   DividendsResponse,
   HistoricalResponse,
@@ -37,6 +38,41 @@ export class GhostfolioController {
     @Inject(REQUEST) private readonly request: RequestWithUser
   ) {}
 
+  @Get('asset-profile/:symbol')
+  @HasPermission(permissions.enableDataProviderGhostfolio)
+  @UseGuards(AuthGuard('api-key'), HasPermissionGuard)
+  public async getAssetProfile(
+    @Param('symbol') symbol: string
+  ): Promise<DataProviderGhostfolioAssetProfileResponse> {
+    const maxDailyRequests = await this.ghostfolioService.getMaxDailyRequests();
+
+    if (
+      this.request.user.dataProviderGhostfolioDailyRequests > maxDailyRequests
+    ) {
+      throw new HttpException(
+        getReasonPhrase(StatusCodes.TOO_MANY_REQUESTS),
+        StatusCodes.TOO_MANY_REQUESTS
+      );
+    }
+
+    try {
+      const assetProfile = await this.ghostfolioService.getAssetProfile({
+        symbol
+      });
+
+      await this.ghostfolioService.incrementDailyRequests({
+        userId: this.request.user.id
+      });
+
+      return assetProfile;
+    } catch {
+      throw new HttpException(
+        getReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR),
+        StatusCodes.INTERNAL_SERVER_ERROR
+      );
+    }
+  }
+
   /**
    * @deprecated
    */
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 78685a61b..7281697bd 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
@@ -1,6 +1,7 @@
 import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
 import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
 import {
+  GetAssetProfileParams,
   GetDividendsParams,
   GetHistoricalParams,
   GetQuotesParams,
@@ -15,6 +16,7 @@ import {
 } from '@ghostfolio/common/config';
 import { PROPERTY_DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER_MAX_REQUESTS } from '@ghostfolio/common/config';
 import {
+  DataProviderGhostfolioAssetProfileResponse,
   DataProviderInfo,
   DividendsResponse,
   HistoricalResponse,
@@ -25,7 +27,7 @@ import {
 import { UserWithSettings } from '@ghostfolio/common/types';
 
 import { Injectable, Logger } from '@nestjs/common';
-import { DataSource } from '@prisma/client';
+import { DataSource, SymbolProfile } from '@prisma/client';
 import { Big } from 'big.js';
 
 @Injectable()
@@ -37,6 +39,44 @@ export class GhostfolioService {
     private readonly propertyService: PropertyService
   ) {}
 
+  public async getAssetProfile({
+    requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'),
+    symbol
+  }: GetAssetProfileParams) {
+    let result: DataProviderGhostfolioAssetProfileResponse = {};
+
+    try {
+      const promises: Promise<Partial<SymbolProfile>>[] = [];
+
+      for (const dataProviderService of this.getDataProviderServices()) {
+        promises.push(
+          dataProviderService
+            .getAssetProfile({
+              requestTimeout,
+              symbol
+            })
+            .then((assetProfile) => {
+              result = {
+                ...result,
+                ...assetProfile,
+                dataSource: DataSource.GHOSTFOLIO
+              };
+
+              return assetProfile;
+            })
+        );
+      }
+
+      await Promise.all(promises);
+
+      return result;
+    } catch (error) {
+      Logger.error(error, 'GhostfolioService');
+
+      throw error;
+    }
+  }
+
   public async getDividends({
     from,
     granularity,
@@ -277,6 +317,7 @@ export class GhostfolioService {
         });
 
       results.items = filteredItems;
+
       return results;
     } catch (error) {
       Logger.error(error, 'GhostfolioService');
diff --git a/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts b/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts
index 5c9eee127..606e6b7fd 100644
--- a/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts
+++ b/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts
@@ -1,6 +1,7 @@
 import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
 import {
   DataProviderInterface,
+  GetAssetProfileParams,
   GetDividendsParams,
   GetHistoricalParams,
   GetQuotesParams,
@@ -41,9 +42,7 @@ export class AlphaVantageService implements DataProviderInterface {
 
   public async getAssetProfile({
     symbol
-  }: {
-    symbol: string;
-  }): Promise<Partial<SymbolProfile>> {
+  }: GetAssetProfileParams): Promise<Partial<SymbolProfile>> {
     return {
       symbol,
       dataSource: this.getName()
diff --git a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts
index fb1fa9b63..d53355b9c 100644
--- a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts
+++ b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts
@@ -1,6 +1,7 @@
 import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
 import {
   DataProviderInterface,
+  GetAssetProfileParams,
   GetDividendsParams,
   GetHistoricalParams,
   GetQuotesParams,
@@ -56,9 +57,7 @@ export class CoinGeckoService implements DataProviderInterface {
 
   public async getAssetProfile({
     symbol
-  }: {
-    symbol: string;
-  }): Promise<Partial<SymbolProfile>> {
+  }: GetAssetProfileParams): Promise<Partial<SymbolProfile>> {
     const response: Partial<SymbolProfile> = {
       symbol,
       assetClass: AssetClass.LIQUIDITY,
diff --git a/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts b/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts
index e427a830a..376b8f159 100644
--- a/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts
+++ b/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts
@@ -1,6 +1,7 @@
 import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
 import {
   DataProviderInterface,
+  GetAssetProfileParams,
   GetDividendsParams,
   GetHistoricalParams,
   GetQuotesParams,
@@ -51,9 +52,7 @@ export class EodHistoricalDataService implements DataProviderInterface {
 
   public async getAssetProfile({
     symbol
-  }: {
-    symbol: string;
-  }): Promise<Partial<SymbolProfile>> {
+  }: GetAssetProfileParams): Promise<Partial<SymbolProfile>> {
     const [searchResult] = await this.getSearchResult(symbol);
 
     return {
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 6a3d0d41f..5216ed214 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
@@ -2,6 +2,7 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/con
 import { CryptocurrencyService } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.service';
 import {
   DataProviderInterface,
+  GetAssetProfileParams,
   GetDividendsParams,
   GetHistoricalParams,
   GetQuotesParams,
@@ -56,10 +57,9 @@ export class FinancialModelingPrepService implements DataProviderInterface {
   }
 
   public async getAssetProfile({
+    requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'),
     symbol
-  }: {
-    symbol: string;
-  }): Promise<Partial<SymbolProfile>> {
+  }: GetAssetProfileParams): Promise<Partial<SymbolProfile>> {
     const response: Partial<SymbolProfile> = {
       symbol,
       dataSource: this.getName()
@@ -70,9 +70,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
         const [quote] = await fetch(
           `${this.URL}/quote/${symbol}?apikey=${this.apiKey}`,
           {
-            signal: AbortSignal.timeout(
-              this.configurationService.get('REQUEST_TIMEOUT')
-            )
+            signal: AbortSignal.timeout(requestTimeout)
           }
         ).then((res) => res.json());
 
@@ -84,9 +82,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
         const [assetProfile] = await fetch(
           `${this.URL}/profile/${symbol}?apikey=${this.apiKey}`,
           {
-            signal: AbortSignal.timeout(
-              this.configurationService.get('REQUEST_TIMEOUT')
-            )
+            signal: AbortSignal.timeout(requestTimeout)
           }
         ).then((res) => res.json());
 
@@ -100,9 +96,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
           const etfCountryWeightings = await fetch(
             `${this.URL}/etf-country-weightings/${symbol}?apikey=${this.apiKey}`,
             {
-              signal: AbortSignal.timeout(
-                this.configurationService.get('REQUEST_TIMEOUT')
-              )
+              signal: AbortSignal.timeout(requestTimeout)
             }
           ).then((res) => res.json());
 
@@ -127,9 +121,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
           const [etfInformation] = await fetch(
             `${this.getUrl({ version: 4 })}/etf-info?symbol=${symbol}&apikey=${this.apiKey}`,
             {
-              signal: AbortSignal.timeout(
-                this.configurationService.get('REQUEST_TIMEOUT')
-              )
+              signal: AbortSignal.timeout(requestTimeout)
             }
           ).then((res) => res.json());
 
@@ -140,9 +132,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
           const [portfolioDate] = await fetch(
             `${this.getUrl({ version: 4 })}/etf-holdings/portfolio-date?symbol=${symbol}&apikey=${this.apiKey}`,
             {
-              signal: AbortSignal.timeout(
-                this.configurationService.get('REQUEST_TIMEOUT')
-              )
+              signal: AbortSignal.timeout(requestTimeout)
             }
           ).then((res) => res.json());
 
@@ -150,9 +140,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
             const etfHoldings = await fetch(
               `${this.getUrl({ version: 4 })}/etf-holdings?date=${portfolioDate.date}&symbol=${symbol}&apikey=${this.apiKey}`,
               {
-                signal: AbortSignal.timeout(
-                  this.configurationService.get('REQUEST_TIMEOUT')
-                )
+                signal: AbortSignal.timeout(requestTimeout)
               }
             ).then((res) => res.json());
 
@@ -170,9 +158,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
           const etfSectorWeightings = await fetch(
             `${this.URL}/etf-sector-weightings/${symbol}?apikey=${this.apiKey}`,
             {
-              signal: AbortSignal.timeout(
-                this.configurationService.get('REQUEST_TIMEOUT')
-              )
+              signal: AbortSignal.timeout(requestTimeout)
             }
           ).then((res) => res.json());
 
@@ -211,7 +197,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
 
       if (error?.name === 'AbortError') {
         message = `RequestError: The operation to get the asset profile for ${symbol} was aborted because the request to the data provider took more than ${(
-          this.configurationService.get('REQUEST_TIMEOUT') / 1000
+          requestTimeout / 1000
         ).toFixed(3)} seconds`;
       }
 
@@ -376,7 +362,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
 
       if (error?.name === 'AbortError') {
         message = `RequestError: The operation to get the quotes was aborted because the request to the data provider took more than ${(
-          this.configurationService.get('REQUEST_TIMEOUT') / 1000
+          requestTimeout / 1000
         ).toFixed(3)} seconds`;
       }
 
diff --git a/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts b/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts
index a674d479a..097464e2f 100644
--- a/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts
+++ b/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts
@@ -2,6 +2,7 @@ import { environment } from '@ghostfolio/api/environments/environment';
 import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
 import {
   DataProviderInterface,
+  GetAssetProfileParams,
   GetDividendsParams,
   GetHistoricalParams,
   GetQuotesParams,
@@ -18,6 +19,7 @@ import {
 } from '@ghostfolio/common/config';
 import { DATE_FORMAT } from '@ghostfolio/common/helper';
 import {
+  DataProviderGhostfolioAssetProfileResponse,
   DataProviderInfo,
   DividendsResponse,
   HistoricalResponse,
@@ -46,21 +48,46 @@ export class GhostfolioService implements DataProviderInterface {
   }
 
   public async getAssetProfile({
+    requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'),
     symbol
-  }: {
-    symbol: string;
-  }): Promise<Partial<SymbolProfile>> {
-    const { items } = await this.search({ query: symbol });
-    const searchResult = items?.[0];
+  }: GetAssetProfileParams): Promise<Partial<SymbolProfile>> {
+    let response: DataProviderGhostfolioAssetProfileResponse = {};
 
-    return {
-      symbol,
-      assetClass: searchResult?.assetClass,
-      assetSubClass: searchResult?.assetSubClass,
-      currency: searchResult?.currency,
-      dataSource: this.getName(),
-      name: searchResult?.name
-    };
+    try {
+      const assetProfile = (await fetch(
+        `${this.URL}/v1/data-providers/ghostfolio/asset-profile/${symbol}`,
+        {
+          headers: await this.getRequestHeaders(),
+          signal: AbortSignal.timeout(requestTimeout)
+        }
+      ).then((res) =>
+        res.json()
+      )) as DataProviderGhostfolioAssetProfileResponse;
+
+      response = assetProfile;
+    } catch (error) {
+      let message = error;
+
+      if (error.name === 'AbortError') {
+        message = `RequestError: The operation to get the quotes was aborted because the request to the data provider took more than ${(
+          requestTimeout / 1000
+        ).toFixed(3)} seconds`;
+      } else if (error.response?.statusCode === StatusCodes.TOO_MANY_REQUESTS) {
+        message = 'RequestError: The daily request limit has been exceeded';
+      } else if (error.response?.statusCode === StatusCodes.UNAUTHORIZED) {
+        if (!error.request?.options?.headers?.authorization?.includes('-')) {
+          message =
+            'RequestError: The provided API key is invalid. Please update it in the Settings section of the Admin Control panel.';
+        } else {
+          message =
+            'RequestError: The provided API key has expired. Please request a new one and update it in the Settings section of the Admin Control panel.';
+        }
+      }
+
+      Logger.error(message, 'GhostfolioService');
+    }
+
+    return response;
   }
 
   public getDataProviderInfo(): DataProviderInfo {
@@ -203,7 +230,7 @@ export class GhostfolioService implements DataProviderInterface {
 
       if (error.name === 'AbortError') {
         message = `RequestError: The operation to get the quotes was aborted because the request to the data provider took more than ${(
-          this.configurationService.get('REQUEST_TIMEOUT') / 1000
+          requestTimeout / 1000
         ).toFixed(3)} seconds`;
       } else if (error.response?.statusCode === StatusCodes.TOO_MANY_REQUESTS) {
         message = 'RequestError: The daily request limit has been exceeded';
@@ -224,10 +251,13 @@ export class GhostfolioService implements DataProviderInterface {
   }
 
   public getTestSymbol() {
-    return 'AAPL.US';
+    return 'AAPL';
   }
 
-  public async search({ query }: GetSearchParams): Promise<LookupResponse> {
+  public async search({
+    requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'),
+    query
+  }: GetSearchParams): Promise<LookupResponse> {
     let searchResult: LookupResponse = { items: [] };
 
     try {
@@ -235,9 +265,7 @@ export class GhostfolioService implements DataProviderInterface {
         `${this.URL}/v2/data-providers/ghostfolio/lookup?query=${query}`,
         {
           headers: await this.getRequestHeaders(),
-          signal: AbortSignal.timeout(
-            this.configurationService.get('REQUEST_TIMEOUT')
-          )
+          signal: AbortSignal.timeout(requestTimeout)
         }
       ).then((res) => res.json())) as LookupResponse;
     } catch (error) {
@@ -245,7 +273,7 @@ export class GhostfolioService implements DataProviderInterface {
 
       if (error.name === 'AbortError') {
         message = `RequestError: The operation to search for ${query} was aborted because the request to the data provider took more than ${(
-          this.configurationService.get('REQUEST_TIMEOUT') / 1000
+          requestTimeout / 1000
         ).toFixed(3)} seconds`;
       } else if (error.response?.statusCode === StatusCodes.TOO_MANY_REQUESTS) {
         message = 'RequestError: The daily request limit has been exceeded';
diff --git a/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts b/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts
index f18d670d1..0c466972d 100644
--- a/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts
+++ b/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts
@@ -1,6 +1,7 @@
 import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
 import {
   DataProviderInterface,
+  GetAssetProfileParams,
   GetDividendsParams,
   GetHistoricalParams,
   GetQuotesParams,
@@ -37,9 +38,7 @@ export class GoogleSheetsService implements DataProviderInterface {
 
   public async getAssetProfile({
     symbol
-  }: {
-    symbol: string;
-  }): Promise<Partial<SymbolProfile>> {
+  }: GetAssetProfileParams): Promise<Partial<SymbolProfile>> {
     return {
       symbol,
       dataSource: this.getName()
diff --git a/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts b/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts
index 5c316aac2..475205a01 100644
--- a/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts
+++ b/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts
@@ -15,9 +15,7 @@ export interface DataProviderInterface {
 
   getAssetProfile({
     symbol
-  }: {
-    symbol: string;
-  }): Promise<Partial<SymbolProfile>>;
+  }: GetAssetProfileParams): Promise<Partial<SymbolProfile>>;
 
   getDataProviderInfo(): DataProviderInfo;
 
@@ -55,6 +53,11 @@ export interface DataProviderInterface {
   search({ includeIndices, query }: GetSearchParams): Promise<LookupResponse>;
 }
 
+export interface GetAssetProfileParams {
+  requestTimeout?: number;
+  symbol: string;
+}
+
 export interface GetDividendsParams {
   from: Date;
   granularity?: Granularity;
@@ -79,5 +82,6 @@ export interface GetQuotesParams {
 export interface GetSearchParams {
   includeIndices?: boolean;
   query: string;
+  requestTimeout?: number;
   userId?: string;
 }
diff --git a/apps/api/src/services/data-provider/manual/manual.service.ts b/apps/api/src/services/data-provider/manual/manual.service.ts
index dfda922c6..331806098 100644
--- a/apps/api/src/services/data-provider/manual/manual.service.ts
+++ b/apps/api/src/services/data-provider/manual/manual.service.ts
@@ -1,6 +1,7 @@
 import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
 import {
   DataProviderInterface,
+  GetAssetProfileParams,
   GetDividendsParams,
   GetHistoricalParams,
   GetQuotesParams,
@@ -43,9 +44,7 @@ export class ManualService implements DataProviderInterface {
 
   public async getAssetProfile({
     symbol
-  }: {
-    symbol: string;
-  }): Promise<Partial<SymbolProfile>> {
+  }: GetAssetProfileParams): Promise<Partial<SymbolProfile>> {
     const assetProfile: Partial<SymbolProfile> = {
       symbol,
       dataSource: this.getName()
diff --git a/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts b/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts
index 4c9bb2717..7762be426 100644
--- a/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts
+++ b/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts
@@ -1,6 +1,7 @@
 import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
 import {
   DataProviderInterface,
+  GetAssetProfileParams,
   GetDividendsParams,
   GetHistoricalParams,
   GetQuotesParams,
@@ -33,9 +34,7 @@ export class RapidApiService implements DataProviderInterface {
 
   public async getAssetProfile({
     symbol
-  }: {
-    symbol: string;
-  }): Promise<Partial<SymbolProfile>> {
+  }: GetAssetProfileParams): Promise<Partial<SymbolProfile>> {
     return {
       symbol,
       dataSource: this.getName()
diff --git a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts
index 27da18ab0..72ae1ff97 100644
--- a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts
+++ b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts
@@ -2,6 +2,7 @@ import { CryptocurrencyService } from '@ghostfolio/api/services/cryptocurrency/c
 import { YahooFinanceDataEnhancerService } from '@ghostfolio/api/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service';
 import {
   DataProviderInterface,
+  GetAssetProfileParams,
   GetDividendsParams,
   GetHistoricalParams,
   GetQuotesParams,
@@ -43,9 +44,7 @@ export class YahooFinanceService implements DataProviderInterface {
 
   public async getAssetProfile({
     symbol
-  }: {
-    symbol: string;
-  }): Promise<Partial<SymbolProfile>> {
+  }: GetAssetProfileParams): Promise<Partial<SymbolProfile>> {
     return this.yahooFinanceDataEnhancerService.getAssetProfile(symbol);
   }
 
diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts
index 7ad4948dc..3dcbbb32a 100644
--- a/libs/common/src/lib/interfaces/index.ts
+++ b/libs/common/src/lib/interfaces/index.ts
@@ -41,6 +41,7 @@ import type { AccountBalancesResponse } from './responses/account-balances-respo
 import type { AiPromptResponse } from './responses/ai-prompt-response.interface';
 import type { ApiKeyResponse } from './responses/api-key-response.interface';
 import type { BenchmarkResponse } from './responses/benchmark-response.interface';
+import type { DataProviderGhostfolioAssetProfileResponse } from './responses/data-provider-ghostfolio-asset-profile-response.interface';
 import type { DataProviderGhostfolioStatusResponse } from './responses/data-provider-ghostfolio-status-response.interface';
 import type { DividendsResponse } from './responses/dividends-response.interface';
 import type { ResponseError } from './responses/errors.interface';
@@ -83,6 +84,7 @@ export {
   BenchmarkProperty,
   BenchmarkResponse,
   Coupon,
+  DataProviderGhostfolioAssetProfileResponse,
   DataProviderGhostfolioStatusResponse,
   DataProviderInfo,
   DividendsResponse,
diff --git a/libs/common/src/lib/interfaces/responses/data-provider-ghostfolio-asset-profile-response.interface.ts b/libs/common/src/lib/interfaces/responses/data-provider-ghostfolio-asset-profile-response.interface.ts
new file mode 100644
index 000000000..7fd0314fb
--- /dev/null
+++ b/libs/common/src/lib/interfaces/responses/data-provider-ghostfolio-asset-profile-response.interface.ts
@@ -0,0 +1,4 @@
+import { SymbolProfile } from '@prisma/client';
+
+export interface DataProviderGhostfolioAssetProfileResponse
+  extends Partial<SymbolProfile> {}