diff --git a/apps/api/src/services/data-provider/data-provider.module.ts b/apps/api/src/services/data-provider/data-provider.module.ts index 5c677751d..e430a0d9b 100644 --- a/apps/api/src/services/data-provider/data-provider.module.ts +++ b/apps/api/src/services/data-provider/data-provider.module.ts @@ -7,6 +7,7 @@ import { Module } from '@nestjs/common'; import { AlphaVantageService } from './alpha-vantage/alpha-vantage.service'; import { DataProviderService } from './data-provider.service'; +import { TrackinsightEnhancerService } from '@ghostfolio/api/services/data-provider/trackinsight-enhancer/trackinsight-enhancer.service'; @Module({ imports: [ConfigurationModule, PrismaModule], @@ -15,7 +16,13 @@ import { DataProviderService } from './data-provider.service'; DataProviderService, GhostfolioScraperApiService, RakutenRapidApiService, - YahooFinanceService + TrackinsightEnhancerService, + YahooFinanceService, + { + provide: 'DataEnhancers', + useFactory: (trackinsight) => [trackinsight], + inject: [TrackinsightEnhancerService] + } ], exports: [DataProviderService, GhostfolioScraperApiService] }) 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 396f2f824..43e0ab8f9 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -8,7 +8,7 @@ import { import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { Granularity } from '@ghostfolio/common/types'; -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { DataSource, MarketData } from '@prisma/client'; import { format } from 'date-fns'; import { isEmpty } from 'lodash'; @@ -17,6 +17,7 @@ import { AlphaVantageService } from './alpha-vantage/alpha-vantage.service'; import { GhostfolioScraperApiService } from './ghostfolio-scraper-api/ghostfolio-scraper-api.service'; import { RakutenRapidApiService } from './rakuten-rapid-api/rakuten-rapid-api.service'; import { YahooFinanceService } from './yahoo-finance/yahoo-finance.service'; +import { DataEnhancerInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-enhancer.interface'; @Injectable() export class DataProviderService { @@ -26,7 +27,9 @@ export class DataProviderService { private readonly ghostfolioScraperApiService: GhostfolioScraperApiService, private readonly prismaService: PrismaService, private readonly rakutenRapidApiService: RakutenRapidApiService, - private readonly yahooFinanceService: YahooFinanceService + private readonly yahooFinanceService: YahooFinanceService, + @Inject('DataEnhancers') + private readonly dataEnhancers: DataEnhancerInterface[] ) { this.rakutenRapidApiService?.setPrisma(this.prismaService); } @@ -45,6 +48,22 @@ export class DataProviderService { ]; } + const promises = []; + for (const symbol of Object.keys(response)) { + let promise = Promise.resolve(response[symbol]); + for (const dataEnhancer of this.dataEnhancers) { + promise = promise.then((r) => + dataEnhancer.enhance(symbol, r).catch((e) => { + console.error(`Failed to enhance data for symbol ${symbol}`, e); + return r; + }) + ); + } + promises.push(promise.then((r) => (response[symbol] = r))); + } + + await Promise.all(promises); + return response; } diff --git a/apps/api/src/services/data-provider/interfaces/data-enhancer.interface.ts b/apps/api/src/services/data-provider/interfaces/data-enhancer.interface.ts new file mode 100644 index 000000000..89a28d4e3 --- /dev/null +++ b/apps/api/src/services/data-provider/interfaces/data-enhancer.interface.ts @@ -0,0 +1,8 @@ +import { IDataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; + +export interface DataEnhancerInterface { + enhance( + symbol: string, + response: IDataProviderResponse + ): Promise; +} diff --git a/apps/api/src/services/data-provider/trackinsight-enhancer/trackinsight-enhancer.service.ts b/apps/api/src/services/data-provider/trackinsight-enhancer/trackinsight-enhancer.service.ts new file mode 100644 index 000000000..fdd8f2c5b --- /dev/null +++ b/apps/api/src/services/data-provider/trackinsight-enhancer/trackinsight-enhancer.service.ts @@ -0,0 +1,44 @@ +import { DataEnhancerInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-enhancer.interface'; +import { IDataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; +import bent from 'bent'; + +const countries = require('countries-list/dist/countries.json'); +const getJSON = bent('json'); + +export class TrackinsightEnhancerService implements DataEnhancerInterface { + public async enhance( + symbol: string, + response: IDataProviderResponse + ): Promise { + if ( + !(response.assetClass === 'EQUITY' && response.assetSubClass === 'ETF') + ) { + return response; + } + + const holdings = await getJSON( + `https://data.trackinsight.com/holdings/${symbol}.json` + ); + + if (!response.countries || response.countries.length === 0) { + response.countries = []; + for (const [name, value] of Object.entries(holdings.countries)) { + let countryCode: string; + + for (const [key, country] of Object.entries(countries)) { + if (country.name === name) { + countryCode = key; + break; + } + } + + response.countries.push({ + code: countryCode, + weight: value.weight + }); + } + } + + return Promise.resolve(response); + } +}