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
|
||||
|
||||
- Extended the health check endpoint to include database and cache operations (experimental)
|
||||
- Refactored various `lodash` functions with native JavaScript equivalents
|
||||
- Improved the language localization for German (`de`)
|
||||
- Upgraded `prisma` from version `6.1.0` to `6.2.1`
|
||||
|
@ -3,13 +3,14 @@ import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interce
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Res,
|
||||
UseInterceptors
|
||||
} from '@nestjs/common';
|
||||
import { DataSource } from '@prisma/client';
|
||||
import { Response } from 'express';
|
||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||
|
||||
import { HealthService } from './health.service';
|
||||
@ -19,9 +20,20 @@ export class HealthController {
|
||||
public constructor(private readonly healthService: HealthService) {}
|
||||
|
||||
@Get()
|
||||
@HttpCode(HttpStatus.OK)
|
||||
public getHealth() {
|
||||
return { status: getReasonPhrase(StatusCodes.OK) };
|
||||
public async getHealth(@Res() response: Response) {
|
||||
const databaseServiceHealthy = await this.healthService.isDatabaseHealthy();
|
||||
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')
|
||||
|
@ -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 { DataEnhancerModule } from '@ghostfolio/api/services/data-provider/data-enhancer/data-enhancer.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';
|
||||
|
||||
@ -12,6 +14,8 @@ import { HealthService } from './health.service';
|
||||
imports: [
|
||||
DataEnhancerModule,
|
||||
DataProviderModule,
|
||||
PropertyModule,
|
||||
RedisCacheModule,
|
||||
TransformDataSourceInRequestModule
|
||||
],
|
||||
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 { 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 { DataSource } from '@prisma/client';
|
||||
@ -8,7 +11,9 @@ import { DataSource } from '@prisma/client';
|
||||
export class HealthService {
|
||||
public constructor(
|
||||
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) {
|
||||
@ -18,4 +23,24 @@ export class HealthService {
|
||||
public async hasResponseFromDataProvider(aDataSource: DataSource) {
|
||||
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 { RedisCache } from 'cache-manager-redis-yet';
|
||||
import { createHash } from 'crypto';
|
||||
import ms from 'ms';
|
||||
|
||||
@Injectable()
|
||||
export class RedisCacheService {
|
||||
@ -59,6 +60,26 @@ export class RedisCacheService {
|
||||
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) {
|
||||
return this.cache.del(key);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user