Your ROOT_URL in app.ini is https://git.cloudchain.link/ but you are visiting https://dash.bss.nz/open-source-mirrors/ghostfolio/commit/e4968dbea7f2d1fe085704e9c4fe73b090b17c6c
You should set ROOT_URL correctly, otherwise the web may not work correctly.
5 changed files with
68 additions and
5 deletions
@ -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 ) ;
}