Feature/add health check endpoints (#1886)

* Add health check endpoints

* Update changelog
pull/1887/head
Thomas Kaul 1 year ago committed by GitHub
parent 3daf55a0dd
commit e965d12e31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added a fallback to historical market data if a data provider does not provide live data
- Added a general health check endpoint
- Added health check endpoints for data providers
### Changed

@ -24,6 +24,7 @@ import { CacheModule } from './cache/cache.module';
import { ExchangeRateModule } from './exchange-rate/exchange-rate.module';
import { ExportModule } from './export/export.module';
import { FrontendMiddleware } from './frontend.middleware';
import { HealthModule } from './health/health.module';
import { ImportModule } from './import/import.module';
import { InfoModule } from './info/info.module';
import { LogoModule } from './logo/logo.module';
@ -57,6 +58,7 @@ import { UserModule } from './user/user.module';
ExchangeRateModule,
ExchangeRateDataModule,
ExportModule,
HealthModule,
ImportModule,
InfoModule,
LogoModule,

@ -0,0 +1,44 @@
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor';
import {
Controller,
Get,
HttpException,
Param,
UseInterceptors
} from '@nestjs/common';
import { HealthService } from './health.service';
import { DataSource } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
@Controller('health')
export class HealthController {
public constructor(private readonly healthService: HealthService) {}
@Get()
public async getHealth() {}
@Get('data-provider/:dataSource')
@UseInterceptors(TransformDataSourceInRequestInterceptor)
public async getHealthOfDataProvider(
@Param('dataSource') dataSource: DataSource
) {
if (!DataSource[dataSource]) {
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
StatusCodes.NOT_FOUND
);
}
const hasResponse = await this.healthService.hasResponseFromDataProvider(
dataSource
);
if (hasResponse !== true) {
throw new HttpException(
getReasonPhrase(StatusCodes.SERVICE_UNAVAILABLE),
StatusCodes.SERVICE_UNAVAILABLE
);
}
}
}

@ -0,0 +1,13 @@
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { Module } from '@nestjs/common';
import { HealthController } from './health.controller';
import { HealthService } from './health.service';
@Module({
controllers: [HealthController],
imports: [ConfigurationModule, DataProviderModule],
providers: [HealthService]
})
export class HealthModule {}

@ -0,0 +1,14 @@
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { Injectable } from '@nestjs/common';
import { DataSource } from '@prisma/client';
@Injectable()
export class HealthService {
public constructor(
private readonly dataProviderService: DataProviderService
) {}
public async hasResponseFromDataProvider(aDataSource: DataSource) {
return this.dataProviderService.checkQuote(aDataSource);
}
}

@ -7,7 +7,7 @@ import {
} from '@ghostfolio/api/services/interfaces/interfaces';
import { DATE_FORMAT } from '@ghostfolio/common/helper';
import { Granularity } from '@ghostfolio/common/types';
import { Injectable, Logger } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { DataSource, SymbolProfile } from '@prisma/client';
import { format, isAfter, isBefore, parse } from 'date-fns';
@ -110,6 +110,10 @@ export class AlphaVantageService implements DataProviderInterface {
return {};
}
public getTestSymbol() {
return undefined;
}
public async search(aQuery: string): Promise<{ items: LookupItem[] }> {
const result = await this.alphaVantage.data.search(aQuery);

@ -160,6 +160,10 @@ export class CoinGeckoService implements DataProviderInterface {
return results;
}
public getTestSymbol() {
return 'bitcoin';
}
public async search(aQuery: string): Promise<{ items: LookupItem[] }> {
let items: LookupItem[] = [];

@ -38,6 +38,24 @@ export class DataProviderService {
}) ?? {};
}
public async checkQuote(dataSource: DataSource) {
const dataProvider = this.getDataProvider(dataSource);
const symbol = dataProvider.getTestSymbol();
const quotes = await this.getQuotes([
{
dataSource,
symbol
}
]);
if (quotes[symbol]?.marketPrice > 0) {
return true;
}
return false;
}
public async getDividends({
dataSource,
from,

@ -172,6 +172,10 @@ export class EodHistoricalDataService implements DataProviderInterface {
return {};
}
public getTestSymbol() {
return 'AAPL.US';
}
public async search(aQuery: string): Promise<{ items: LookupItem[] }> {
const searchResult = await this.getSearchResult(aQuery);

@ -143,6 +143,10 @@ export class GoogleSheetsService implements DataProviderInterface {
return {};
}
public getTestSymbol() {
return 'INDEXSP:.INX';
}
public async search(aQuery: string): Promise<{ items: LookupItem[] }> {
const items = await this.prismaService.symbolProfile.findMany({
select: {

@ -40,5 +40,7 @@ export interface DataProviderInterface {
aSymbols: string[]
): Promise<{ [symbol: string]: IDataProviderResponse }>;
getTestSymbol(): string;
search(aQuery: string): Promise<{ items: LookupItem[] }>;
}

@ -163,6 +163,10 @@ export class ManualService implements DataProviderInterface {
return {};
}
public getTestSymbol() {
return undefined;
}
public async search(aQuery: string): Promise<{ items: LookupItem[] }> {
let items = await this.prismaService.symbolProfile.findMany({
select: {

@ -113,6 +113,10 @@ export class RapidApiService implements DataProviderInterface {
return {};
}
public getTestSymbol() {
return undefined;
}
public async search(aQuery: string): Promise<{ items: LookupItem[] }> {
return { items: [] };
}

@ -167,6 +167,7 @@ export class YahooFinanceService implements DataProviderInterface {
if (aSymbols.length <= 0) {
return {};
}
const yahooFinanceSymbols = aSymbols.map((symbol) =>
this.yahooFinanceDataEnhancerService.convertToYahooFinanceSymbol(symbol)
);
@ -251,6 +252,10 @@ export class YahooFinanceService implements DataProviderInterface {
}
}
public getTestSymbol() {
return 'AAPL';
}
public async search(aQuery: string): Promise<{ items: LookupItem[] }> {
const items: LookupItem[] = [];

Loading…
Cancel
Save