diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index b9f00bd..0e0001c 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -19,6 +19,7 @@ import { GenomeModule } from './genome/genome.module'; import { MptModule } from './mpt/mpt.module'; import { DashboardModule } from './dashboard/dashboard.module'; import { GeneModule } from './gene/gene.module'; +import { SystemModule } from './system/system.module'; @Module({ imports: [ @@ -61,6 +62,7 @@ import { GeneModule } from './gene/gene.module'; // 기타 HelpModule, + SystemModule, ], controllers: [AppController], providers: [AppService, JwtStrategy], diff --git a/backend/src/system/system.controller.ts b/backend/src/system/system.controller.ts new file mode 100644 index 0000000..acf360c --- /dev/null +++ b/backend/src/system/system.controller.ts @@ -0,0 +1,14 @@ +import { Controller, Get } from '@nestjs/common'; +import { SystemService, SystemHealthResponse } from './system.service'; +import { Public } from '../common/decorators/public.decorator'; + +@Controller('system') +export class SystemController { + constructor(private readonly systemService: SystemService) {} + + @Public() + @Get('health') + async getHealth(): Promise { + return this.systemService.getHealth(); + } +} diff --git a/backend/src/system/system.module.ts b/backend/src/system/system.module.ts new file mode 100644 index 0000000..e10e1ca --- /dev/null +++ b/backend/src/system/system.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { SystemController } from './system.controller'; +import { SystemService } from './system.service'; + +@Module({ + controllers: [SystemController], + providers: [SystemService], +}) +export class SystemModule {} diff --git a/backend/src/system/system.service.ts b/backend/src/system/system.service.ts new file mode 100644 index 0000000..f0be4c0 --- /dev/null +++ b/backend/src/system/system.service.ts @@ -0,0 +1,89 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { InjectDataSource } from '@nestjs/typeorm'; +import { DataSource } from 'typeorm'; +import { InjectRedis } from '@nestjs-modules/ioredis'; +import Redis from 'ioredis'; + +export interface SystemHealthResponse { + status: 'ok' | 'error'; + timestamp: string; + environment: string; + database: { + host: string; + port: number; + database: string; + user: string; + status: 'connected' | 'disconnected'; + error?: string; + }; + redis: { + host: string; + port: number; + status: 'connected' | 'disconnected'; + error?: string; + }; +} + +@Injectable() +export class SystemService { + constructor( + private configService: ConfigService, + @InjectDataSource() private dataSource: DataSource, + @InjectRedis() private redis: Redis, + ) {} + + async getHealth(): Promise { + const dbHealth = await this.checkDatabase(); + const redisHealth = await this.checkRedis(); + + return { + status: dbHealth.status === 'connected' && redisHealth.status === 'connected' ? 'ok' : 'error', + timestamp: new Date().toISOString(), + environment: this.configService.get('NODE_ENV') || 'development', + database: dbHealth, + redis: redisHealth, + }; + } + + private async checkDatabase() { + const config = { + host: this.configService.get('POSTGRES_HOST') || 'unknown', + port: parseInt(this.configService.get('POSTGRES_PORT')) || 5432, + database: this.configService.get('POSTGRES_DB') || 'unknown', + user: this.configService.get('POSTGRES_USER') || 'unknown', + }; + + try { + await this.dataSource.query('SELECT 1'); + return { ...config, status: 'connected' as const }; + } catch (error) { + return { ...config, status: 'disconnected' as const, error: error.message }; + } + } + + private async checkRedis() { + const redisUrl = this.configService.get('REDIS_URL') || ''; + let host = this.configService.get('REDIS_HOST') || 'unknown'; + let port = parseInt(this.configService.get('REDIS_PORT')) || 6379; + + if (redisUrl) { + try { + const url = new URL(redisUrl); + host = url.hostname; + port = parseInt(url.port) || 6379; + } catch {} + } + + try { + const pong = await this.redis.ping(); + return { + host, + port, + status: pong === 'PONG' ? 'connected' as const : 'disconnected' as const, + }; + } catch (error) { + return { host, port, status: 'disconnected' as const, error: error.message }; + } + } +}