From e1b2c60adddcea2d8dddfe672a8734fe02b5c9c7 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 24 Nov 2024 10:05:39 +0100 Subject: [PATCH] Set up Ghostfolio data provider --- .../ghostfolio/get-historical.dto.ts | 8 ++- .../ghostfolio/ghostfolio.controller.ts | 1 + .../ghostfolio/ghostfolio.service.ts | 27 ++++------ .../ghostfolio/ghostfolio.service.ts | 52 +++++++++++++++++-- 4 files changed, 66 insertions(+), 22 deletions(-) diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/get-historical.dto.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/get-historical.dto.ts index db1cd7a36..385c51d52 100644 --- a/apps/api/src/app/endpoints/data-providers/ghostfolio/get-historical.dto.ts +++ b/apps/api/src/app/endpoints/data-providers/ghostfolio/get-historical.dto.ts @@ -1,9 +1,15 @@ -import { IsISO8601 } from 'class-validator'; +import { Granularity } from '@ghostfolio/common/types'; + +import { IsIn, IsISO8601, IsOptional } from 'class-validator'; export class GetHistoricalDto { @IsISO8601() from: string; + @IsIn(['day', 'month'] as Granularity[]) + @IsOptional() + granularity: Granularity; + @IsISO8601() to: string; } 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 ddb48ef50..58a3224c1 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 @@ -56,6 +56,7 @@ export class GhostfolioController { const historicalData = await this.ghostfolioService.getHistorical({ symbol, from: parseDate(query.from), + granularity: query.granularity, to: parseDate(query.to) }); 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 aa618abb1..e77279d75 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,5 +1,10 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; +import { + GetHistoricalParams, + GetQuotesParams, + GetSearchParams +} from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; @@ -31,15 +36,11 @@ export class GhostfolioService { public async getHistorical({ from, + granularity, requestTimeout, to, symbol - }: { - from: Date; - requestTimeout?: number; - symbol: string; - to: Date; - }) { + }: GetHistoricalParams) { const result: HistoricalResponse = { historicalData: {} }; try { @@ -52,6 +53,7 @@ export class GhostfolioService { dataProviderService .getHistorical({ from, + granularity, requestTimeout, symbol, to @@ -83,13 +85,7 @@ export class GhostfolioService { ); } - public async getQuotes({ - requestTimeout, - symbols - }: { - requestTimeout?: number; - symbols: string[]; - }) { + public async getQuotes({ requestTimeout, symbols }: GetQuotesParams) { const promises: Promise[] = []; const results: QuotesResponse = { quotes: {} }; @@ -180,10 +176,7 @@ export class GhostfolioService { public async lookup({ includeIndices = false, query - }: { - includeIndices?: boolean; - query: string; - }): Promise { + }: GetSearchParams): Promise { const results: LookupResponse = { items: [] }; if (!query) { 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 fcd15907a..d5c214d4e 100644 --- a/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts +++ b/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts @@ -13,14 +13,17 @@ import { } from '@ghostfolio/api/services/interfaces/interfaces'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { PROPERTY_API_KEY_GHOSTFOLIO } from '@ghostfolio/common/config'; +import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { DataProviderInfo, + HistoricalResponse, LookupResponse, QuotesResponse } from '@ghostfolio/common/interfaces'; import { Injectable, Logger } from '@nestjs/common'; import { DataSource, SymbolProfile } from '@prisma/client'; +import { format } from 'date-fns'; import got from 'got'; @Injectable() @@ -52,9 +55,16 @@ export class GhostfolioService implements DataProviderInterface { }: { symbol: string; }): Promise> { + const { items } = await this.search({ query: symbol }); + const searchResult = items?.[0]; + return { symbol, - dataSource: this.getName() + assetClass: searchResult?.assetClass, + assetSubClass: searchResult?.assetSubClass, + currency: searchResult?.currency, + dataSource: this.getName(), + name: searchResult?.name }; } @@ -70,11 +80,45 @@ export class GhostfolioService implements DataProviderInterface { return {}; } - public async getHistorical({}: GetHistoricalParams): Promise<{ + public async getHistorical({ + from, + granularity = 'day', + requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), + symbol, + to + }: GetHistoricalParams): Promise<{ [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; }> { - // TODO - return {}; + try { + const abortController = new AbortController(); + + setTimeout(() => { + abortController.abort(); + }, requestTimeout); + + const { historicalData } = await got( + `${this.URL}/v1/data-providers/ghostfolio/historical/${symbol}?from=${format(from, DATE_FORMAT)}&granularity=${granularity}&to=${format( + to, + DATE_FORMAT + )}`, + { + headers: this.getRequestHeaders(), + // @ts-ignore + signal: abortController.signal + } + ).json(); + + return { + [symbol]: historicalData + }; + } catch (error) { + throw new Error( + `Could not get historical market data for ${symbol} (${this.getName()}) from ${format( + from, + DATE_FORMAT + )} to ${format(to, DATE_FORMAT)}: [${error.name}] ${error.message}` + ); + } } public getMaxNumberOfSymbolsPerRequest() {