Feature/extend health check endpoint by database and cache operations (#4188)
* Extend health check endpoint by database and cache operations * Update changelog
This commit is contained in:
parent
ec79a9efb6
commit
e4968dbea7
@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Extended the health check endpoint to include database and cache operations (experimental)
|
||||||
- Refactored various `lodash` functions with native JavaScript equivalents
|
- Refactored various `lodash` functions with native JavaScript equivalents
|
||||||
- Improved the language localization for German (`de`)
|
- Improved the language localization for German (`de`)
|
||||||
- Upgraded `prisma` from version `6.1.0` to `6.2.1`
|
- Upgraded `prisma` from version `6.1.0` to `6.2.1`
|
||||||
|
@ -3,13 +3,14 @@ import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interce
|
|||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
HttpCode,
|
|
||||||
HttpException,
|
HttpException,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
Param,
|
Param,
|
||||||
|
Res,
|
||||||
UseInterceptors
|
UseInterceptors
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { DataSource } from '@prisma/client';
|
import { DataSource } from '@prisma/client';
|
||||||
|
import { Response } from 'express';
|
||||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||||
|
|
||||||
import { HealthService } from './health.service';
|
import { HealthService } from './health.service';
|
||||||
@ -19,9 +20,20 @@ export class HealthController {
|
|||||||
public constructor(private readonly healthService: HealthService) {}
|
public constructor(private readonly healthService: HealthService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@HttpCode(HttpStatus.OK)
|
public async getHealth(@Res() response: Response) {
|
||||||
public getHealth() {
|
const databaseServiceHealthy = await this.healthService.isDatabaseHealthy();
|
||||||
return { status: getReasonPhrase(StatusCodes.OK) };
|
const redisCacheServiceHealthy =
|
||||||
|
await this.healthService.isRedisCacheHealthy();
|
||||||
|
|
||||||
|
if (databaseServiceHealthy && redisCacheServiceHealthy) {
|
||||||
|
return response
|
||||||
|
.status(HttpStatus.OK)
|
||||||
|
.json({ status: getReasonPhrase(StatusCodes.OK) });
|
||||||
|
} else {
|
||||||
|
return response
|
||||||
|
.status(HttpStatus.SERVICE_UNAVAILABLE)
|
||||||
|
.json({ status: getReasonPhrase(StatusCodes.SERVICE_UNAVAILABLE) });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('data-enhancer/:name')
|
@Get('data-enhancer/:name')
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
|
||||||
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
|
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
|
||||||
import { DataEnhancerModule } from '@ghostfolio/api/services/data-provider/data-enhancer/data-enhancer.module';
|
import { DataEnhancerModule } from '@ghostfolio/api/services/data-provider/data-enhancer/data-enhancer.module';
|
||||||
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
||||||
|
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
|
||||||
|
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
@ -12,6 +14,8 @@ import { HealthService } from './health.service';
|
|||||||
imports: [
|
imports: [
|
||||||
DataEnhancerModule,
|
DataEnhancerModule,
|
||||||
DataProviderModule,
|
DataProviderModule,
|
||||||
|
PropertyModule,
|
||||||
|
RedisCacheModule,
|
||||||
TransformDataSourceInRequestModule
|
TransformDataSourceInRequestModule
|
||||||
],
|
],
|
||||||
providers: [HealthService]
|
providers: [HealthService]
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
import { DataEnhancerService } from '@ghostfolio/api/services/data-provider/data-enhancer/data-enhancer.service';
|
import { DataEnhancerService } from '@ghostfolio/api/services/data-provider/data-enhancer/data-enhancer.service';
|
||||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
|
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
|
||||||
|
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
||||||
|
import { PROPERTY_CURRENCIES } from '@ghostfolio/common/config';
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { DataSource } from '@prisma/client';
|
import { DataSource } from '@prisma/client';
|
||||||
@ -8,7 +11,9 @@ import { DataSource } from '@prisma/client';
|
|||||||
export class HealthService {
|
export class HealthService {
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly dataEnhancerService: DataEnhancerService,
|
private readonly dataEnhancerService: DataEnhancerService,
|
||||||
private readonly dataProviderService: DataProviderService
|
private readonly dataProviderService: DataProviderService,
|
||||||
|
private readonly propertyService: PropertyService,
|
||||||
|
private readonly redisCacheService: RedisCacheService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async hasResponseFromDataEnhancer(aName: string) {
|
public async hasResponseFromDataEnhancer(aName: string) {
|
||||||
@ -18,4 +23,24 @@ export class HealthService {
|
|||||||
public async hasResponseFromDataProvider(aDataSource: DataSource) {
|
public async hasResponseFromDataProvider(aDataSource: DataSource) {
|
||||||
return this.dataProviderService.checkQuote(aDataSource);
|
return this.dataProviderService.checkQuote(aDataSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async isDatabaseHealthy() {
|
||||||
|
try {
|
||||||
|
await this.propertyService.getByKey(PROPERTY_CURRENCIES);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async isRedisCacheHealthy() {
|
||||||
|
try {
|
||||||
|
const isHealthy = await this.redisCacheService.isHealthy();
|
||||||
|
|
||||||
|
return isHealthy;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import { Inject, Injectable, Logger } from '@nestjs/common';
|
|||||||
import { Milliseconds } from 'cache-manager';
|
import { Milliseconds } from 'cache-manager';
|
||||||
import { RedisCache } from 'cache-manager-redis-yet';
|
import { RedisCache } from 'cache-manager-redis-yet';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
|
import ms from 'ms';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RedisCacheService {
|
export class RedisCacheService {
|
||||||
@ -59,6 +60,26 @@ export class RedisCacheService {
|
|||||||
return `quote-${getAssetProfileIdentifier({ dataSource, symbol })}`;
|
return `quote-${getAssetProfileIdentifier({ dataSource, symbol })}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async isHealthy() {
|
||||||
|
try {
|
||||||
|
const client = this.cache.store.client;
|
||||||
|
|
||||||
|
const isHealthy = await Promise.race([
|
||||||
|
client.ping(),
|
||||||
|
new Promise((_, reject) =>
|
||||||
|
setTimeout(
|
||||||
|
() => reject(new Error('Redis health check timeout')),
|
||||||
|
ms('2 seconds')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
|
||||||
|
return isHealthy === 'PONG';
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async remove(key: string) {
|
public async remove(key: string) {
|
||||||
return this.cache.del(key);
|
return this.cache.del(key);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user