diff --git a/package.json b/package.json index 98ed39ff6..709cf170c 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,8 @@ "dependencies": { "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-decorators": "^7.17.8", + "@nestjs/axios": "^3.0.2", + "@nestjs/terminus": "^10.2.2", "@nestjs/throttler": "^5.0.1", "cross-env": "^7.0.3", "fuse.js": "^7.0.0", diff --git a/packages/api/src/meta/meta.controller.ts b/packages/api/src/meta/meta.controller.ts index 20fec45e3..040c89e63 100644 --- a/packages/api/src/meta/meta.controller.ts +++ b/packages/api/src/meta/meta.controller.ts @@ -1,13 +1,35 @@ import { Controller, Get } from "@nestjs/common"; +import { + HealthCheckService, + HttpHealthIndicator, + HealthCheck, + TypeOrmHealthIndicator, + HealthCheckResult, +} from "@nestjs/terminus"; import { MetaService } from "./meta.service"; import { type MetaInfo } from "@graduate/common"; @Controller("meta") export class MetaController { - constructor(private readonly metaService: MetaService) {} + constructor( + private readonly metaService: MetaService, + private health: HealthCheckService, + private http: HttpHealthIndicator, + private db: TypeOrmHealthIndicator + ) {} @Get("/info") getMetaInfo(): MetaInfo { return this.metaService.getMetaInfo(); } + + @Get("/health") + @HealthCheck() + async getHealthInfo(): Promise { + return await this.metaService.getHealthInfo( + this.health, + this.http, + this.db + ); + } } diff --git a/packages/api/src/meta/meta.module.ts b/packages/api/src/meta/meta.module.ts index 5d0433160..c17d02bf0 100644 --- a/packages/api/src/meta/meta.module.ts +++ b/packages/api/src/meta/meta.module.ts @@ -1,10 +1,13 @@ import { Module } from "@nestjs/common"; +import { TerminusModule } from "@nestjs/terminus"; +import { HttpModule } from "@nestjs/axios"; import { MetaService } from "./meta.service"; import { MetaController } from "./meta.controller"; @Module({ controllers: [MetaController], providers: [MetaService], + imports: [HttpModule, TerminusModule], exports: [MetaService], }) export class MetaModule {} diff --git a/packages/api/src/meta/meta.service.ts b/packages/api/src/meta/meta.service.ts index b1b0bfe90..6b4559932 100644 --- a/packages/api/src/meta/meta.service.ts +++ b/packages/api/src/meta/meta.service.ts @@ -1,5 +1,11 @@ import { type MetaInfo } from "@graduate/common"; import { Injectable } from "@nestjs/common"; +import { + HealthCheckService, + HttpHealthIndicator, + TypeOrmHealthIndicator, + HealthCheckResult, +} from "@nestjs/terminus"; @Injectable() export class MetaService { @@ -14,4 +20,16 @@ export class MetaService { environment: process.env.NODE_ENV ?? false, }; } + + getHealthInfo( + health: HealthCheckService, + http: HttpHealthIndicator, + db: TypeOrmHealthIndicator + ): Promise { + return health.check([ + () => + http.pingCheck("graduate_api", "https://graduatenu.com/api/meta/info"), + () => db.pingCheck("database"), + ]); + } } diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 2e3ddfb78..fa38d2863 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -492,3 +492,30 @@ export interface MetaInfo { build_timestamp: Maybe; environment: Maybe; } + +export interface HealthCheckResponse { + status: string; + info: OverallHealthInfo; + error: OverallHealthDetails; + details: OverallHealthDetails; +} + +export interface OverallHealthInfo { + graduate_api?: IndividualServiceHealthInfo; + database?: IndividualServiceHealthInfo; +} + +export interface IndividualServiceHealthInfo { + status: string; +} +export interface OverallHealthDetails { + graduate_api?: IndividualServiceHealthDetails; + database?: IndividualServiceHealthDetails; +} + +export interface IndividualServiceHealthDetails { + status: string; + message?: string; + statusCode?: number; + statusText?: string; +} diff --git a/yarn.lock b/yarn.lock index b3156e7a1..7f684c002 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3610,6 +3610,17 @@ __metadata: languageName: node linkType: hard +"@nestjs/axios@npm:^3.0.2": + version: 3.0.2 + resolution: "@nestjs/axios@npm:3.0.2" + peerDependencies: + "@nestjs/common": ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + axios: ^1.3.1 + rxjs: ^6.0.0 || ^7.0.0 + checksum: 285a735fb5db602b63aa4a37e161f609b2cec05b69f4bffe983617c2136ac29c0a33bb96e6276d22a656907bed5d53460e740310bc05c043dcd39c37db7cda29 + languageName: node + linkType: hard + "@nestjs/cli@npm:^8.0.0": version: 8.2.8 resolution: "@nestjs/cli@npm:8.2.8" @@ -3763,6 +3774,61 @@ __metadata: languageName: node linkType: hard +"@nestjs/terminus@npm:^10.2.2": + version: 10.2.2 + resolution: "@nestjs/terminus@npm:10.2.2" + dependencies: + boxen: 5.1.2 + check-disk-space: 3.4.0 + peerDependencies: + "@grpc/grpc-js": "*" + "@grpc/proto-loader": "*" + "@mikro-orm/core": "*" + "@mikro-orm/nestjs": "*" + "@nestjs/axios": ^1.0.0 || ^2.0.0 || ^3.0.0 + "@nestjs/common": ^9.0.0 || ^10.0.0 + "@nestjs/core": ^9.0.0 || ^10.0.0 + "@nestjs/microservices": ^9.0.0 || ^10.0.0 + "@nestjs/mongoose": ^9.0.0 || ^10.0.0 + "@nestjs/sequelize": ^9.0.0 || ^10.0.0 + "@nestjs/typeorm": ^9.0.0 || ^10.0.0 + "@prisma/client": "*" + mongoose: "*" + reflect-metadata: 0.1.x || 0.2.x + rxjs: 7.x + sequelize: "*" + typeorm: "*" + peerDependenciesMeta: + "@grpc/grpc-js": + optional: true + "@grpc/proto-loader": + optional: true + "@mikro-orm/core": + optional: true + "@mikro-orm/nestjs": + optional: true + "@nestjs/axios": + optional: true + "@nestjs/microservices": + optional: true + "@nestjs/mongoose": + optional: true + "@nestjs/sequelize": + optional: true + "@nestjs/typeorm": + optional: true + "@prisma/client": + optional: true + mongoose: + optional: true + sequelize: + optional: true + typeorm: + optional: true + checksum: 79693dac80dbbc7552b4517de4b0126708dff11dc75d079653742a72abe43b715bdeed559696b541589c52030546fee9a73c4dbc85662f827431838be9641590 + languageName: node + linkType: hard + "@nestjs/testing@npm:^9.3.12": version: 9.4.3 resolution: "@nestjs/testing@npm:9.4.3" @@ -5095,6 +5161,15 @@ __metadata: languageName: node linkType: hard +"ansi-align@npm:^3.0.0": + version: 3.0.1 + resolution: "ansi-align@npm:3.0.1" + dependencies: + string-width: ^4.1.0 + checksum: 6abfa08f2141d231c257162b15292467081fa49a208593e055c866aa0455b57f3a86b5a678c190c618faa79b4c59e254493099cb700dd9cf2293c6be2c8f5d8d + languageName: node + linkType: hard + "ansi-colors@npm:4.1.1": version: 4.1.1 resolution: "ansi-colors@npm:4.1.1" @@ -5616,6 +5691,22 @@ __metadata: languageName: node linkType: hard +"boxen@npm:5.1.2": + version: 5.1.2 + resolution: "boxen@npm:5.1.2" + dependencies: + ansi-align: ^3.0.0 + camelcase: ^6.2.0 + chalk: ^4.1.0 + cli-boxes: ^2.2.1 + string-width: ^4.2.2 + type-fest: ^0.20.2 + widest-line: ^3.1.0 + wrap-ansi: ^7.0.0 + checksum: 82d03e42a72576ff235123f17b7c505372fe05c83f75f61e7d4fa4bcb393897ec95ce766fecb8f26b915f0f7a7227d66e5ec7cef43f5b2bd9d3aeed47ec55877 + languageName: node + linkType: hard + "brace-expansion@npm:^1.1.7": version: 1.1.11 resolution: "brace-expansion@npm:1.1.11" @@ -5870,6 +5961,13 @@ __metadata: languageName: node linkType: hard +"check-disk-space@npm:3.4.0": + version: 3.4.0 + resolution: "check-disk-space@npm:3.4.0" + checksum: 0154c149c34a1233fe542f2a9f9ea2526bb90169489259eda8de1cf6870f765e8f5299c3b085085cc1ef33d9559f589fd10609c7e52d2e624ef6b5720dd9743c + languageName: node + linkType: hard + "chokidar@npm:3.5.3, chokidar@npm:^3.4.0, chokidar@npm:^3.5.3": version: 3.5.3 resolution: "chokidar@npm:3.5.3" @@ -5948,6 +6046,13 @@ __metadata: languageName: node linkType: hard +"cli-boxes@npm:^2.2.1": + version: 2.2.1 + resolution: "cli-boxes@npm:2.2.1" + checksum: be79f8ec23a558b49e01311b39a1ea01243ecee30539c880cf14bf518a12e223ef40c57ead0cb44f509bffdffc5c129c746cd50d863ab879385370112af4f585 + languageName: node + linkType: hard + "cli-cursor@npm:^3.1.0": version: 3.1.0 resolution: "cli-cursor@npm:3.1.0" @@ -8057,6 +8162,8 @@ __metadata: "@babel/plugin-proposal-decorators": ^7.17.8 "@babel/preset-env": ^7.16.11 "@babel/preset-typescript": ^7.16.7 + "@nestjs/axios": ^3.0.2 + "@nestjs/terminus": ^10.2.2 "@nestjs/throttler": ^5.0.1 "@types/nodemailer": ^6.4.7 "@typescript-eslint/eslint-plugin": ^5.15.0 @@ -12481,7 +12588,7 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": +"string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.0.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.2, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -13770,6 +13877,15 @@ __metadata: languageName: node linkType: hard +"widest-line@npm:^3.1.0": + version: 3.1.0 + resolution: "widest-line@npm:3.1.0" + dependencies: + string-width: ^4.0.0 + checksum: 03db6c9d0af9329c37d74378ff1d91972b12553c7d72a6f4e8525fe61563fa7adb0b9d6e8d546b7e059688712ea874edd5ded475999abdeedf708de9849310e0 + languageName: node + linkType: hard + "windows-release@npm:^4.0.0": version: 4.0.0 resolution: "windows-release@npm:4.0.0"