部署优化
This commit is contained in:
@@ -17,6 +17,7 @@ import { OperationsModule } from './domains/operations/operations.module';
|
||||
import { AdminModule } from './applications/admin/admin.module';
|
||||
import { PlayerModule } from './applications/player/player.module';
|
||||
import { AgentPortalModule } from './applications/agent/agent-portal.module';
|
||||
import { HealthModule } from './applications/health/health.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -36,6 +37,7 @@ import { AgentPortalModule } from './applications/agent/agent-portal.module';
|
||||
AdminModule,
|
||||
PlayerModule,
|
||||
AgentPortalModule,
|
||||
HealthModule,
|
||||
],
|
||||
providers: [{ provide: APP_GUARD, useClass: JwtAuthGuard }],
|
||||
})
|
||||
|
||||
42
apps/api/src/applications/health/health.controller.spec.ts
Normal file
42
apps/api/src/applications/health/health.controller.spec.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { ServiceUnavailableException } from '@nestjs/common';
|
||||
import { HealthController } from './health.controller';
|
||||
|
||||
describe('HealthController', () => {
|
||||
function createController(options?: { database?: boolean; redis?: boolean }) {
|
||||
const database = options?.database ?? true;
|
||||
const redis = options?.redis ?? true;
|
||||
const prisma = {
|
||||
$queryRaw: jest.fn().mockImplementation(() => {
|
||||
if (!database) throw new Error('database unavailable');
|
||||
return Promise.resolve([{ ok: 1 }]);
|
||||
}),
|
||||
};
|
||||
const redisService = {
|
||||
raw: {
|
||||
ping: jest.fn().mockImplementation(() => {
|
||||
if (!redis) throw new Error('redis unavailable');
|
||||
return Promise.resolve('PONG');
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
return new HealthController(prisma as never, redisService as never);
|
||||
}
|
||||
|
||||
it('reports liveness without external checks', () => {
|
||||
expect(createController().live()).toEqual({ status: 'ok' });
|
||||
});
|
||||
|
||||
it('reports readiness when database and redis respond', async () => {
|
||||
await expect(createController().ready()).resolves.toEqual({
|
||||
status: 'ok',
|
||||
checks: { database: 'ok', redis: 'ok' },
|
||||
});
|
||||
});
|
||||
|
||||
it('fails readiness when a dependency is unavailable', async () => {
|
||||
await expect(createController({ redis: false }).ready()).rejects.toBeInstanceOf(
|
||||
ServiceUnavailableException,
|
||||
);
|
||||
});
|
||||
});
|
||||
60
apps/api/src/applications/health/health.controller.ts
Normal file
60
apps/api/src/applications/health/health.controller.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Controller, Get, ServiceUnavailableException } from '@nestjs/common';
|
||||
import { Public } from '../../shared/common/decorators';
|
||||
import { PrismaService } from '../../shared/prisma/prisma.service';
|
||||
import { RedisService } from '../../shared/redis/redis.service';
|
||||
|
||||
type CheckStatus = 'ok' | 'error';
|
||||
|
||||
@Controller('health')
|
||||
export class HealthController {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly redis: RedisService,
|
||||
) {}
|
||||
|
||||
@Public()
|
||||
@Get('live')
|
||||
live() {
|
||||
return { status: 'ok' as const };
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get('ready')
|
||||
async ready() {
|
||||
const checks: Record<'database' | 'redis', CheckStatus> = {
|
||||
database: 'ok',
|
||||
redis: 'ok',
|
||||
};
|
||||
|
||||
const [databaseReady, redisReady] = await Promise.all([
|
||||
this.checkDatabase(),
|
||||
this.checkRedis(),
|
||||
]);
|
||||
|
||||
if (!databaseReady) checks.database = 'error';
|
||||
if (!redisReady) checks.redis = 'error';
|
||||
|
||||
if (!databaseReady || !redisReady) {
|
||||
throw new ServiceUnavailableException({ status: 'error', checks });
|
||||
}
|
||||
|
||||
return { status: 'ok' as const, checks };
|
||||
}
|
||||
|
||||
private async checkDatabase(): Promise<boolean> {
|
||||
try {
|
||||
await this.prisma.$queryRaw`SELECT 1`;
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async checkRedis(): Promise<boolean> {
|
||||
try {
|
||||
return (await this.redis.raw.ping()) === 'PONG';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
7
apps/api/src/applications/health/health.module.ts
Normal file
7
apps/api/src/applications/health/health.module.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { HealthController } from './health.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [HealthController],
|
||||
})
|
||||
export class HealthModule {}
|
||||
Reference in New Issue
Block a user