diff --git a/backend/src/opsce/health/decorator/public-decorator.ts b/backend/src/opsce/health/decorator/public-decorator.ts new file mode 100644 index 00000000..30a5a2e6 --- /dev/null +++ b/backend/src/opsce/health/decorator/public-decorator.ts @@ -0,0 +1,6 @@ +import { SetMetadata } from '@nestjs/common'; + +export const IS_PUBLIC_KEY = 'isPublic'; + +export const Public = () => + SetMetadata(IS_PUBLIC_KEY, true); \ No newline at end of file diff --git a/backend/src/opsce/health/healthController.ts b/backend/src/opsce/health/healthController.ts new file mode 100644 index 00000000..db8c2b8a --- /dev/null +++ b/backend/src/opsce/health/healthController.ts @@ -0,0 +1,55 @@ + + + +export class healthController { + constructor(private readonly healthService: HealthService) {} + + @Get() + getHealth(): string { + return this.healthService.getHealthStatus(); + } +} + + +import { + Controller, + Get, +} from '@nestjs/common'; + +import { + HealthCheck, + HealthCheckService, + TypeOrmHealthIndicator, + DiskHealthIndicator, +} from '@nestjs/terminus'; + +import { RedisHealthIndicator } from './indicators/redis.health'; +import { Public } from '../../auth/decorators/public.decorator'; + +@Controller('health') +export class HealthController { + constructor( + private readonly health: HealthCheckService, + private readonly db: TypeOrmHealthIndicator, + private readonly disk: DiskHealthIndicator, + private readonly redis: RedisHealthIndicator, + ) {} + + @Get() + @Public() + @HealthCheck() + check() { + return this.health.check([ + () => this.db.pingCheck('postgres'), + + () => + this.redis.isHealthy('redis'), + + () => + this.disk.checkStorage('storage', { + path: '/', + thresholdPercent: 0.9, + }), + ]); + } +} \ No newline at end of file diff --git a/backend/src/opsce/health/healthModule.ts b/backend/src/opsce/health/healthModule.ts new file mode 100644 index 00000000..d4b1f808 --- /dev/null +++ b/backend/src/opsce/health/healthModule.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TerminusModule } from '@nestjs/terminus'; + +import { HealthController } from './health.controller'; +import { RedisHealthIndicator } from './indicators/redis.health'; + +@Module({ + imports: [TerminusModule], + controllers: [HealthController], + providers: [RedisHealthIndicator], +}) +export class HealthModule {} \ No newline at end of file diff --git a/backend/src/opsce/health/healthSerivce.ts b/backend/src/opsce/health/healthSerivce.ts new file mode 100644 index 00000000..5c99b3fa --- /dev/null +++ b/backend/src/opsce/health/healthSerivce.ts @@ -0,0 +1,63 @@ + + +@Injectable() +export class HealthService { + getHealthStatus(): string { + return 'OK'; + } +} + + +import { + Injectable, +} from '@nestjs/common'; + +import { + HealthIndicator, + HealthIndicatorResult, + HealthCheckError, +} from '@nestjs/terminus'; + +import Redis from 'ioredis'; + +@Injectable() +export class RedisHealthIndicator extends HealthIndicator { + private readonly redis: Redis; + + constructor() { + super(); + + this.redis = new Redis({ + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + password: process.env.REDIS_PASSWORD, + }); + } + + async isHealthy( + key: string, + ): Promise { + try { + const response = await this.redis.ping(); + + const result = this.getStatus( + key, + response === 'PONG', + ); + + if (response !== 'PONG') { + throw new HealthCheckError( + 'Redis check failed', + result, + ); + } + + return result; + } catch (error) { + throw new HealthCheckError( + 'Redis unavailable', + this.getStatus(key, false), + ); + } + } +} \ No newline at end of file