diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index bf533d836..74a864db0 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -21,6 +21,7 @@ jobs: with: images: dandevaud/ghostfolio tags: | + type=semver,pattern={{major}} type=semver,pattern={{version}} - name: Set up QEMU diff --git a/CHANGELOG.md b/CHANGELOG.md index d77b922ea..dce838331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added a data migration to set `accountType` to `NULL` in the account database table + +## 2.18.0 - 2023-11-05 + +### Added + +- Added support to import activities by `isin` in the _Yahoo Finance_ service +- Added a new tag with the major version to the docker image on _Docker Hub_ +- Added a blog post: _Hacktoberfest 2023 Debriefing_ + +### Changed + +- Upgraded `angular` from version `16.2.1` to `16.2.12` + +### Fixed + +- Fixed an issue to get quotes in the _CoinGecko_ service +- Loosened the validation in the activities import (expects values greater than or equal to 0 for `fee`, `quantity` and `unitPrice`) +- Handled an issue with a failing database query (`account.findMany()`) related to activities without account + +## 2.17.0 - 2023-11-02 + +### Added + +- Added a button to edit the exchange rates in the admin control panel + ### Changed - Improved the language localization for German (`de`) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index ea581c8cf..5618bb274 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -20,6 +20,12 @@ Use `*ngIf="user?.settings?.isExperimentalFeatures"` in HTML template ## Dependencies +### Angular + +#### Upgrade (minor versions) + +1. Run `npx npm-check-updates --upgrade --target "minor" --filter "/@angular.*/"` + ### Nx #### Upgrade diff --git a/README.md b/README.md index 87e6c7d1b..45f0414bf 100644 --- a/README.md +++ b/README.md @@ -230,18 +230,18 @@ Deprecated: `GET http://localhost:3333/api/v1/auth/anonymous/ { + const label1 = DEFAULT_CURRENCY; + const label2 = currency; + return { - label1: DEFAULT_CURRENCY, - label2: currency, + label1, + label2, + dataSource: + DataSource[ + this.configurationService.get('DATA_SOURCE_EXCHANGE_RATES') + ], + symbol: `${label1}${label2}`, value: this.exchangeRateDataService.toCurrency( 1, DEFAULT_CURRENCY, diff --git a/apps/api/src/app/order/create-order.dto.ts b/apps/api/src/app/order/create-order.dto.ts index 3eafa704f..f25a7ee12 100644 --- a/apps/api/src/app/order/create-order.dto.ts +++ b/apps/api/src/app/order/create-order.dto.ts @@ -13,7 +13,6 @@ import { IsISO8601, IsNumber, IsOptional, - IsPositive, IsString, Min } from 'class-validator'; @@ -54,7 +53,7 @@ export class CreateOrderDto { fee: number; @IsNumber() - @IsPositive() + @Min(0) quantity: number; @IsString() @@ -68,7 +67,7 @@ export class CreateOrderDto { type: Type; @IsNumber() - @IsPositive() + @Min(0) unitPrice: number; @IsBoolean() diff --git a/apps/api/src/app/order/update-order.dto.ts b/apps/api/src/app/order/update-order.dto.ts index 9d968aa86..3123d5665 100644 --- a/apps/api/src/app/order/update-order.dto.ts +++ b/apps/api/src/app/order/update-order.dto.ts @@ -8,12 +8,10 @@ import { import { Transform, TransformFnParams } from 'class-transformer'; import { IsArray, - IsBoolean, IsEnum, IsISO8601, IsNumber, IsOptional, - IsPositive, IsString, Min } from 'class-validator'; @@ -56,7 +54,7 @@ export class UpdateOrderDto { id: string; @IsNumber() - @IsPositive() + @Min(0) quantity: number; @IsString() @@ -70,6 +68,6 @@ export class UpdateOrderDto { type: Type; @IsNumber() - @IsPositive() + @Min(0) unitPrice: number; } diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index f8fd6a7b5..d768268ec 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -1989,9 +1989,13 @@ export class PortfolioService { }); } else { const accountIds = uniq( - orders.map(({ accountId }) => { - return accountId; - }) + orders + .filter(({ accountId }) => { + return accountId; + }) + .map(({ accountId }) => { + return accountId; + }) ); currentAccounts = await this.accountService.accounts({ diff --git a/apps/api/src/assets/sitemap.xml b/apps/api/src/assets/sitemap.xml index 2b34b50ec..5f7049311 100644 --- a/apps/api/src/assets/sitemap.xml +++ b/apps/api/src/assets/sitemap.xml @@ -54,6 +54,10 @@ https://ghostfol.io/de/ressourcen/personal-finance-tools ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-8figures + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-altoo ${currentDate}T00:00:00+00:00 @@ -90,6 +94,10 @@ https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-finary ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-finwise + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-folishare ${currentDate}T00:00:00+00:00 @@ -102,6 +110,10 @@ https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-gospatz ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-intuit-mint + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-justetf ${currentDate}T00:00:00+00:00 @@ -142,6 +154,10 @@ https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-projectionlab ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-rocket-money + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-seeking-alpha ${currentDate}T00:00:00+00:00 @@ -174,6 +190,10 @@ https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-utluna ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-vyzer + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-wealthica ${currentDate}T00:00:00+00:00 @@ -286,6 +306,10 @@ https://ghostfol.io/en/blog/2023/09/hacktoberfest-2023 ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/en/blog/2023/11/hacktoberfest-2023-debriefing + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/en/faq ${currentDate}T00:00:00+00:00 @@ -320,6 +344,10 @@ https://ghostfol.io/en/resources/personal-finance-tools ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-8figures + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-altoo ${currentDate}T00:00:00+00:00 @@ -356,6 +384,10 @@ https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-finary ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-finwise + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-folishare ${currentDate}T00:00:00+00:00 @@ -368,6 +400,10 @@ https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-gospatz ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-intuit-mint + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-justetf ${currentDate}T00:00:00+00:00 @@ -408,6 +444,10 @@ https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-projectionlab ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-rocket-money + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-seeking-alpha ${currentDate}T00:00:00+00:00 @@ -440,6 +480,10 @@ https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-utluna ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-vyzer + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-wealthica ${currentDate}T00:00:00+00:00 @@ -614,6 +658,10 @@ https://ghostfol.io/it/risorse/personal-finance-tools ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-8figures + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-altoo ${currentDate}T00:00:00+00:00 @@ -650,6 +698,10 @@ https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-finary ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-finwise + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-folishare ${currentDate}T00:00:00+00:00 @@ -662,6 +714,10 @@ https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-gospatz ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-intuit-mint + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-justetf ${currentDate}T00:00:00+00:00 @@ -702,6 +758,10 @@ https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-projectionlab ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-rocket-money + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-seeking-alpha ${currentDate}T00:00:00+00:00 @@ -734,6 +794,10 @@ https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-utluna ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-vyzer + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-wealthica ${currentDate}T00:00:00+00:00 @@ -754,6 +818,10 @@ https://ghostfol.io/nl/bronnen/personal-finance-tools ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-8figures + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-altoo ${currentDate}T00:00:00+00:00 @@ -790,6 +858,10 @@ https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-finary ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-finwise + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-folishare ${currentDate}T00:00:00+00:00 @@ -802,6 +874,10 @@ https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-gospatz ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-intuit-mint + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-justetf ${currentDate}T00:00:00+00:00 @@ -842,6 +918,10 @@ https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-projectionlab ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-rocket-money + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-seeking-alpha ${currentDate}T00:00:00+00:00 @@ -874,6 +954,10 @@ https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-utluna ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-vyzer + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-wealthica ${currentDate}T00:00:00+00:00 diff --git a/apps/api/src/middlewares/html-template.middleware.ts b/apps/api/src/middlewares/html-template.middleware.ts index ba5112dac..3d61ae940 100644 --- a/apps/api/src/middlewares/html-template.middleware.ts +++ b/apps/api/src/middlewares/html-template.middleware.ts @@ -75,6 +75,10 @@ const locales = { '/en/blog/2023/09/hacktoberfest-2023': { featureGraphicPath: 'assets/images/blog/hacktoberfest-2023.png', title: `Hacktoberfest 2023 - ${title}` + }, + '/en/blog/2023/11/hacktoberfest-2023-debriefing': { + featureGraphicPath: 'assets/images/blog/hacktoberfest-2023.png', + title: `Hacktoberfest 2023 Debriefing - ${title}` } }; 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 973fc5df2..fbbfffc67 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 @@ -105,9 +105,11 @@ export class AlphaVantageService implements DataProviderInterface { return DataSource.ALPHA_VANTAGE; } - public async getQuotes( - aSymbols: string[] - ): Promise<{ [symbol: string]: IDataProviderResponse }> { + public async getQuotes({ + symbols + }: { + symbols: string[]; + }): Promise<{ [symbol: string]: IDataProviderResponse }> { return {}; } 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 4360822f0..3e93d42bf 100644 --- a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts +++ b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts @@ -134,13 +134,15 @@ export class CoinGeckoService implements DataProviderInterface { return DataSource.COINGECKO; } - public async getQuotes( - aSymbols: string[] - ): Promise<{ [symbol: string]: IDataProviderResponse }> { - const results: { [symbol: string]: IDataProviderResponse } = {}; + public async getQuotes({ + symbols + }: { + symbols: string[]; + }): Promise<{ [symbol: string]: IDataProviderResponse }> { + const response: { [symbol: string]: IDataProviderResponse } = {}; - if (aSymbols.length <= 0) { - return {}; + if (symbols.length <= 0) { + return response; } try { @@ -150,8 +152,8 @@ export class CoinGeckoService implements DataProviderInterface { abortController.abort(); }, DEFAULT_REQUEST_TIMEOUT); - const response = await got( - `${this.URL}/simple/price?ids=${aSymbols.join( + const quotes = await got( + `${this.URL}/simple/price?ids=${symbols.join( ',' )}&vs_currencies=${DEFAULT_CURRENCY.toLowerCase()}`, { @@ -160,22 +162,20 @@ export class CoinGeckoService implements DataProviderInterface { } ).json(); - for (const symbol in response) { - if (Object.prototype.hasOwnProperty.call(response, symbol)) { - results[symbol] = { - currency: DEFAULT_CURRENCY, - dataProviderInfo: this.getDataProviderInfo(), - dataSource: DataSource.COINGECKO, - marketPrice: response[symbol][DEFAULT_CURRENCY.toLowerCase()], - marketState: 'open' - }; - } + for (const symbol in quotes) { + response[symbol] = { + currency: DEFAULT_CURRENCY, + dataProviderInfo: this.getDataProviderInfo(), + dataSource: DataSource.COINGECKO, + marketPrice: quotes[symbol][DEFAULT_CURRENCY.toLowerCase()], + marketState: 'open' + }; } } catch (error) { Logger.error(error, 'CoinGeckoService'); } - return results; + return response; } public getTestSymbol() { 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 8731e709c..d46af66a7 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 @@ -10,6 +10,7 @@ import { Prisma, SymbolProfile } from '@prisma/client'; +import { isISIN } from 'class-validator'; import { countries } from 'countries-list'; import yahooFinance from 'yahoo-finance2'; import type { Price } from 'yahoo-finance2/dist/esm/src/modules/quoteSummary-iface'; @@ -156,7 +157,20 @@ export class YahooFinanceDataEnhancerService implements DataEnhancerInterface { const response: Partial = {}; try { - const symbol = this.convertToYahooFinanceSymbol(aSymbol); + let symbol = aSymbol; + + if (isISIN(symbol)) { + try { + const { quotes } = await yahooFinance.search(symbol); + + if (quotes.length === 1) { + symbol = quotes[0].symbol; + } + } catch {} + } else { + symbol = this.convertToYahooFinanceSymbol(symbol); + } + const assetProfile = await yahooFinance.quoteSummary(symbol, { modules: ['price', 'summaryProfile', 'topHoldings'] }); @@ -176,7 +190,7 @@ export class YahooFinanceDataEnhancerService implements DataEnhancerInterface { shortName: assetProfile.price.shortName, symbol: assetProfile.price.symbol }); - response.symbol = aSymbol; + response.symbol = assetProfile.price.symbol; if (assetSubClass === AssetSubClass.MUTUALFUND) { response.sectors = []; 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 557699495..7a998eeb3 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -311,7 +311,9 @@ export class DataProviderService { i + maximumNumberOfSymbolsPerRequest ); - const promise = Promise.resolve(dataProvider.getQuotes(symbolsChunk)); + const promise = Promise.resolve( + dataProvider.getQuotes({ symbols: symbolsChunk }) + ); promises.push( promise.then(async (result) => { 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 ac2f35c04..37c37e389 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 @@ -131,17 +131,21 @@ export class EodHistoricalDataService implements DataProviderInterface { return DataSource.EOD_HISTORICAL_DATA; } - public async getQuotes( - aSymbols: string[] - ): Promise<{ [symbol: string]: IDataProviderResponse }> { - const symbols = aSymbols.map((symbol) => { - return this.convertToEodSymbol(symbol); - }); + public async getQuotes({ + symbols + }: { + symbols: string[]; + }): Promise<{ [symbol: string]: IDataProviderResponse }> { + let response: { [symbol: string]: IDataProviderResponse } = {}; if (symbols.length <= 0) { - return {}; + return response; } + const eodHistoricalDataSymbols = symbols.map((symbol) => { + return this.convertToEodSymbol(symbol); + }); + try { const abortController = new AbortController(); @@ -150,9 +154,9 @@ export class EodHistoricalDataService implements DataProviderInterface { }, DEFAULT_REQUEST_TIMEOUT); const realTimeResponse = await got( - `${this.URL}/real-time/${symbols[0]}?api_token=${ + `${this.URL}/real-time/${eodHistoricalDataSymbols[0]}?api_token=${ this.apiKey - }&fmt=json&s=${symbols.join(',')}`, + }&fmt=json&s=${eodHistoricalDataSymbols.join(',')}`, { // @ts-ignore signal: abortController.signal @@ -160,10 +164,12 @@ export class EodHistoricalDataService implements DataProviderInterface { ).json(); const quotes = - symbols.length === 1 ? [realTimeResponse] : realTimeResponse; + eodHistoricalDataSymbols.length === 1 + ? [realTimeResponse] + : realTimeResponse; const searchResponse = await Promise.all( - symbols + eodHistoricalDataSymbols .filter((symbol) => { return !symbol.endsWith('.FOREX'); }) @@ -176,7 +182,7 @@ export class EodHistoricalDataService implements DataProviderInterface { return items[0]; }); - const response = quotes.reduce( + response = quotes.reduce( ( result: { [symbol: string]: IDataProviderResponse }, { close, code, timestamp } 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 4fd1d4ebd..b08bc099e 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 @@ -113,13 +113,15 @@ export class FinancialModelingPrepService implements DataProviderInterface { return DataSource.FINANCIAL_MODELING_PREP; } - public async getQuotes( - aSymbols: string[] - ): Promise<{ [symbol: string]: IDataProviderResponse }> { - const results: { [symbol: string]: IDataProviderResponse } = {}; + public async getQuotes({ + symbols + }: { + symbols: string[]; + }): Promise<{ [symbol: string]: IDataProviderResponse }> { + const response: { [symbol: string]: IDataProviderResponse } = {}; - if (aSymbols.length <= 0) { - return {}; + if (symbols.length <= 0) { + return response; } try { @@ -130,7 +132,7 @@ export class FinancialModelingPrepService implements DataProviderInterface { }, DEFAULT_REQUEST_TIMEOUT); const response = await got( - `${this.URL}/quote/${aSymbols.join(',')}?apikey=${this.apiKey}`, + `${this.URL}/quote/${symbols.join(',')}?apikey=${this.apiKey}`, { // @ts-ignore signal: abortController.signal @@ -138,7 +140,7 @@ export class FinancialModelingPrepService implements DataProviderInterface { ).json(); for (const { price, symbol } of response) { - results[symbol] = { + response[symbol] = { currency: DEFAULT_CURRENCY, dataProviderInfo: this.getDataProviderInfo(), dataSource: DataSource.FINANCIAL_MODELING_PREP, @@ -150,7 +152,7 @@ export class FinancialModelingPrepService implements DataProviderInterface { Logger.error(error, 'FinancialModelingPrepService'); } - return results; + return response; } public getTestSymbol() { 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 f4b592371..a541fcd12 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 @@ -99,18 +99,20 @@ export class GoogleSheetsService implements DataProviderInterface { return DataSource.GOOGLE_SHEETS; } - public async getQuotes( - aSymbols: string[] - ): Promise<{ [symbol: string]: IDataProviderResponse }> { - if (aSymbols.length <= 0) { - return {}; + public async getQuotes({ + symbols + }: { + symbols: string[]; + }): Promise<{ [symbol: string]: IDataProviderResponse }> { + const response: { [symbol: string]: IDataProviderResponse } = {}; + + if (symbols.length <= 0) { + return response; } try { - const response: { [symbol: string]: IDataProviderResponse } = {}; - const symbolProfiles = await this.symbolProfileService.getSymbolProfiles( - aSymbols.map((symbol) => { + symbols.map((symbol) => { return { symbol, dataSource: this.getName() @@ -129,7 +131,7 @@ export class GoogleSheetsService implements DataProviderInterface { const marketPrice = parseFloat(row['marketPrice']); const symbol = row['symbol']; - if (aSymbols.includes(symbol)) { + if (symbols.includes(symbol)) { response[symbol] = { marketPrice, currency: symbolProfiles.find((symbolProfile) => { 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 2a16cc24c..f4daeb108 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 @@ -36,9 +36,11 @@ export interface DataProviderInterface { getName(): DataSource; - getQuotes( - aSymbols: string[] - ): Promise<{ [symbol: string]: IDataProviderResponse }>; + getQuotes({ + symbols + }: { + symbols: string[]; + }): Promise<{ [symbol: string]: IDataProviderResponse }>; getTestSymbol(): 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 5c84a9c92..bea4b60ce 100644 --- a/apps/api/src/services/data-provider/manual/manual.service.ts +++ b/apps/api/src/services/data-provider/manual/manual.service.ts @@ -133,18 +133,20 @@ export class ManualService implements DataProviderInterface { return DataSource.MANUAL; } - public async getQuotes( - aSymbols: string[] - ): Promise<{ [symbol: string]: IDataProviderResponse }> { + public async getQuotes({ + symbols + }: { + symbols: string[]; + }): Promise<{ [symbol: string]: IDataProviderResponse }> { const response: { [symbol: string]: IDataProviderResponse } = {}; - if (aSymbols.length <= 0) { + if (symbols.length <= 0) { return response; } try { const symbolProfiles = await this.symbolProfileService.getSymbolProfiles( - aSymbols.map((symbol) => { + symbols.map((symbol) => { return { symbol, dataSource: this.getName() }; }) ); @@ -154,10 +156,10 @@ export class ManualService implements DataProviderInterface { orderBy: { date: 'desc' }, - take: aSymbols.length, + take: symbols.length, where: { symbol: { - in: aSymbols + in: symbols } } }); 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 7743d7805..ce188ffe0 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 @@ -87,15 +87,17 @@ export class RapidApiService implements DataProviderInterface { return DataSource.RAPID_API; } - public async getQuotes( - aSymbols: string[] - ): Promise<{ [symbol: string]: IDataProviderResponse }> { - if (aSymbols.length <= 0) { + public async getQuotes({ + symbols + }: { + symbols: string[]; + }): Promise<{ [symbol: string]: IDataProviderResponse }> { + if (symbols.length <= 0) { return {}; } try { - const symbol = aSymbols[0]; + const symbol = symbols[0]; if (symbol === ghostfolioFearAndGreedIndexSymbol) { const fgi = await this.getFearAndGreedIndex(); 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 c7c0ebbc8..2f69448f9 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 @@ -30,7 +30,7 @@ export class YahooFinanceService implements DataProviderInterface { public async getAssetProfile( aSymbol: string ): Promise> { - const { assetClass, assetSubClass, currency, name } = + const { assetClass, assetSubClass, currency, name, symbol } = await this.yahooFinanceDataEnhancerService.getAssetProfile(aSymbol); return { @@ -38,8 +38,8 @@ export class YahooFinanceService implements DataProviderInterface { assetSubClass, currency, name, - dataSource: this.getName(), - symbol: aSymbol + symbol, + dataSource: this.getName() }; } @@ -156,20 +156,22 @@ export class YahooFinanceService implements DataProviderInterface { return DataSource.YAHOO; } - public async getQuotes( - aSymbols: string[] - ): Promise<{ [symbol: string]: IDataProviderResponse }> { - if (aSymbols.length <= 0) { - return {}; + public async getQuotes({ + symbols + }: { + symbols: string[]; + }): Promise<{ [symbol: string]: IDataProviderResponse }> { + const response: { [symbol: string]: IDataProviderResponse } = {}; + + if (symbols.length <= 0) { + return response; } - const yahooFinanceSymbols = aSymbols.map((symbol) => + const yahooFinanceSymbols = symbols.map((symbol) => this.yahooFinanceDataEnhancerService.convertToYahooFinanceSymbol(symbol) ); try { - const response: { [symbol: string]: IDataProviderResponse } = {}; - let quotes: Pick< Quote, 'currency' | 'marketState' | 'regularMarketPrice' | 'symbol' diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index 4de9a1357..1c8131bca 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -150,11 +150,11 @@ export class AssetProfileDialog implements OnDestroy, OnInit { } this.assetProfileForm.setValue({ - name: this.assetProfile.name, - assetClass: this.assetProfile.assetClass, - assetSubClass: this.assetProfile.assetSubClass, + assetClass: this.assetProfile.assetClass ?? null, + assetSubClass: this.assetProfile.assetSubClass ?? null, comment: this.assetProfile?.comment ?? '', tags: this.assetProfile?.tags, + name: this.assetProfile.name ?? this.assetProfile.symbol, scraperConfiguration: JSON.stringify( this.assetProfile?.scraperConfiguration ?? {} ), diff --git a/apps/client/src/app/components/admin-overview/admin-overview.html b/apps/client/src/app/components/admin-overview/admin-overview.html index 40ae7a150..e82f787f9 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.html +++ b/apps/client/src/app/components/admin-overview/admin-overview.html @@ -55,6 +55,18 @@ {{ exchangeRate.label2 }} + + +