Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { buildWinstonOptions } from './common/logger.config';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { DocumentsModule } from './documents/documents.module';
import { MailModule } from './mail/mail.module';
import { HealthModule } from './module/health/health.module';
import { QueueModule } from './queue/queue.module';
import { RiskAssessmentModule } from './risk-assessment/risk-assessment.module';
import { StellarModule } from './stellar/stellar.module';
Expand Down Expand Up @@ -51,6 +52,7 @@ import { ConfigValidationSchema } from './config/config.validation';
UsersModule,
AuthModule,
DocumentsModule,
HealthModule,
RiskAssessmentModule,
StellarModule,
VerificationModule,
Expand Down
29 changes: 29 additions & 0 deletions backend/src/module/health/health.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Controller, Get } from '@nestjs/common';
import {
HealthCheck,
HealthCheckService,
TypeOrmHealthIndicator,
} from '@nestjs/terminus';

import { DiskHealthIndicator } from './indicators/disk-health.indicator';
import { RedisHealthIndicator } from './indicators/redis-health.indicator';

@Controller('module/health')
export class HealthController {
constructor(
private readonly health: HealthCheckService,
private readonly typeOrm: TypeOrmHealthIndicator,
private readonly redis: RedisHealthIndicator,
private readonly disk: DiskHealthIndicator,
) {}

@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.typeOrm.pingCheck('postgres', { timeout: 1500 }),
() => this.redis.checkRedis('redis'),
() => this.disk.checkDiskSpace('disk'),
]);
}
}
14 changes: 14 additions & 0 deletions backend/src/module/health/health.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus';

import { QueueModule } from '../../queue/queue.module';
import { HealthController } from './health.controller';
import { DiskHealthIndicator } from './indicators/disk-health.indicator';
import { RedisHealthIndicator } from './indicators/redis-health.indicator';

@Module({
imports: [TerminusModule, QueueModule],
controllers: [HealthController],
providers: [DiskHealthIndicator, RedisHealthIndicator],
})
export class HealthModule {}
31 changes: 31 additions & 0 deletions backend/src/module/health/indicators/disk-health.indicator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Injectable } from '@nestjs/common';
import { HealthIndicatorService } from '@nestjs/terminus';
import checkDiskSpace from 'check-disk-space';

@Injectable()
export class DiskHealthIndicator {
constructor(
private readonly healthIndicatorService: HealthIndicatorService,
) {}

async checkDiskSpace(key: string) {
const indicator = this.healthIndicatorService.check(key);
const path = process.platform === 'win32' ? 'C:\\' : '/';
const diskSpace = await checkDiskSpace(path);
const freeMb = Math.floor(diskSpace.free / 1024 / 1024);

if (freeMb < 500) {
return indicator.down({
path,
freeMb,
requiredFreeMb: 500,
});
}

return indicator.up({
path,
freeMb,
requiredFreeMb: 500,
});
}
}
63 changes: 63 additions & 0 deletions backend/src/module/health/indicators/redis-health.indicator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Injectable } from '@nestjs/common';
import { HealthIndicatorService } from '@nestjs/terminus';
import { createClient } from 'redis';

import { QueueService } from '../../../queue/queue.service';

@Injectable()
export class RedisHealthIndicator {
constructor(
private readonly healthIndicatorService: HealthIndicatorService,
private readonly queueService: QueueService,
) {}

async checkRedis(key: string) {
const indicator = this.healthIndicatorService.check(key);
const connection = this.queueService.getConnectionOptions();
const redisConnection = connection as {
host?: string;
port?: number;
password?: string;
};
const host = redisConnection.host || '127.0.0.1';
const port = redisConnection.port || 6379;
const client = createClient({
socket: {
host,
port,
},
password: redisConnection.password,
});

try {
await client.connect();
const pong = await client.ping();

if (pong !== 'PONG') {
return indicator.down({
host,
port,
message: `Unexpected ping response: ${pong}`,
});
}

return indicator.up({
host,
port,
});
} catch (error) {
return indicator.down({
host,
port,
message:
error instanceof Error ? error.message : 'Redis health check failed',
});
} finally {
if (client.isOpen) {
await client.quit().catch(() => client.disconnect());
} else {
client.disconnect();
}
}
}
}
Loading