Skip to content

Commit 1b3d47f

Browse files
committed
added healthcheck service and endpoint, updated e2e with healthcheck test
1 parent 5523792 commit 1b3d47f

File tree

4 files changed

+161
-8
lines changed

4 files changed

+161
-8
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { FastifyInstance } from "fastify";
2+
import Healthcheck, { SystemReport } from "../shared/utils/common/Healthcheck";
3+
import { Handler } from "./Handler";
4+
import { isException } from "../shared/utils/guards/ExceptionGuard";
5+
6+
export class CommonHandler extends Handler<Healthcheck> {
7+
constructor(server: FastifyInstance, healthcheck: Healthcheck) {
8+
super(server, undefined, healthcheck)
9+
}
10+
11+
public override handleRoutes(): void {
12+
13+
this.server.get("/ping", async (_, reply) => {
14+
reply.code(200).send("pong")
15+
})
16+
17+
this.server.head("/", async (_, reply) => {
18+
reply.code(200).send()
19+
})
20+
21+
this.server.get<{
22+
Reply: {
23+
200: SystemReport
24+
}
25+
}>("/healthcheck", async (_, reply) => {
26+
27+
const result = await this.service.getFullSystemReport()
28+
if (isException(result)) {
29+
// @ts-ignore
30+
reply.code(result.statusCode).send(result)
31+
return
32+
}
33+
34+
reply.code(200).send(result)
35+
})
36+
}
37+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { RedisClientType } from "redis";
2+
import { DataSource } from "typeorm";
3+
import { withExceptionCatch } from "../../decorators/WithExceptionCatch";
4+
5+
export type SystemReport = {
6+
databaseAccess: boolean;
7+
cacheAccess: boolean;
8+
serverUptimeInSec: number;
9+
databaseUptimeInSec: number;
10+
}
11+
12+
export default class Healthcheck {
13+
constructor(
14+
private redis: RedisClientType,
15+
private dataSource: DataSource
16+
) {}
17+
18+
private async isRedisAccesible(): Promise<boolean> {
19+
try {
20+
await this.redis.PING()
21+
return true
22+
} catch {
23+
return false
24+
}
25+
}
26+
27+
private async isPostgreSQLAccesible(): Promise<boolean> {
28+
try {
29+
await this.dataSource.query("SELECT user;")
30+
return true
31+
} catch {
32+
return false
33+
}
34+
}
35+
36+
private async getPostgreSQLUptime(): Promise<number> {
37+
try {
38+
const result = await this.dataSource.query("SELECT FLOOR(EXTRACT('epoch' from current_timestamp - pg_postmaster_start_time()));")
39+
return parseInt(result[0]["floor"])
40+
} catch {
41+
return 0
42+
}
43+
}
44+
45+
private getProccessUptime(): number {
46+
return Math.floor(process.uptime())
47+
}
48+
49+
@withExceptionCatch
50+
public async getFullSystemReport(): Promise<SystemReport> {
51+
return {
52+
databaseAccess: await this.isPostgreSQLAccesible(),
53+
cacheAccess: await this.isRedisAccesible(),
54+
serverUptimeInSec: this.getProccessUptime(),
55+
databaseUptimeInSec: await this.getPostgreSQLUptime(),
56+
}
57+
}
58+
}

source/main.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import { initAndGetDataSource } from './api/v1/database/InitDataSource'
1515
import { NoteEntity } from './api/v1/database/entities/Note'
1616
import { initSwaggerViewer } from './api/v1/openapi/swagger/InitSwagger'
1717
import { connectAndGetRedisInstance } from './api/v1/cache/InitRedisInstance'
18+
import Healthcheck from './api/v1/shared/utils/common/Healthcheck'
19+
import { CommonHandler } from './api/v1/handlers/CommonHandler'
1820

1921
const main = async () => {
2022
CONFIG.log()
@@ -26,8 +28,6 @@ const main = async () => {
2628
}
2729
})
2830

29-
30-
3131
await initSwaggerViewer(server)
3232

3333
server.addHook('onRequest', logRequestMetadata)
@@ -40,29 +40,31 @@ const main = async () => {
4040
CONFIG.databasePassword,
4141
CONFIG.databaseName
4242
)
43-
43+
appDataSource.isInitialized
4444
const redis = await connectAndGetRedisInstance(
4545
CONFIG.redisConnectionString
4646
)
47-
47+
redis.PING()
4848
// services DI
4949
const usersService = new UsersService(
5050
appDataSource.getRepository(UserEntity.User)
5151
)
5252
const authService = new AuthService(usersService, redis)
5353
const authentication = authenticationFactory(authService)
5454
const notesService = new NotesService(appDataSource.getRepository(NoteEntity.Note), usersService)
55-
55+
const healthcheck = new Healthcheck(redis, appDataSource)
5656

5757
// registering handlers with version prefix
5858
server.register((server, _, done) => {
5959
const usersHandler = new UsersHandler(server, authentication, usersService)
6060
const notesHandler = new NotesHandler(server, authentication, notesService)
6161
const authHandler = new AuthHandler(server, authentication, authService)
62+
const commonHandler = new CommonHandler(server, healthcheck)
6263

6364
usersHandler.handleRoutes()
6465
notesHandler.handleRoutes()
6566
authHandler.handleRoutes()
67+
commonHandler.handleRoutes()
6668
done()
6769
}, { prefix: "/api/v1" })
6870

tests/e2e/api_v1.postman_collection.json

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,68 @@
11
{
22
"info": {
3-
"_postman_id": "2ab9da82-cedd-4f0f-a26c-6e98eb3f0f8f",
3+
"_postman_id": "b7daecf2-89c3-4203-a003-2b69d9b68cab",
44
"name": "NodeNotes v1",
55
"description": "**NodeNotes E2E tests coverage snippets.**\n\n_Linear testing scenario, negatives and positives._\n\n_All endpoints coverage_",
66
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
7-
"_exporter_id": "27933519"
7+
"_exporter_id": "27933519",
8+
"_collection_link": "https://blue-trinity-780436.postman.co/workspace/My-Workspace~ad9419b7-fa53-4d0c-8bc5-91d56b7dcbdc/collection/27933519-b7daecf2-89c3-4203-a003-2b69d9b68cab?action=share&source=collection_link&creator=27933519"
89
},
910
"item": [
11+
{
12+
"name": "Healthcheck",
13+
"item": [
14+
{
15+
"name": "Healthcheck",
16+
"event": [
17+
{
18+
"listen": "test",
19+
"script": {
20+
"exec": [
21+
"pm.test(\"Status code is 200\", function () {\r",
22+
" pm.response.to.have.status(200);\r",
23+
"});\r",
24+
"\r",
25+
"const result = pm.response.json()\r",
26+
"pm.test(\"Database is accesible\", function () {\r",
27+
" pm.expect(result.databaseAccess).to.equal(true)\r",
28+
"});\r",
29+
"\r",
30+
"pm.test(\"Cache is accesible\", function () {\r",
31+
" pm.expect(result.cacheAccess).to.equal(true)\r",
32+
"});\r",
33+
"\r",
34+
"pm.test(\"Uptimes are greater than 0\", function () {\r",
35+
" pm.expect(result.serverUptimeInSec).to.be.greaterThan(-1)\r",
36+
" pm.expect(result.databaseUptimeInSec).to.be.greaterThan(-1)\r",
37+
"});\r",
38+
""
39+
],
40+
"type": "text/javascript",
41+
"packages": {}
42+
}
43+
}
44+
],
45+
"request": {
46+
"method": "GET",
47+
"header": [],
48+
"url": {
49+
"raw": "{{protocol}}://{{host}}:{{port}}/api/v1/healthcheck",
50+
"protocol": "{{protocol}}",
51+
"host": [
52+
"{{host}}"
53+
],
54+
"port": "{{port}}",
55+
"path": [
56+
"api",
57+
"v1",
58+
"healthcheck"
59+
]
60+
}
61+
},
62+
"response": []
63+
}
64+
]
65+
},
1066
{
1167
"name": "Registration",
1268
"item": [
@@ -3864,7 +3920,7 @@
38643920
},
38653921
{
38663922
"key": "port",
3867-
"value": "8080",
3923+
"value": "8000",
38683924
"type": "string"
38693925
},
38703926
{

0 commit comments

Comments
 (0)