Browse Source

Feature/add health check endpoints (#1886)

* Add health check endpoints

* Update changelog
pull/1887/head
Thomas Kaul 2 years ago
committed by GitHub
parent
commit
e965d12e31
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CHANGELOG.md
  2. 2
      apps/api/src/app/app.module.ts
  3. 44
      apps/api/src/app/health/health.controller.ts
  4. 13
      apps/api/src/app/health/health.module.ts
  5. 14
      apps/api/src/app/health/health.service.ts
  6. 6
      apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts
  7. 4
      apps/api/src/services/data-provider/coingecko/coingecko.service.ts
  8. 18
      apps/api/src/services/data-provider/data-provider.service.ts
  9. 4
      apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts
  10. 4
      apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts
  11. 2
      apps/api/src/services/data-provider/interfaces/data-provider.interface.ts
  12. 4
      apps/api/src/services/data-provider/manual/manual.service.ts
  13. 4
      apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts
  14. 5
      apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts

2
CHANGELOG.md

@ -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

2
apps/api/src/app/app.module.ts

@ -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,

44
apps/api/src/app/health/health.controller.ts

@ -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
);
}
}
}

13
apps/api/src/app/health/health.module.ts

@ -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 {}

14
apps/api/src/app/health/health.service.ts

@ -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);
}
}

6
apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts

@ -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);

4
apps/api/src/services/data-provider/coingecko/coingecko.service.ts

@ -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[] = [];

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

@ -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,

4
apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts

@ -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);

4
apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts

@ -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: {

2
apps/api/src/services/data-provider/interfaces/data-provider.interface.ts

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

4
apps/api/src/services/data-provider/manual/manual.service.ts

@ -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: {

4
apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts

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

5
apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts

@ -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