diff --git a/backend/src/monitoring/health.service.ts b/backend/src/monitoring/health.service.ts index f531b10..e4f57d8 100644 --- a/backend/src/monitoring/health.service.ts +++ b/backend/src/monitoring/health.service.ts @@ -1,5 +1,7 @@ -import { Injectable, Optional } from '@nestjs/common'; +import { Inject, Injectable, Optional } from '@nestjs/common'; +import type { Redis } from 'ioredis'; import { DatabaseService } from '../common/database/database.service'; +import { REDIS_CLIENT } from '../common/redis/redis.module'; export interface HealthStatus { status: 'ok' | 'degraded' | 'down'; @@ -11,13 +13,17 @@ export interface HealthStatus { export class HealthService { private readonly startTime = Date.now(); - constructor(@Optional() private readonly database?: DatabaseService) {} + constructor( + @Optional() private readonly database?: DatabaseService, + @Optional() @Inject(REDIS_CLIENT) private readonly redis?: Redis | null, + ) {} async check(): Promise { const checks: Record = { api: true, stellar: await this.checkStellar(), database: await this.checkDatabase(), + redis: await this.checkRedis(), memory: process.memoryUsage().heapUsed < 500 * 1024 * 1024, }; const failing = Object.values(checks).filter(v => !v).length; @@ -28,6 +34,22 @@ export class HealthService { }; } + /** + * Redis is required infrastructure for rate limiting, idempotency keys, and + * the outbox — closes #219. An unconfigured REDIS_URL still reports healthy + * (this environment doesn't require Redis), but once configured a failed + * ping counts against overall health instead of being silently invisible. + */ + private async checkRedis(): Promise { + if (!this.redis) return true; + try { + const pong = await this.redis.ping(); + return pong === 'PONG'; + } catch { + return false; + } + } + /** * PostgreSQL is currently optional infrastructure — not yet a hard dependency of any * service — so an unconfigured pool reports healthy (true) rather than degraded. Once