Basic Architecture of Sport Web

This commit is contained in:
Nông Đức Huy
2026-08-11 13:37:25 +07:00
commit 8032fff6ac
262 changed files with 20348 additions and 0 deletions
@@ -0,0 +1,34 @@
import { Controller, Get, HttpCode, HttpStatus } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { Public } from '@/common/decorators/public.decorator';
import { HealthService, type HealthCheckResult } from './health.service';
@ApiTags('health')
@Controller('health')
export class HealthController {
constructor(private readonly healthService: HealthService) {}
/**
* Liveness. Answers "is the process running?" and nothing else — it must not
* touch the database, or a brief DB blip would make the orchestrator kill
* otherwise-healthy containers.
*/
@Public()
@Get('live')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Liveness probe' })
live(): { status: 'ok' } {
return { status: 'ok' };
}
/** Readiness / deep check: should this instance receive traffic? */
@Public()
@Get()
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Readiness probe with dependency status' })
check(): Promise<HealthCheckResult> {
return this.healthService.check();
}
}
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { HealthController } from './health.controller';
import { HealthService } from './health.service';
@Module({
controllers: [HealthController],
providers: [HealthService],
})
export class HealthModule {}
@@ -0,0 +1,67 @@
import { Inject, Injectable } from '@nestjs/common';
import { APP_CONFIG } from '@/config/app-config.module';
import type { AppConfig } from '@/config/configuration';
import { PrismaService } from '@/infrastructure/prisma/prisma.service';
import { RedisService } from '@/infrastructure/redis/redis.service';
export interface DependencyStatus {
status: 'up' | 'down';
latencyMs: number | null;
error?: string;
}
export interface HealthCheckResult {
status: 'ok' | 'degraded';
uptimeSeconds: number;
version: string;
environment: string;
dependencies: {
database: DependencyStatus;
redis: DependencyStatus;
};
}
@Injectable()
export class HealthService {
constructor(
@Inject(APP_CONFIG) private readonly config: AppConfig,
private readonly prisma: PrismaService,
private readonly redis: RedisService,
) {}
/**
* Deep check — used by dashboards and by `docker compose` dependency gates.
* Dependencies are probed in parallel so a slow one cannot mask another.
*/
async check(): Promise<HealthCheckResult> {
const [database, redis] = await Promise.all([
probe(() => this.prisma.ping()),
probe(() => this.redis.ping()),
]);
const healthy = database.status === 'up' && redis.status === 'up';
return {
status: healthy ? 'ok' : 'degraded',
uptimeSeconds: Math.round(process.uptime()),
version: this.config.app.version,
environment: this.config.app.env,
dependencies: { database, redis },
};
}
}
async function probe(fn: () => Promise<unknown>): Promise<DependencyStatus> {
const startedAt = performance.now();
try {
await fn();
return { status: 'up', latencyMs: Math.round(performance.now() - startedAt) };
} catch (error) {
return {
status: 'down',
latencyMs: null,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}