refactor health module

This commit is contained in:
Philipinho
2024-07-05 18:59:26 +01:00
parent 9496ec9b57
commit 35dcd5f254
9 changed files with 261 additions and 33 deletions

View File

@ -43,6 +43,7 @@
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-fastify": "^10.3.9",
"@nestjs/platform-socket.io": "^10.3.9",
"@nestjs/terminus": "^10.2.3",
"@nestjs/websockets": "^10.3.9",
"@react-email/components": "0.0.19",
"@react-email/render": "^0.0.15",

View File

@ -16,7 +16,15 @@ export class TransformHttpResponseInterceptor<T>
intercept(
context: ExecutionContext,
next: CallHandler<T>,
): Observable<Response<T>> {
): Observable<Response<T> | any> {
const request = context.switchToHttp().getRequest();
const path = request.url;
// Skip interceptor for the /api/health path
if (path === '/api/health') {
return next.handle();
}
return next.handle().pipe(
map((data) => {
const status = context.switchToHttp().getResponse().statusCode;

View File

@ -34,7 +34,10 @@ export class CoreModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(DomainMiddleware)
.exclude({ path: 'auth/setup', method: RequestMethod.POST })
.exclude(
{ path: 'auth/setup', method: RequestMethod.POST },
{ path: 'health', method: RequestMethod.GET },
)
.forRoutes('*');
}
}

View File

@ -1,37 +1,22 @@
import { KyselyDB } from '@docmost/db/types/kysely.types';
import {
Controller,
Get,
HttpCode,
HttpStatus,
InternalServerErrorException,
Logger,
} from '@nestjs/common';
import { sql } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { Redis } from 'ioredis';
import { EnvironmentService } from '../environment/environment.service';
import { Controller, Get } from '@nestjs/common';
import { HealthCheck, HealthCheckService } from '@nestjs/terminus';
import { PostgresHealthIndicator } from './postgres.health';
import { RedisHealthIndicator } from './redis.health';
@Controller()
@Controller('health')
export class HealthController {
constructor(
@InjectKysely() private readonly db: KyselyDB,
private environmentService: EnvironmentService,
private health: HealthCheckService,
private postgres: PostgresHealthIndicator,
private redis: RedisHealthIndicator,
) {}
private readonly logger = new Logger(HealthController.name);
@Get('health')
@HttpCode(HttpStatus.OK)
async health() {
try {
const redis = new Redis(this.environmentService.getRedisUrl());
await sql`SELECT 1=1`.execute(this.db);
await redis.ping();
} catch (error) {
this.logger.error('Health check failed');
throw new InternalServerErrorException();
}
@Get()
@HealthCheck()
async check() {
return this.health.check([
() => this.postgres.pingCheck('database'),
() => this.redis.pingCheck('redis'),
]);
}
}

View File

@ -1,8 +1,13 @@
import { Global, Module } from '@nestjs/common';
import { HealthController } from './health.controller';
import { TerminusModule } from '@nestjs/terminus';
import { PostgresHealthIndicator } from './postgres.health';
import { RedisHealthIndicator } from './redis.health';
@Global()
@Module({
controllers: [HealthController],
providers: [PostgresHealthIndicator, RedisHealthIndicator],
imports: [TerminusModule],
})
export class HealthModule {}

View File

@ -0,0 +1,38 @@
import { InjectKysely } from 'nestjs-kysely';
import {
HealthCheckError,
HealthIndicator,
HealthIndicatorResult,
} from '@nestjs/terminus';
import { Injectable, Logger } from '@nestjs/common';
import { sql } from 'kysely';
import { KyselyDB } from '@docmost/db/types/kysely.types';
@Injectable()
export class PostgresHealthIndicator extends HealthIndicator {
private readonly logger = new Logger(PostgresHealthIndicator.name);
constructor(@InjectKysely() private readonly db: KyselyDB) {
super();
}
async pingCheck(key: string): Promise<HealthIndicatorResult> {
let isHealthy = false;
try {
await sql`SELECT 1=1`.execute(this.db);
isHealthy = true;
} catch (e) {
this.logger.error(JSON.stringify(e));
}
if (isHealthy) {
return this.getStatus(key, isHealthy);
} else {
throw new HealthCheckError(
`${key} is not available`,
this.getStatus(key, isHealthy),
);
}
}
}

View File

@ -0,0 +1,41 @@
import {
HealthCheckError,
HealthIndicator,
HealthIndicatorResult,
} from '@nestjs/terminus';
import { Injectable, Logger } from '@nestjs/common';
import { EnvironmentService } from '../environment/environment.service';
import { Redis } from 'ioredis';
@Injectable()
export class RedisHealthIndicator extends HealthIndicator {
private readonly logger = new Logger(RedisHealthIndicator.name);
constructor(private environmentService: EnvironmentService) {
super();
}
async pingCheck(key: string): Promise<HealthIndicatorResult> {
let isHealthy = false;
try {
const redis = new Redis(this.environmentService.getRedisUrl(), {
maxRetriesPerRequest: 15,
});
await redis.ping();
isHealthy = true;
} catch (e) {
this.logger.error(e);
}
if (isHealthy) {
return this.getStatus(key, isHealthy);
} else {
throw new HealthCheckError(
`${key} is not available`,
this.getStatus(key, isHealthy),
);
}
}
}

View File

@ -38,7 +38,8 @@ async function bootstrap() {
.addHook('preHandler', function (req, reply, done) {
if (
req.originalUrl.startsWith('/api') &&
!req.originalUrl.startsWith('/api/auth/setup')
!req.originalUrl.startsWith('/api/auth/setup') &&
!req.originalUrl.startsWith('/api/health')
) {
if (!req.raw?.['workspaceId']) {
throw new NotFoundException('Workspace not found');