Skip to content

Commit 0ea5743

Browse files
Merge branch 'main' into feat/issues
2 parents f1e4101 + 44e0144 commit 0ea5743

8 files changed

Lines changed: 994 additions & 11 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { SetMetadata } from '@nestjs/common';
2+
3+
export const IS_PUBLIC_KEY = 'isPublic';
4+
5+
export const Public = () =>
6+
SetMetadata(IS_PUBLIC_KEY, true);
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
2+
3+
4+
export class healthController {
5+
constructor(private readonly healthService: HealthService) {}
6+
7+
@Get()
8+
getHealth(): string {
9+
return this.healthService.getHealthStatus();
10+
}
11+
}
12+
13+
14+
import {
15+
Controller,
16+
Get,
17+
} from '@nestjs/common';
18+
19+
import {
20+
HealthCheck,
21+
HealthCheckService,
22+
TypeOrmHealthIndicator,
23+
DiskHealthIndicator,
24+
} from '@nestjs/terminus';
25+
26+
import { RedisHealthIndicator } from './indicators/redis.health';
27+
import { Public } from '../../auth/decorators/public.decorator';
28+
29+
@Controller('health')
30+
export class HealthController {
31+
constructor(
32+
private readonly health: HealthCheckService,
33+
private readonly db: TypeOrmHealthIndicator,
34+
private readonly disk: DiskHealthIndicator,
35+
private readonly redis: RedisHealthIndicator,
36+
) {}
37+
38+
@Get()
39+
@Public()
40+
@HealthCheck()
41+
check() {
42+
return this.health.check([
43+
() => this.db.pingCheck('postgres'),
44+
45+
() =>
46+
this.redis.isHealthy('redis'),
47+
48+
() =>
49+
this.disk.checkStorage('storage', {
50+
path: '/',
51+
thresholdPercent: 0.9,
52+
}),
53+
]);
54+
}
55+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Module } from '@nestjs/common';
2+
import { TerminusModule } from '@nestjs/terminus';
3+
4+
import { HealthController } from './health.controller';
5+
import { RedisHealthIndicator } from './indicators/redis.health';
6+
7+
@Module({
8+
imports: [TerminusModule],
9+
controllers: [HealthController],
10+
providers: [RedisHealthIndicator],
11+
})
12+
export class HealthModule {}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
2+
3+
@Injectable()
4+
export class HealthService {
5+
getHealthStatus(): string {
6+
return 'OK';
7+
}
8+
}
9+
10+
11+
import {
12+
Injectable,
13+
} from '@nestjs/common';
14+
15+
import {
16+
HealthIndicator,
17+
HealthIndicatorResult,
18+
HealthCheckError,
19+
} from '@nestjs/terminus';
20+
21+
import Redis from 'ioredis';
22+
23+
@Injectable()
24+
export class RedisHealthIndicator extends HealthIndicator {
25+
private readonly redis: Redis;
26+
27+
constructor() {
28+
super();
29+
30+
this.redis = new Redis({
31+
host: process.env.REDIS_HOST,
32+
port: Number(process.env.REDIS_PORT),
33+
password: process.env.REDIS_PASSWORD,
34+
});
35+
}
36+
37+
async isHealthy(
38+
key: string,
39+
): Promise<HealthIndicatorResult> {
40+
try {
41+
const response = await this.redis.ping();
42+
43+
const result = this.getStatus(
44+
key,
45+
response === 'PONG',
46+
);
47+
48+
if (response !== 'PONG') {
49+
throw new HealthCheckError(
50+
'Redis check failed',
51+
result,
52+
);
53+
}
54+
55+
return result;
56+
} catch (error) {
57+
throw new HealthCheckError(
58+
'Redis unavailable',
59+
this.getStatus(key, false),
60+
);
61+
}
62+
}
63+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Module } from '@nestjs/common';
2+
import { APP_GUARD } from '@nestjs/core';
3+
import { ConfigModule, ConfigService } from '@nestjs/config';
4+
import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';
5+
6+
@Module({
7+
imports: [
8+
ConfigModule,
9+
10+
ThrottlerModule.forRootAsync({
11+
inject: [ConfigService],
12+
useFactory: (configService: ConfigService) => ({
13+
throttlers: [
14+
{
15+
ttl: Number(configService.get('THROTTLE_TTL', 60)) * 1000,
16+
limit: Number(configService.get('THROTTLE_LIMIT', 100)),
17+
},
18+
],
19+
}),
20+
}),
21+
],
22+
23+
providers: [
24+
{
25+
provide: APP_GUARD,
26+
useClass: ThrottlerGuard,
27+
},
28+
],
29+
})
30+
export class OpsceModule {}

backend/src/opsce/opsce.module.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ import { LocationsModule } from './locations/locations.module';
77
import { AuthModule } from './auth/auth.module';
88
import { UploadsModule } from './uploads/uploads.module';
99

10+
/**
11+
* OpsceModule
12+
*
13+
* Aggregates all operational sub-modules: users, locations, audit, and
14+
* departments. ConfigModule is already global (registered in AppModule), so
15+
* every sub-module can inject ConfigService without re-importing it here.
16+
*/
1017
@Module({
1118
imports: [
1219
AuthModule,
@@ -27,4 +34,4 @@ import { UploadsModule } from './uploads/uploads.module';
2734
UploadsModule,
2835
],
2936
})
30-
export class OpsceModule {}
37+
export class OpsceModule {}

0 commit comments

Comments
 (0)