Browse Source

Set up Ghostfolio data provider

pull/4016/head
Thomas Kaul 9 months ago
parent
commit
6ef8598d72
  1. 3
      apps/api/src/app/import/import.service.ts
  2. 5
      apps/api/src/services/data-provider/data-provider.module.ts
  3. 29
      apps/api/src/services/data-provider/data-provider.service.ts
  4. 132
      apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts
  5. 1
      libs/common/src/lib/config.ts

3
apps/api/src/app/import/import.service.ts

@ -582,12 +582,13 @@ export class ImportService {
const assetProfiles: {
[assetProfileIdentifier: string]: Partial<SymbolProfile>;
} = {};
const dataSources = await this.dataProviderService.getDataSources();
for (const [
index,
{ currency, dataSource, symbol, type }
] of activitiesDto.entries()) {
if (!this.configurationService.get('DATA_SOURCES').includes(dataSource)) {
if (!dataSources.includes(dataSource)) {
throw new Error(
`activities.${index}.dataSource ("${dataSource}") is not valid`
);

5
apps/api/src/services/data-provider/data-provider.module.ts

@ -5,6 +5,7 @@ import { AlphaVantageService } from '@ghostfolio/api/services/data-provider/alph
import { CoinGeckoService } from '@ghostfolio/api/services/data-provider/coingecko/coingecko.service';
import { EodHistoricalDataService } from '@ghostfolio/api/services/data-provider/eod-historical-data/eod-historical-data.service';
import { FinancialModelingPrepService } from '@ghostfolio/api/services/data-provider/financial-modeling-prep/financial-modeling-prep.service';
import { GhostfolioService } from '@ghostfolio/api/services/data-provider/ghostfolio/ghostfolio.service';
import { GoogleSheetsService } from '@ghostfolio/api/services/data-provider/google-sheets/google-sheets.service';
import { ManualService } from '@ghostfolio/api/services/data-provider/manual/manual.service';
import { RapidApiService } from '@ghostfolio/api/services/data-provider/rapid-api/rapid-api.service';
@ -37,6 +38,7 @@ import { DataProviderService } from './data-provider.service';
DataProviderService,
EodHistoricalDataService,
FinancialModelingPrepService,
GhostfolioService,
GoogleSheetsService,
ManualService,
RapidApiService,
@ -47,6 +49,7 @@ import { DataProviderService } from './data-provider.service';
CoinGeckoService,
EodHistoricalDataService,
FinancialModelingPrepService,
GhostfolioService,
GoogleSheetsService,
ManualService,
RapidApiService,
@ -58,6 +61,7 @@ import { DataProviderService } from './data-provider.service';
coinGeckoService,
eodHistoricalDataService,
financialModelingPrepService,
ghostfolioService,
googleSheetsService,
manualService,
rapidApiService,
@ -67,6 +71,7 @@ import { DataProviderService } from './data-provider.service';
coinGeckoService,
eodHistoricalDataService,
financialModelingPrepService,
ghostfolioService,
googleSheetsService,
manualService,
rapidApiService,

29
apps/api/src/services/data-provider/data-provider.service.ts

@ -11,6 +11,7 @@ import { PropertyService } from '@ghostfolio/api/services/property/property.serv
import {
DEFAULT_CURRENCY,
DERIVED_CURRENCIES,
PROPERTY_API_KEY_GHOSTFOLIO,
PROPERTY_DATA_SOURCE_MAPPING
} from '@ghostfolio/common/config';
import {
@ -153,6 +154,24 @@ export class DataProviderService {
return DataSource[this.configurationService.get('DATA_SOURCE_IMPORT')];
}
public async getDataSources(): Promise<DataSource[]> {
const dataSources: DataSource[] = this.configurationService
.get('DATA_SOURCES')
.map((dataSource) => {
return DataSource[dataSource];
});
const ghostfolioApiKey = (await this.propertyService.getByKey(
PROPERTY_API_KEY_GHOSTFOLIO
)) as string;
if (ghostfolioApiKey) {
dataSources.push('GHOSTFOLIO');
}
return dataSources.sort();
}
public async getDividends({
dataSource,
from,
@ -589,11 +608,11 @@ export class DataProviderService {
return { items: lookupItems };
}
const dataProviderServices = this.configurationService
.get('DATA_SOURCES')
.map((dataSource) => {
return this.getDataProvider(DataSource[dataSource]);
});
const dataSources = await this.getDataSources();
const dataProviderServices = dataSources.map((dataSource) => {
return this.getDataProvider(DataSource[dataSource]);
});
for (const dataProviderService of dataProviderServices) {
promises.push(

132
apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts

@ -0,0 +1,132 @@
import { environment } from '@ghostfolio/api/environments/environment';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import {
DataProviderInterface,
GetDividendsParams,
GetHistoricalParams,
GetQuotesParams,
GetSearchParams
} from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface';
import {
IDataProviderHistoricalResponse,
IDataProviderResponse
} 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 {
DataProviderInfo,
LookupResponse
} from '@ghostfolio/common/interfaces';
import { Injectable, Logger } from '@nestjs/common';
import { DataSource, SymbolProfile } from '@prisma/client';
import got from 'got';
@Injectable()
export class GhostfolioService implements DataProviderInterface {
private apiKey: string;
private readonly URL = environment.production
? 'https://ghostfol.io/api'
: `${this.configurationService.get('ROOT_URL')}/api`;
public constructor(
private readonly configurationService: ConfigurationService,
private readonly propertyService: PropertyService
) {
void this.initialize();
}
public async initialize() {
this.apiKey = (await this.propertyService.getByKey(
PROPERTY_API_KEY_GHOSTFOLIO
)) as string;
}
public canHandle() {
return true;
}
public async getAssetProfile({
symbol
}: {
symbol: string;
}): Promise<Partial<SymbolProfile>> {
return {
symbol,
dataSource: this.getName()
};
}
public getDataProviderInfo(): DataProviderInfo {
return {
isPremium: true,
name: 'Ghostfolio',
url: 'https://ghostfo.io'
};
}
public async getDividends({}: GetDividendsParams) {
return {};
}
public async getHistorical({}: GetHistoricalParams): Promise<{
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
}> {
// TODO
return {};
}
public getMaxNumberOfSymbolsPerRequest() {
return 20;
}
public getName(): DataSource {
return DataSource.GHOSTFOLIO;
}
public async getQuotes({}: GetQuotesParams): Promise<{
[symbol: string]: IDataProviderResponse;
}> {
// TODO
return {};
}
public getTestSymbol() {
return 'AAPL.US';
}
public async search({ query }: GetSearchParams): Promise<LookupResponse> {
let searchResult: LookupResponse = { items: [] };
try {
const abortController = new AbortController();
setTimeout(() => {
abortController.abort();
}, this.configurationService.get('REQUEST_TIMEOUT'));
searchResult = await got(
`${this.URL}/v1/data-providers/ghostfolio/lookup?query=${query}`,
{
headers: {
Authorization: `Bearer ${this.apiKey}`
},
// @ts-ignore
signal: abortController.signal
}
).json<any>();
} catch (error) {
let message = error;
if (error?.code === 'ABORT_ERR') {
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'
)}ms`;
}
Logger.error(message, 'GhostfolioService');
}
return searchResult;
}
}

1
libs/common/src/lib/config.ts

@ -111,6 +111,7 @@ export const MAX_TOP_HOLDINGS = 50;
export const NUMERICAL_PRECISION_THRESHOLD = 100000;
export const PROPERTY_API_KEY_GHOSTFOLIO = 'API_KEY_GHOSTFOLIO';
export const PROPERTY_BENCHMARKS = 'BENCHMARKS';
export const PROPERTY_BETTER_UPTIME_MONITOR_ID = 'BETTER_UPTIME_MONITOR_ID';
export const PROPERTY_COUNTRIES_OF_SUBSCRIBERS = 'COUNTRIES_OF_SUBSCRIBERS';

Loading…
Cancel
Save