Skip to content

Commit ebcdfb0

Browse files
DayifourYaishaa
andauthored
Final Test (#15)
* Refactor OTP service to publish notifications with updated message structure and routing key * Refactor RabbitMQ configuration and health route for improved readability and consistency * Refactor notification controller and server initialization for improved structure and readability * Add user contact service and update notification handling for email and phone * Refactor code structure for improved readability in Otp, NotificationService, and UserContactService * Update Docker Compose configuration, refactor Notification entity imports, enhance OTP service with user contact retrieval * Update Docker Compose to use external network and enhance external notification consumer logging * Update dependencies: body-parser to 2.2.1 and nodemailer to 6.10.1 * Enhance OTP generation to validate email and phone inputs, and retrieve user contacts if not provided * Refactor InterServices and NotificationService to include optional email and phone fields, enhancing contact retrieval logic for notifications * Add Zod validation library and enhance notification service with multi-channel support for email and SMS * Enhance notification and OTP generation with Zod validation for request bodies * feat: Implement notification and OTP services with RabbitMQ integration - Added RabbitMQ configuration and channel management in `rabbitmq.js`. - Created notification controller to handle sending notifications and retrieving them. - Developed OTP controller for generating and verifying OTPs. - Introduced data source configuration for PostgreSQL using TypeORM. - Defined Notification and Otp entities with appropriate fields and relationships. - Implemented notification service for sending notifications via SMS and email. - Created OTP service for generating and verifying OTPs, including publishing events to RabbitMQ. - Added user contact service for retrieving user contact information. - Implemented mail service for sending emails using Nodemailer. - Developed message templates for generating notification messages based on type. - Created health check route for service status. - Set up consumers for processing notifications from RabbitMQ. - Added external consumer for handling inter-service notifications. * feat: notifications inter-service et robustesse consumer Co-authored-by: Aissata Traore <traoreaissata423@gmail.com> * fix: messages de transfert sender/receiver Co-authored-by: Aissata Traore <traoreaissata423@gmail.com> * Edit: Add more concise infos in health endpoints --------- Co-authored-by: Aissata Traore <traoreaissata423@gmail.com>
1 parent 96b1809 commit ebcdfb0

File tree

2 files changed

+213
-4
lines changed

2 files changed

+213
-4
lines changed

dist/routes/health.js

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,93 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
44
};
55
Object.defineProperty(exports, "__esModule", { value: true });
66
const express_1 = __importDefault(require("express"));
7+
const fs_1 = __importDefault(require("fs"));
8+
const path_1 = __importDefault(require("path"));
9+
const rabbitmq_1 = require("../config/rabbitmq");
10+
const data_source_1 = require("../data-source");
711
const router = express_1.default.Router();
12+
// Configurable via env
13+
const HEALTH_TIMEOUT_MS = parseInt(process.env.HEALTH_CHECK_TIMEOUT_MS || "1000", 10);
14+
const HEALTH_CACHE_TTL_MS = parseInt(process.env.HEALTH_CACHE_TTL_MS || "5000", 10);
15+
const EXPOSE_ERRORS = process.env.HEALTH_EXPOSE_ERRORS === "true";
16+
// Read package.json for version fallback
17+
let pkgVersion;
18+
let pkgName;
19+
try {
20+
const pkgPath = path_1.default.join(__dirname, "..", "..", "package.json");
21+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
22+
const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, "utf8"));
23+
pkgVersion = pkg.version;
24+
pkgName = pkg.name;
25+
}
26+
catch {
27+
// ignore
28+
}
29+
// Simple cache to avoid expensive repeated checks
30+
let readinessCache = null;
31+
function withTimeout(p, ms, name) {
32+
return Promise.race([
33+
p,
34+
new Promise((_, rej) => setTimeout(() => rej(new Error(`timeout:${name || "op"}`)), ms)),
35+
]);
36+
}
37+
// Simple liveness probe
838
router.get("/health", (req, res) => {
9-
res.status(200).json({ status: "OK" });
39+
res.status(200).json({ status: "OK", uptime: process.uptime() });
40+
});
41+
// Readiness probe: checks PostgreSQL connection and RabbitMQ channel
42+
router.get("/health/ready", async (req, res) => {
43+
const now = Date.now();
44+
if (readinessCache && now - readinessCache.ts < HEALTH_CACHE_TTL_MS) {
45+
return res.status(readinessCache.code).json(readinessCache.result);
46+
}
47+
const result = {
48+
status: "OK",
49+
uptime: process.uptime(),
50+
timestamp: new Date().toISOString(),
51+
version: process.env.SERVICE_VERSION || pkgVersion,
52+
commit: process.env.COMMIT_SHA,
53+
components: {
54+
db: { status: "UNKNOWN" },
55+
rabbitmq: { status: "UNKNOWN" },
56+
},
57+
};
58+
// Check DB with timeout
59+
try {
60+
if (!data_source_1.AppDataSource.isInitialized) {
61+
throw new Error("DataSource not initialized");
62+
}
63+
// lightweight query with timeout
64+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
65+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
66+
await withTimeout(data_source_1.AppDataSource.query("SELECT 1"), HEALTH_TIMEOUT_MS, "db");
67+
result.components.db.status = "OK";
68+
}
69+
catch (err) {
70+
result.status = "NOT_OK";
71+
result.components.db.status = "DOWN";
72+
result.components.db.error = EXPOSE_ERRORS
73+
? err instanceof Error
74+
? err.message
75+
: String(err)
76+
: "unavailable";
77+
}
78+
// Check RabbitMQ with timeout
79+
try {
80+
await withTimeout(Promise.resolve((0, rabbitmq_1.getRabbitChannel)()), HEALTH_TIMEOUT_MS, "rabbitmq");
81+
result.components.rabbitmq.status = "OK";
82+
}
83+
catch (err) {
84+
result.status = "NOT_OK";
85+
result.components.rabbitmq.status = "DOWN";
86+
result.components.rabbitmq.error = EXPOSE_ERRORS
87+
? err instanceof Error
88+
? err.message
89+
: String(err)
90+
: "unavailable";
91+
}
92+
const code = result.status === "OK" ? 200 : 503;
93+
readinessCache = { ts: now, result, code };
94+
res.status(code).json(result);
1095
});
1196
exports.default = router;

src/routes/health.ts

Lines changed: 127 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,136 @@
11
import express, { type Request, type Response } from "express";
2+
import fs from "fs";
3+
import path from "path";
4+
import { getRabbitChannel } from "../config/rabbitmq";
5+
import { AppDataSource } from "../data-source";
6+
27
const router = express.Router();
38

4-
interface HealthResponse {
9+
interface ComponentStatus {
10+
status: string;
11+
error?: string;
12+
}
13+
14+
interface ReadyResponse {
515
status: string;
16+
uptime: number;
17+
timestamp: string;
18+
version?: string;
19+
commit?: string;
20+
components: {
21+
db: ComponentStatus;
22+
rabbitmq: ComponentStatus;
23+
};
24+
}
25+
26+
// Configurable via env
27+
const HEALTH_TIMEOUT_MS = parseInt(
28+
process.env.HEALTH_CHECK_TIMEOUT_MS || "1000",
29+
10,
30+
);
31+
const HEALTH_CACHE_TTL_MS = parseInt(
32+
process.env.HEALTH_CACHE_TTL_MS || "5000",
33+
10,
34+
);
35+
const EXPOSE_ERRORS = process.env.HEALTH_EXPOSE_ERRORS === "true";
36+
37+
// Read package.json for version fallback
38+
let pkgVersion: string | undefined;
39+
let pkgName: string | undefined;
40+
try {
41+
const pkgPath = path.join(__dirname, "..", "..", "package.json");
42+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
43+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
44+
pkgVersion = pkg.version;
45+
pkgName = pkg.name;
46+
} catch {
47+
// ignore
648
}
749

8-
router.get("/health", (req: Request, res: Response<HealthResponse>) => {
9-
res.status(200).json({ status: "OK" });
50+
// Simple cache to avoid expensive repeated checks
51+
let readinessCache: { ts: number; result: ReadyResponse; code: number } | null =
52+
null;
53+
54+
function withTimeout<T>(p: Promise<T>, ms: number, name?: string): Promise<T> {
55+
return Promise.race([
56+
p,
57+
new Promise<T>((_, rej) =>
58+
setTimeout(() => rej(new Error(`timeout:${name || "op"}`)), ms),
59+
),
60+
]);
61+
}
62+
63+
// Simple liveness probe
64+
router.get("/health", (req: Request, res: Response) => {
65+
res.status(200).json({ status: "OK", uptime: process.uptime() });
1066
});
1167

68+
// Readiness probe: checks PostgreSQL connection and RabbitMQ channel
69+
router.get(
70+
"/health/ready",
71+
async (req: Request, res: Response<ReadyResponse>) => {
72+
const now = Date.now();
73+
if (readinessCache && now - readinessCache.ts < HEALTH_CACHE_TTL_MS) {
74+
return res.status(readinessCache.code).json(readinessCache.result);
75+
}
76+
77+
const result: ReadyResponse = {
78+
status: "OK",
79+
uptime: process.uptime(),
80+
timestamp: new Date().toISOString(),
81+
version: process.env.SERVICE_VERSION || pkgVersion,
82+
commit: process.env.COMMIT_SHA,
83+
components: {
84+
db: { status: "UNKNOWN" },
85+
rabbitmq: { status: "UNKNOWN" },
86+
},
87+
};
88+
89+
// Check DB with timeout
90+
try {
91+
if (!AppDataSource.isInitialized) {
92+
throw new Error("DataSource not initialized");
93+
}
94+
// lightweight query with timeout
95+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
96+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
97+
await withTimeout(
98+
AppDataSource.query("SELECT 1"),
99+
HEALTH_TIMEOUT_MS,
100+
"db",
101+
);
102+
result.components.db.status = "OK";
103+
} catch (err: unknown) {
104+
result.status = "NOT_OK";
105+
result.components.db.status = "DOWN";
106+
result.components.db.error = EXPOSE_ERRORS
107+
? err instanceof Error
108+
? err.message
109+
: String(err)
110+
: "unavailable";
111+
}
112+
113+
// Check RabbitMQ with timeout
114+
try {
115+
await withTimeout(
116+
Promise.resolve(getRabbitChannel()),
117+
HEALTH_TIMEOUT_MS,
118+
"rabbitmq",
119+
);
120+
result.components.rabbitmq.status = "OK";
121+
} catch (err: unknown) {
122+
result.status = "NOT_OK";
123+
result.components.rabbitmq.status = "DOWN";
124+
result.components.rabbitmq.error = EXPOSE_ERRORS
125+
? err instanceof Error
126+
? err.message
127+
: String(err)
128+
: "unavailable";
129+
}
130+
131+
const code = result.status === "OK" ? 200 : 503;
132+
readinessCache = { ts: now, result, code };
133+
res.status(code).json(result);
134+
},
135+
);
12136
export default router;

0 commit comments

Comments
 (0)