diff --git a/CHANGELOG.md b/CHANGELOG.md index b39f92677a..c3280f51ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Extended the _Public API_ with the liveness probe endpoint (`GET api/v1/health/liveness`) (experimental) + ### Changed - Upgraded `@simplewebauthn/browser` and `@simplewebauthn/server` from version `13.3` to `14.0` diff --git a/README.md b/README.md index b9352e3bd3..59c7c5d2c6 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,8 @@ You can get the _Bearer Token_ via `POST http://localhost:3333/api/v1/auth/anony ### Health Check (experimental) +The endpoint checks the connection to the database and to _Redis_. It is suitable as the readiness probe of an orchestrator (e.g. `readinessProbe` in _Kubernetes_) and as the `healthcheck` in _Docker Compose_. The check of _Redis_ can take up to 5 seconds. Thus, set the timeout of the probe to at least 5 seconds (e.g. `timeoutSeconds: 5` in _Kubernetes_). + #### Request `GET http://localhost:3333/api/v1/health` @@ -211,6 +213,28 @@ You can get the _Bearer Token_ via `POST http://localhost:3333/api/v1/auth/anony } ``` +### Liveness Probe (experimental) + +The endpoint answers without a check of the database or _Redis_. It is suitable as the liveness probe of an orchestrator (e.g. `livenessProbe` in _Kubernetes_), so that an outage of a dependency after the start does not restart the container. The database migrations run before the server starts. Thus, also use the endpoint as the startup probe (e.g. `startupProbe` in _Kubernetes_), so that a long migration does not restart the container. + +#### Request + +`GET http://localhost:3333/api/v1/health/liveness` + +**Info:** No Bearer Token is required for liveness probe + +#### Response + +##### Success + +`200 OK` + +``` +{ + "status": "OK" +} +``` + ### Import Portfolio Data #### Prerequisites diff --git a/apps/api/src/app/health/health.controller.ts b/apps/api/src/app/health/health.controller.ts index 4f88a03f0c..32aae38e3c 100644 --- a/apps/api/src/app/health/health.controller.ts +++ b/apps/api/src/app/health/health.controller.ts @@ -3,7 +3,8 @@ import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interce import { AiServiceHealthResponse, DataEnhancerHealthResponse, - DataProviderHealthResponse + DataProviderHealthResponse, + LivenessHealthResponse } from '@ghostfolio/common/interfaces'; import { @@ -33,9 +34,11 @@ export class HealthController { @Get() public async getHealth(@Res() response: Response) { - const databaseServiceHealthy = await this.healthService.isDatabaseHealthy(); - const redisCacheServiceHealthy = - await this.healthService.isRedisCacheHealthy(); + const [databaseServiceHealthy, redisCacheServiceHealthy] = + await Promise.all([ + this.healthService.isDatabaseHealthy(), + this.healthService.isRedisCacheHealthy() + ]); if (databaseServiceHealthy && redisCacheServiceHealthy) { return response @@ -116,4 +119,9 @@ export class HealthController { .json({ status: getReasonPhrase(StatusCodes.SERVICE_UNAVAILABLE) }); } } + + @Get('liveness') + public getLiveness(): LivenessHealthResponse { + return { status: getReasonPhrase(StatusCodes.OK) }; + } } diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index c581b8e56e..113e485b74 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -68,6 +68,7 @@ import type { ExportResponse } from './responses/export-response.interface'; import type { HistoricalResponse } from './responses/historical-response.interface'; import type { ImportResponse } from './responses/import-response.interface'; import type { InfoResponse } from './responses/info-response.interface'; +import type { LivenessHealthResponse } from './responses/liveness-health-response.interface'; import type { LookupResponse } from './responses/lookup-response.interface'; import type { MarketDataOfMarketsResponse } from './responses/market-data-of-markets-response.interface'; import type { OAuthResponse } from './responses/oauth-response.interface'; @@ -156,6 +157,7 @@ export { InfoResponse, InvestmentItem, LineChartItem, + LivenessHealthResponse, LookupItem, LookupResponse, MarketData, diff --git a/libs/common/src/lib/interfaces/responses/liveness-health-response.interface.ts b/libs/common/src/lib/interfaces/responses/liveness-health-response.interface.ts new file mode 100644 index 0000000000..28db1fb5b3 --- /dev/null +++ b/libs/common/src/lib/interfaces/responses/liveness-health-response.interface.ts @@ -0,0 +1,3 @@ +export interface LivenessHealthResponse { + status: string; +}