diff --git a/apps/shared/src/modules/health/constants.ts b/apps/shared/src/modules/health/constants.ts new file mode 100644 index 0000000000000000000000000000000000000000..197819846ccd8af559649884e286d9b30d61f994 --- /dev/null +++ b/apps/shared/src/modules/health/constants.ts @@ -0,0 +1 @@ +export const NATS = 'NATS'; diff --git a/apps/shared/src/modules/health/health.controller.ts b/apps/shared/src/modules/health/health.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..fb6e94b8e3d16426201b08090bef748f4dff65cc --- /dev/null +++ b/apps/shared/src/modules/health/health.controller.ts @@ -0,0 +1,20 @@ +import { Controller, Get, VERSION_NEUTRAL } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { HealthCheck, HealthCheckService } from '@nestjs/terminus'; + +import { NATSHealthIndicator } from './indicators/nats.health.js'; + +@Controller({ version: VERSION_NEUTRAL }) +@ApiTags('Health') +export class HealthController { + public constructor( + private readonly natsHealthIndicator: NATSHealthIndicator, + private readonly health: HealthCheckService, + ) {} + + @Get() + @HealthCheck() + public check() { + return this.health.check([() => this.natsHealthIndicator.isHealthy()]); + } +} diff --git a/apps/shared/src/modules/health/health.module-definition.ts b/apps/shared/src/modules/health/health.module-definition.ts new file mode 100644 index 0000000000000000000000000000000000000000..d548701ec130377bd3a455ddc92631b778da0cc1 --- /dev/null +++ b/apps/shared/src/modules/health/health.module-definition.ts @@ -0,0 +1,14 @@ +import { ConfigurableModuleBuilder } from '@nestjs/common'; + +export interface HealthModuleOptions { + nats?: { + monitoringUrl: string; + }; +} + +export const { + ConfigurableModuleClass, + MODULE_OPTIONS_TOKEN, + OPTIONS_TYPE, + ASYNC_OPTIONS_TYPE, +} = new ConfigurableModuleBuilder<HealthModuleOptions>().build(); diff --git a/apps/shared/src/modules/health/health.module.ts b/apps/shared/src/modules/health/health.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..c9776505198b34436c1f1f8a63ed68d0b5bcec47 --- /dev/null +++ b/apps/shared/src/modules/health/health.module.ts @@ -0,0 +1,14 @@ +import { HttpModule } from '@nestjs/axios'; +import { Module } from '@nestjs/common'; +import { TerminusModule } from '@nestjs/terminus'; + +import { HealthController } from './health.controller.js'; +import { ConfigurableModuleClass } from './health.module-definition.js'; +import { NATSHealthIndicator } from './indicators/nats.health.js'; + +@Module({ + imports: [HttpModule, TerminusModule], + controllers: [HealthController], + providers: [NATSHealthIndicator], +}) +export class HealthModule extends ConfigurableModuleClass {} diff --git a/apps/shared/src/modules/health/indicators/nats.health.ts b/apps/shared/src/modules/health/indicators/nats.health.ts new file mode 100644 index 0000000000000000000000000000000000000000..b011cd8dab235c6b6ef381f78007ac543537f3a3 --- /dev/null +++ b/apps/shared/src/modules/health/indicators/nats.health.ts @@ -0,0 +1,31 @@ +import type { HealthIndicatorResult } from '@nestjs/terminus'; + +import { Inject, Injectable } from '@nestjs/common'; +import { HealthIndicator, HttpHealthIndicator } from '@nestjs/terminus'; + +import { NATS } from '../constants.js'; +import { + HealthModuleOptions, + MODULE_OPTIONS_TOKEN, +} from '../health.module-definition.js'; + +@Injectable() +export class NATSHealthIndicator extends HealthIndicator { + public constructor( + @Inject(MODULE_OPTIONS_TOKEN) + private readonly moduleOptions: HealthModuleOptions, + private readonly http: HttpHealthIndicator, + ) { + super(); + } + + public async isHealthy(): Promise<HealthIndicatorResult> { + if (this.moduleOptions.nats?.monitoringUrl) { + return this.http.pingCheck(NATS, this.moduleOptions.nats.monitoringUrl); + } + + return this.getStatus(NATS, true, { + message: 'NATS server monitoring URL is not provided. Skipping check.', + }); + } +}