diff --git a/CHANGELOG.md b/CHANGELOG.md index 853d6d4aa..ab4a0db12 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 - Changed the performance calculation to a time-weighted approach +- Exposed the environment variable `REQUEST_TIMEOUT` - Used the `HasPermission` annotation in endpoints - Upgraded `Nx` from version `17.2.5` to `17.2.7` diff --git a/README.md b/README.md index 0a2053e43..09270e529 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,7 @@ We provide official container images hosted on [Docker Hub](https://hub.docker.c | `REDIS_HOST` | | The host where _Redis_ is running | | `REDIS_PASSWORD` | | The password of _Redis_ | | `REDIS_PORT` | | The port where _Redis_ is running | +| `REQUEST_TIMEOUT` | `2000` | The timeout of network requests to data providers in milliseconds | ### Run with Docker Compose diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts index 744e86c9c..a93070cd1 100644 --- a/apps/api/src/app/info/info.service.ts +++ b/apps/api/src/app/info/info.service.ts @@ -8,7 +8,6 @@ import { PropertyService } from '@ghostfolio/api/services/property/property.serv import { TagService } from '@ghostfolio/api/services/tag/tag.service'; import { DEFAULT_CURRENCY, - DEFAULT_REQUEST_TIMEOUT, PROPERTY_BETTER_UPTIME_MONITOR_ID, PROPERTY_COUNTRIES_OF_SUBSCRIBERS, PROPERTY_DEMO_USER_ID, @@ -162,7 +161,7 @@ export class InfoService { setTimeout(() => { abortController.abort(); - }, DEFAULT_REQUEST_TIMEOUT); + }, this.configurationService.get('REQUEST_TIMEOUT')); const { pull_count } = await got( `https://hub.docker.com/v2/repositories/ghostfolio/ghostfolio`, @@ -187,7 +186,7 @@ export class InfoService { setTimeout(() => { abortController.abort(); - }, DEFAULT_REQUEST_TIMEOUT); + }, this.configurationService.get('REQUEST_TIMEOUT')); const { body } = await got('https://github.com/ghostfolio/ghostfolio', { // @ts-ignore @@ -214,7 +213,7 @@ export class InfoService { setTimeout(() => { abortController.abort(); - }, DEFAULT_REQUEST_TIMEOUT); + }, this.configurationService.get('REQUEST_TIMEOUT')); const { stargazers_count } = await got( `https://api.github.com/repos/ghostfolio/ghostfolio`, @@ -342,7 +341,7 @@ export class InfoService { setTimeout(() => { abortController.abort(); - }, DEFAULT_REQUEST_TIMEOUT); + }, this.configurationService.get('REQUEST_TIMEOUT')); const { data } = await got( `https://uptime.betterstack.com/api/v2/monitors/${monitorId}/sla?from=${format( diff --git a/apps/api/src/app/logo/logo.service.ts b/apps/api/src/app/logo/logo.service.ts index 80ae1d6a9..91aaa2cbc 100644 --- a/apps/api/src/app/logo/logo.service.ts +++ b/apps/api/src/app/logo/logo.service.ts @@ -1,5 +1,5 @@ +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; -import { DEFAULT_REQUEST_TIMEOUT } from '@ghostfolio/common/config'; import { UniqueAsset } from '@ghostfolio/common/interfaces'; import { HttpException, Injectable } from '@nestjs/common'; import { DataSource } from '@prisma/client'; @@ -9,6 +9,7 @@ import { StatusCodes, getReasonPhrase } from 'http-status-codes'; @Injectable() export class LogoService { public constructor( + private readonly configurationService: ConfigurationService, private readonly symbolProfileService: SymbolProfileService ) {} @@ -46,7 +47,7 @@ export class LogoService { setTimeout(() => { abortController.abort(); - }, DEFAULT_REQUEST_TIMEOUT); + }, this.configurationService.get('REQUEST_TIMEOUT')); return got( `https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${aUrl}&size=64`, diff --git a/apps/api/src/services/configuration/configuration.service.ts b/apps/api/src/services/configuration/configuration.service.ts index b355d5a20..5b76055c1 100644 --- a/apps/api/src/services/configuration/configuration.service.ts +++ b/apps/api/src/services/configuration/configuration.service.ts @@ -44,6 +44,7 @@ export class ConfigurationService { REDIS_HOST: str({ default: 'localhost' }), REDIS_PASSWORD: str({ default: '' }), REDIS_PORT: port({ default: 6379 }), + REQUEST_TIMEOUT: num({ default: 2000 }), ROOT_URL: str({ default: DEFAULT_ROOT_URL }), STRIPE_PUBLIC_KEY: str({ default: '' }), STRIPE_SECRET_KEY: str({ default: '' }), 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 2b03f519d..b40f35b36 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 @@ -5,7 +5,6 @@ import { IDataProviderHistoricalResponse, IDataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; -import { DEFAULT_REQUEST_TIMEOUT } from '@ghostfolio/common/config'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { Granularity } from '@ghostfolio/common/types'; import { Injectable } from '@nestjs/common'; @@ -107,7 +106,7 @@ export class AlphaVantageService implements DataProviderInterface { } public async getQuotes({ - requestTimeout = DEFAULT_REQUEST_TIMEOUT, + requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), symbols }: { requestTimeout?: number; 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 a9eb44405..d2156ed3b 100644 --- a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts +++ b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts @@ -1,13 +1,11 @@ import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; import { IDataProviderHistoricalResponse, IDataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; -import { - DEFAULT_CURRENCY, - DEFAULT_REQUEST_TIMEOUT -} from '@ghostfolio/common/config'; +import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { DataProviderInfo } from '@ghostfolio/common/interfaces'; import { Granularity } from '@ghostfolio/common/types'; @@ -25,7 +23,9 @@ import got from 'got'; export class CoinGeckoService implements DataProviderInterface { private readonly URL = 'https://api.coingecko.com/api/v3'; - public constructor() {} + public constructor( + private readonly configurationService: ConfigurationService + ) {} public canHandle(symbol: string) { return true; @@ -47,7 +47,7 @@ export class CoinGeckoService implements DataProviderInterface { setTimeout(() => { abortController.abort(); - }, DEFAULT_REQUEST_TIMEOUT); + }, this.configurationService.get('REQUEST_TIMEOUT')); const { name } = await got(`${this.URL}/coins/${aSymbol}`, { // @ts-ignore @@ -59,7 +59,9 @@ export class CoinGeckoService implements DataProviderInterface { let message = error; if (error?.code === 'ABORT_ERR') { - message = `RequestError: The operation was aborted because the request to the data provider took more than ${DEFAULT_REQUEST_TIMEOUT}ms`; + message = `RequestError: The operation was aborted because the request to the data provider took more than ${this.configurationService.get( + 'REQUEST_TIMEOUT' + )}ms`; } Logger.error(message, 'CoinGeckoService'); @@ -95,7 +97,7 @@ export class CoinGeckoService implements DataProviderInterface { setTimeout(() => { abortController.abort(); - }, DEFAULT_REQUEST_TIMEOUT); + }, this.configurationService.get('REQUEST_TIMEOUT')); const { prices } = await got( `${ @@ -141,7 +143,7 @@ export class CoinGeckoService implements DataProviderInterface { } public async getQuotes({ - requestTimeout = DEFAULT_REQUEST_TIMEOUT, + requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), symbols }: { requestTimeout?: number; @@ -183,7 +185,9 @@ export class CoinGeckoService implements DataProviderInterface { let message = error; if (error?.code === 'ABORT_ERR') { - message = `RequestError: The operation was aborted because the request to the data provider took more than ${DEFAULT_REQUEST_TIMEOUT}ms`; + message = `RequestError: The operation was aborted because the request to the data provider took more than ${this.configurationService.get( + 'REQUEST_TIMEOUT' + )}ms`; } Logger.error(message, 'CoinGeckoService'); @@ -210,7 +214,7 @@ export class CoinGeckoService implements DataProviderInterface { setTimeout(() => { abortController.abort(); - }, DEFAULT_REQUEST_TIMEOUT); + }, this.configurationService.get('REQUEST_TIMEOUT')); const { coins } = await got(`${this.URL}/search?query=${query}`, { // @ts-ignore @@ -231,7 +235,9 @@ export class CoinGeckoService implements DataProviderInterface { let message = error; if (error?.code === 'ABORT_ERR') { - message = `RequestError: The operation was aborted because the request to the data provider took more than ${DEFAULT_REQUEST_TIMEOUT}ms`; + message = `RequestError: The operation was aborted because the request to the data provider took more than ${this.configurationService.get( + 'REQUEST_TIMEOUT' + )}ms`; } Logger.error(message, 'CoinGeckoService'); diff --git a/apps/api/src/services/data-provider/data-enhancer/openfigi/openfigi.service.ts b/apps/api/src/services/data-provider/data-enhancer/openfigi/openfigi.service.ts index dda30ba1a..5e4f01c22 100644 --- a/apps/api/src/services/data-provider/data-enhancer/openfigi/openfigi.service.ts +++ b/apps/api/src/services/data-provider/data-enhancer/openfigi/openfigi.service.ts @@ -1,6 +1,5 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataEnhancerInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-enhancer.interface'; -import { DEFAULT_REQUEST_TIMEOUT } from '@ghostfolio/common/config'; import { parseSymbol } from '@ghostfolio/common/helper'; import { Injectable } from '@nestjs/common'; import { SymbolProfile } from '@prisma/client'; @@ -15,7 +14,7 @@ export class OpenFigiDataEnhancerService implements DataEnhancerInterface { ) {} public async enhance({ - requestTimeout = DEFAULT_REQUEST_TIMEOUT, + requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), response, symbol }: { diff --git a/apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts b/apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts index c949a1f22..9a79fa694 100644 --- a/apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts +++ b/apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts @@ -1,5 +1,5 @@ +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataEnhancerInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-enhancer.interface'; -import { DEFAULT_REQUEST_TIMEOUT } from '@ghostfolio/common/config'; import { Country } from '@ghostfolio/common/interfaces/country.interface'; import { Sector } from '@ghostfolio/common/interfaces/sector.interface'; import { Injectable } from '@nestjs/common'; @@ -21,8 +21,12 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface { 'Information Technology': 'Technology' }; + public constructor( + private readonly configurationService: ConfigurationService + ) {} + public async enhance({ - requestTimeout = DEFAULT_REQUEST_TIMEOUT, + requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), response, symbol }: { @@ -55,7 +59,7 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface { setTimeout(() => { abortController.abort(); - }, DEFAULT_REQUEST_TIMEOUT); + }, this.configurationService.get('REQUEST_TIMEOUT')); return got( `${TrackinsightDataEnhancerService.baseUrl}/funds/${symbol.split( @@ -82,7 +86,7 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface { setTimeout(() => { abortController.abort(); - }, DEFAULT_REQUEST_TIMEOUT); + }, this.configurationService.get('REQUEST_TIMEOUT')); const holdings = await got( `${TrackinsightDataEnhancerService.baseUrl}/holdings/${symbol}.json`, @@ -97,7 +101,7 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface { setTimeout(() => { abortController.abort(); - }, DEFAULT_REQUEST_TIMEOUT); + }, this.configurationService.get('REQUEST_TIMEOUT')); return got( `${TrackinsightDataEnhancerService.baseUrl}/holdings/${symbol.split( diff --git a/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.spec.ts b/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.spec.ts index 8a8ab1f08..951a623d0 100644 --- a/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.spec.ts +++ b/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.spec.ts @@ -1,3 +1,4 @@ +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { CryptocurrencyService } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.service'; import { YahooFinanceDataEnhancerService } from './yahoo-finance.service'; @@ -25,13 +26,16 @@ jest.mock( ); describe('YahooFinanceDataEnhancerService', () => { + let configurationService: ConfigurationService; let cryptocurrencyService: CryptocurrencyService; let yahooFinanceDataEnhancerService: YahooFinanceDataEnhancerService; beforeAll(async () => { + configurationService = new ConfigurationService(); cryptocurrencyService = new CryptocurrencyService(); yahooFinanceDataEnhancerService = new YahooFinanceDataEnhancerService( + configurationService, cryptocurrencyService ); }); diff --git a/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts b/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts index 67fc0a9c1..0ef622c42 100644 --- a/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts +++ b/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts @@ -1,10 +1,7 @@ +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { CryptocurrencyService } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.service'; import { DataEnhancerInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-enhancer.interface'; -import { - DEFAULT_CURRENCY, - DEFAULT_REQUEST_TIMEOUT, - UNKNOWN_KEY -} from '@ghostfolio/common/config'; +import { DEFAULT_CURRENCY, UNKNOWN_KEY } from '@ghostfolio/common/config'; import { isCurrency } from '@ghostfolio/common/helper'; import { Injectable, Logger } from '@nestjs/common'; import { @@ -22,6 +19,7 @@ import type { Price } from 'yahoo-finance2/dist/esm/src/modules/quoteSummary-ifa @Injectable() export class YahooFinanceDataEnhancerService implements DataEnhancerInterface { public constructor( + private readonly configurationService: ConfigurationService, private readonly cryptocurrencyService: CryptocurrencyService ) {} @@ -76,7 +74,7 @@ export class YahooFinanceDataEnhancerService implements DataEnhancerInterface { } public async enhance({ - requestTimeout = DEFAULT_REQUEST_TIMEOUT, + requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), response, symbol }: { 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 448d54b25..f9cd7be71 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 @@ -5,10 +5,7 @@ import { IDataProviderHistoricalResponse, IDataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; -import { - DEFAULT_CURRENCY, - DEFAULT_REQUEST_TIMEOUT -} from '@ghostfolio/common/config'; +import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; import { DATE_FORMAT, isCurrency } from '@ghostfolio/common/helper'; import { Granularity } from '@ghostfolio/common/types'; import { Injectable, Logger } from '@nestjs/common'; @@ -82,7 +79,7 @@ export class EodHistoricalDataService implements DataProviderInterface { setTimeout(() => { abortController.abort(); - }, DEFAULT_REQUEST_TIMEOUT); + }, this.configurationService.get('REQUEST_TIMEOUT')); const response = await got( `${this.URL}/eod/${symbol}?api_token=${ @@ -132,7 +129,7 @@ export class EodHistoricalDataService implements DataProviderInterface { } public async getQuotes({ - requestTimeout = DEFAULT_REQUEST_TIMEOUT, + requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), symbols }: { requestTimeout?: number; @@ -232,7 +229,9 @@ export class EodHistoricalDataService implements DataProviderInterface { let message = error; if (error?.code === 'ABORT_ERR') { - message = `RequestError: The operation was aborted because the request to the data provider took more than ${DEFAULT_REQUEST_TIMEOUT}ms`; + message = `RequestError: The operation was aborted because the request to the data provider took more than ${this.configurationService.get( + 'REQUEST_TIMEOUT' + )}ms`; } Logger.error(message, 'EodHistoricalDataService'); @@ -359,7 +358,7 @@ export class EodHistoricalDataService implements DataProviderInterface { setTimeout(() => { abortController.abort(); - }, DEFAULT_REQUEST_TIMEOUT); + }, this.configurationService.get('REQUEST_TIMEOUT')); const response = await got( `${this.URL}/search/${aQuery}?api_token=${this.apiKey}`, @@ -391,7 +390,9 @@ export class EodHistoricalDataService implements DataProviderInterface { let message = error; if (error?.code === 'ABORT_ERR') { - message = `RequestError: The operation was aborted because the request to the data provider took more than ${DEFAULT_REQUEST_TIMEOUT}ms`; + message = `RequestError: The operation was aborted because the request to the data provider took more than ${this.configurationService.get( + 'REQUEST_TIMEOUT' + )}ms`; } Logger.error(message, 'EodHistoricalDataService'); 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 1c3db1520..e16093d3e 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 @@ -5,10 +5,7 @@ import { IDataProviderHistoricalResponse, IDataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; -import { - DEFAULT_CURRENCY, - DEFAULT_REQUEST_TIMEOUT -} from '@ghostfolio/common/config'; +import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper'; import { DataProviderInfo } from '@ghostfolio/common/interfaces'; import { Granularity } from '@ghostfolio/common/types'; @@ -70,7 +67,7 @@ export class FinancialModelingPrepService implements DataProviderInterface { setTimeout(() => { abortController.abort(); - }, DEFAULT_REQUEST_TIMEOUT); + }, this.configurationService.get('REQUEST_TIMEOUT')); const { historical } = await got( `${this.URL}/historical-price-full/${aSymbol}?apikey=${this.apiKey}`, @@ -114,7 +111,7 @@ export class FinancialModelingPrepService implements DataProviderInterface { } public async getQuotes({ - requestTimeout = DEFAULT_REQUEST_TIMEOUT, + requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), symbols }: { requestTimeout?: number; @@ -154,7 +151,9 @@ export class FinancialModelingPrepService implements DataProviderInterface { let message = error; if (error?.code === 'ABORT_ERR') { - message = `RequestError: The operation was aborted because the request to the data provider took more than ${DEFAULT_REQUEST_TIMEOUT}ms`; + message = `RequestError: The operation was aborted because the request to the data provider took more than ${this.configurationService.get( + 'REQUEST_TIMEOUT' + )}ms`; } Logger.error(message, 'FinancialModelingPrepService'); @@ -181,7 +180,7 @@ export class FinancialModelingPrepService implements DataProviderInterface { setTimeout(() => { abortController.abort(); - }, DEFAULT_REQUEST_TIMEOUT); + }, this.configurationService.get('REQUEST_TIMEOUT')); const result = await got( `${this.URL}/search?query=${query}&apikey=${this.apiKey}`, @@ -205,7 +204,9 @@ export class FinancialModelingPrepService implements DataProviderInterface { let message = error; if (error?.code === 'ABORT_ERR') { - message = `RequestError: The operation was aborted because the request to the data provider took more than ${DEFAULT_REQUEST_TIMEOUT}ms`; + message = `RequestError: The operation was aborted because the request to the data provider took more than ${this.configurationService.get( + 'REQUEST_TIMEOUT' + )}ms`; } Logger.error(message, 'FinancialModelingPrepService'); 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 6dc8dc80b..e753db3a3 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 @@ -7,7 +7,6 @@ import { } from '@ghostfolio/api/services/interfaces/interfaces'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; -import { DEFAULT_REQUEST_TIMEOUT } from '@ghostfolio/common/config'; import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper'; import { Granularity } from '@ghostfolio/common/types'; import { Injectable, Logger } from '@nestjs/common'; @@ -101,7 +100,7 @@ export class GoogleSheetsService implements DataProviderInterface { } public async getQuotes({ - requestTimeout = DEFAULT_REQUEST_TIMEOUT, + requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), symbols }: { requestTimeout?: number; 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 1464a526d..9a0ff82d9 100644 --- a/apps/api/src/services/data-provider/manual/manual.service.ts +++ b/apps/api/src/services/data-provider/manual/manual.service.ts @@ -1,4 +1,5 @@ import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; import { IDataProviderHistoricalResponse, @@ -6,7 +7,6 @@ import { } from '@ghostfolio/api/services/interfaces/interfaces'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; -import { DEFAULT_REQUEST_TIMEOUT } from '@ghostfolio/common/config'; import { DATE_FORMAT, extractNumberFromString, @@ -23,6 +23,7 @@ import got from 'got'; @Injectable() export class ManualService implements DataProviderInterface { public constructor( + private readonly configurationService: ConfigurationService, private readonly prismaService: PrismaService, private readonly symbolProfileService: SymbolProfileService ) {} @@ -100,7 +101,7 @@ export class ManualService implements DataProviderInterface { setTimeout(() => { abortController.abort(); - }, DEFAULT_REQUEST_TIMEOUT); + }, this.configurationService.get('REQUEST_TIMEOUT')); const { body } = await got(url, { headers, @@ -134,7 +135,7 @@ export class ManualService implements DataProviderInterface { } public async getQuotes({ - requestTimeout = DEFAULT_REQUEST_TIMEOUT, + requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), symbols }: { requestTimeout?: number; 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 5c2df222c..ba746ebbe 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 @@ -5,10 +5,7 @@ import { IDataProviderHistoricalResponse, IDataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; -import { - DEFAULT_REQUEST_TIMEOUT, - ghostfolioFearAndGreedIndexSymbol -} from '@ghostfolio/common/config'; +import { ghostfolioFearAndGreedIndexSymbol } from '@ghostfolio/common/config'; import { DATE_FORMAT, getYesterday } from '@ghostfolio/common/helper'; import { Granularity } from '@ghostfolio/common/types'; import { Injectable, Logger } from '@nestjs/common'; @@ -88,7 +85,7 @@ export class RapidApiService implements DataProviderInterface { } public async getQuotes({ - requestTimeout = DEFAULT_REQUEST_TIMEOUT, + requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), symbols }: { requestTimeout?: number; @@ -146,7 +143,7 @@ export class RapidApiService implements DataProviderInterface { setTimeout(() => { abortController.abort(); - }, DEFAULT_REQUEST_TIMEOUT); + }, this.configurationService.get('REQUEST_TIMEOUT')); const { fgi } = await got( `https://fear-and-greed-index.p.rapidapi.com/v1/fgi`, @@ -166,7 +163,9 @@ export class RapidApiService implements DataProviderInterface { let message = error; if (error?.code === 'ABORT_ERR') { - message = `RequestError: The operation was aborted because the request to the data provider took more than ${DEFAULT_REQUEST_TIMEOUT}ms`; + message = `RequestError: The operation was aborted because the request to the data provider took more than ${this.configurationService.get( + 'REQUEST_TIMEOUT' + )}ms`; } Logger.error(message, 'RapidApiService'); 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 96bffa7ea..2a972d176 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 @@ -1,4 +1,5 @@ import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { CryptocurrencyService } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.service'; import { YahooFinanceDataEnhancerService } from '@ghostfolio/api/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service'; import { DataProviderInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; @@ -6,10 +7,7 @@ import { IDataProviderHistoricalResponse, IDataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; -import { - DEFAULT_CURRENCY, - DEFAULT_REQUEST_TIMEOUT -} from '@ghostfolio/common/config'; +import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { Granularity } from '@ghostfolio/common/types'; import { Injectable, Logger } from '@nestjs/common'; @@ -22,6 +20,7 @@ import { Quote } from 'yahoo-finance2/dist/esm/src/modules/quote'; @Injectable() export class YahooFinanceService implements DataProviderInterface { public constructor( + private readonly configurationService: ConfigurationService, private readonly cryptocurrencyService: CryptocurrencyService, private readonly yahooFinanceDataEnhancerService: YahooFinanceDataEnhancerService ) {} @@ -160,7 +159,7 @@ export class YahooFinanceService implements DataProviderInterface { } public async getQuotes({ - requestTimeout = DEFAULT_REQUEST_TIMEOUT, + requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), symbols }: { requestTimeout?: number; diff --git a/apps/api/src/services/interfaces/environment.interface.ts b/apps/api/src/services/interfaces/environment.interface.ts index 9b10a3205..a410070a0 100644 --- a/apps/api/src/services/interfaces/environment.interface.ts +++ b/apps/api/src/services/interfaces/environment.interface.ts @@ -32,6 +32,7 @@ export interface Environment extends CleanedEnvAccessors { REDIS_HOST: string; REDIS_PASSWORD: string; REDIS_PORT: number; + REQUEST_TIMEOUT: number; ROOT_URL: string; STRIPE_PUBLIC_KEY: string; STRIPE_SECRET_KEY: string; diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index b8369d60a..18926f0b8 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -39,7 +39,6 @@ export const DEFAULT_CURRENCY = 'USD'; export const DEFAULT_DATE_FORMAT_MONTH_YEAR = 'MMM yyyy'; export const DEFAULT_LANGUAGE_CODE = 'en'; export const DEFAULT_PAGE_SIZE = 50; -export const DEFAULT_REQUEST_TIMEOUT = ms('2 seconds'); export const DEFAULT_ROOT_URL = 'http://localhost:4200'; export const EMERGENCY_FUND_TAG_ID = '4452656d-9fa4-4bd0-ba38-70492e31d180';