rhoam-shared-utils 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +2 -0
- package/package.json +29 -0
- package/src/constants/constant.js +173 -0
- package/src/helpers/helpers.js +129 -0
- package/src/index.js +9 -0
- package/src/kafka/client.js +13 -0
- package/src/kafka/consumer.js +29 -0
- package/src/kafka/eventlistener.js +29 -0
- package/src/kafka/producer.js +21 -0
- package/src/kafka/topics.js +19 -0
- package/src/middleware/VerifyAdmin.js +28 -0
- package/src/utils/logger.js +37 -0
- package/types/rhoam-shared.d.ts +5 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jabez-Techzar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rhoam-shared-utils",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shared infrastructure utilities for Rhoam microservices",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Jabez Nehemiah",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "src/index.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"src",
|
|
14
|
+
"types"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "node src/index.js"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"axios": "^1.13.2",
|
|
21
|
+
"jsonwebtoken": "^9.0.2",
|
|
22
|
+
"kafkajs": "^2.2.4",
|
|
23
|
+
"winston": "^3.18.3",
|
|
24
|
+
"winston-daily-rotate-file": "^5.0.0"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
export const KYC_ENUM = {
|
|
2
|
+
1: "Verified",
|
|
3
|
+
2: "Rejected",
|
|
4
|
+
3: "Mannual Approval Needed",
|
|
5
|
+
4: "Not Verified Yet",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const ACTIVE_STATUS = {
|
|
9
|
+
1: "Active",
|
|
10
|
+
0: "In-Active",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const TRANSACTION_STATUS = {
|
|
14
|
+
1: "Pending",
|
|
15
|
+
2: "Successful",
|
|
16
|
+
3: "Failed",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const BASE_URLS = {
|
|
20
|
+
user_services: "http://localhost:3000/api/v1/users",
|
|
21
|
+
auth_services: "http://localhost:3000/api/v1/auth",
|
|
22
|
+
admin_services: "http://localhost:3000/api/v1/admin",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
export const SERVICE_URLS = {
|
|
27
|
+
user_services: "http://localhost:4001/api/v1/users",
|
|
28
|
+
auth_services: "http://localhost:4000/api/v1/auth",
|
|
29
|
+
admin_services: "http://locahost:4003/api/v1/admin",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
export const GENDERS = {
|
|
34
|
+
1: 'Male',
|
|
35
|
+
2: 'Female',
|
|
36
|
+
3: 'Others',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const OTP_APIS = {
|
|
40
|
+
1: 'WHATSAPP',
|
|
41
|
+
2: 'SMS',
|
|
42
|
+
3: 'TELEGRAM',
|
|
43
|
+
4: 'EMAIL',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const FILE_TYPES = {
|
|
47
|
+
PROFILE_IMAGE: '1',
|
|
48
|
+
EKYC_PASSPORT: '2',
|
|
49
|
+
EKYC_SELFIE: '3',
|
|
50
|
+
EKYC_OTHER_DOCS: '4',
|
|
51
|
+
AI_CHAT_FILE: '5',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const EXT_TYPES ={
|
|
55
|
+
IMAGE: '1',
|
|
56
|
+
DOC: '2',
|
|
57
|
+
PDF: '3',
|
|
58
|
+
EXCEL: '4',
|
|
59
|
+
VIDEO: '5',
|
|
60
|
+
AUDIO: '6',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const RESPONSE_CODES = {
|
|
64
|
+
// Server errors
|
|
65
|
+
SERVER_ERROR: "SERVER_ERROR",
|
|
66
|
+
|
|
67
|
+
// Authentication & Login
|
|
68
|
+
LOGIN_REQUIRED: "LOGIN_REQUIRED",
|
|
69
|
+
LOGIN_SUCCESSFUL: "LOGIN_SUCCESSFUL",
|
|
70
|
+
LOGIN_FAILED: "LOGIN_FAILED",
|
|
71
|
+
|
|
72
|
+
// OTP related
|
|
73
|
+
OTP_SENT: "OTP_SENT",
|
|
74
|
+
OTP_VERIFIED: "OTP_VERIFIED",
|
|
75
|
+
OTP_INVALID: "OTP_INVALID",
|
|
76
|
+
OTP_EXPIRED: "OTP_EXPIRED",
|
|
77
|
+
|
|
78
|
+
// Device & Session
|
|
79
|
+
NEW_DEVICE_DETECTED: "NEW_DEVICE_DETECTED",
|
|
80
|
+
INVALID_SESSION: "INVALID_SESSION",
|
|
81
|
+
SESSION_EXPIRED: "SESSION_EXPIRED",
|
|
82
|
+
|
|
83
|
+
// Passcode (PIN)
|
|
84
|
+
PASSCODE_REQUIRED: "PASSCODE_REQUIRED",
|
|
85
|
+
PASSCODE_SETUP_REQUIRED: "PASSCODE_SETUP_REQUIRED",
|
|
86
|
+
PASSCODE_SETUP_SUCCESSFUL: "PASSCODE_SETUP_SUCCESSFUL",
|
|
87
|
+
PASSCODE_AUTH_SUCCESSFUL: "PASSCODE_AUTH_SUCCESSFUL",
|
|
88
|
+
PASSCODE_AUTH_FAILED: "PASSCODE_AUTH_FAILED",
|
|
89
|
+
PASSCODE_INVALID: "PASSCODE_INVALID",
|
|
90
|
+
PASSCODE_LOCKED: "PASSCODE_LOCKED",
|
|
91
|
+
|
|
92
|
+
// Biometric
|
|
93
|
+
BIOMETRIC_SETUP_SUCCESSFUL: "BIOMETRIC_SETUP_SUCCESSFUL",
|
|
94
|
+
BIOMETRIC_AUTH_SUCCESSFUL: "BIOMETRIC_AUTH_SUCCESSFUL",
|
|
95
|
+
BIOMETRIC_AUTH_FAILED: "BIOMETRIC_AUTH_FAILED",
|
|
96
|
+
BIOMETRIC_NOT_AVAILABLE: "BIOMETRIC_NOT_AVAILABLE",
|
|
97
|
+
BIOMETRIC_NOT_ENROLLED: "BIOMETRIC_NOT_ENROLLED",
|
|
98
|
+
|
|
99
|
+
// Account status
|
|
100
|
+
ACCOUNT_LOCKED: "ACCOUNT_LOCKED",
|
|
101
|
+
ACCOUNT_DEACTIVATED: "ACCOUNT_DEACTIVATED",
|
|
102
|
+
ACCOUNT_SUSPENDED: "ACCOUNT_SUSPENDED",
|
|
103
|
+
TOO_MANY_ATTEMPTS: "TOO_MANY_ATTEMPTS",
|
|
104
|
+
ACCOUNT_UNLOCKED: "ACCOUNT_UNLOCKED",
|
|
105
|
+
|
|
106
|
+
// Tokens
|
|
107
|
+
INVALID_TOKEN: "INVALID_TOKEN",
|
|
108
|
+
TOKEN_EXPIRED: "TOKEN_EXPIRED",
|
|
109
|
+
REFRESH_TOKEN_REQUIRED: "REFRESH_TOKEN_REQUIRED",
|
|
110
|
+
|
|
111
|
+
// Authorization
|
|
112
|
+
UNAUTHORIZED: "UNAUTHORIZED",
|
|
113
|
+
FORBIDDEN: "FORBIDDEN",
|
|
114
|
+
INSUFFICIENT_PERMISSIONS: "INSUFFICIENT_PERMISSIONS",
|
|
115
|
+
|
|
116
|
+
// Validation
|
|
117
|
+
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
118
|
+
INVALID_INPUT: "INVALID_INPUT",
|
|
119
|
+
MISSING_FIELDS: "MISSING_FIELDS",
|
|
120
|
+
|
|
121
|
+
// Rate limiting
|
|
122
|
+
RATE_LIMITED: "RATE_LIMITED",
|
|
123
|
+
TOO_MANY_REQUESTS: "TOO_MANY_REQUESTS",
|
|
124
|
+
|
|
125
|
+
// Account Already Exists,
|
|
126
|
+
ACCOUNT_EXISTS: "ACCOUNT ALREADY EXISTS",
|
|
127
|
+
VERIFICATION_REQUIRED: "VERIFICATION_REQUIRED",
|
|
128
|
+
NO_ACCOUNT_FOUND: "NO ACCOUNT FOUND",
|
|
129
|
+
|
|
130
|
+
// Password Events
|
|
131
|
+
PASSWORD_CHANGED: "PASSWORD CHANGED",
|
|
132
|
+
|
|
133
|
+
// Ekyc Statuses
|
|
134
|
+
EKYC_INITIATED : "EKYC_INITIATED",
|
|
135
|
+
EKYC_NOT_INITIATED: "EKYC_NOT_INITIATED",
|
|
136
|
+
EKYC_STEP_ONE_COMPLETED: "EKYC STEP ONE COMPLETED",
|
|
137
|
+
EKYC_STEP_TWO_COMPLETED: "EKYC STEP TWO COMPLETED",
|
|
138
|
+
EKYC_STEP_THREE_COMPLETED: "EKYC STEP THREE COMPLETED",
|
|
139
|
+
EKYC_VERIFICATION_COMPLETE: "EKYC_VERIFICATION_COMPLETE",
|
|
140
|
+
EKYC_VERIFICATION_FAILED:"EKYC_VERIFICATION_FAILED",
|
|
141
|
+
EKYC_MANNUAL_APPROVAL_NEEDED: "EKYC_MANNUAL_APPROVAL_NEEDED",
|
|
142
|
+
|
|
143
|
+
// Operation
|
|
144
|
+
OPERATION_SUCCESS: "OPERATION_SUCCESS",
|
|
145
|
+
OPERATION_FAILED: "OPERATION_FAILED",
|
|
146
|
+
|
|
147
|
+
NOT_FOUND: "NOT_FOUND",
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export const ROLES = {
|
|
153
|
+
1: 'Normal Users',
|
|
154
|
+
2: 'Admin Users'
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export const EKYC_STEPS = {
|
|
158
|
+
PERSONAL_INFO: 1,
|
|
159
|
+
DOCUMENT_SUBMISSION: 2,
|
|
160
|
+
SELFIE_VERIFICATION: 3,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
export const SERVICES_TYPE = {
|
|
165
|
+
1 : 'Authentication Service',
|
|
166
|
+
2 : 'User Service',
|
|
167
|
+
3 : 'API Gateway',
|
|
168
|
+
4 : 'Admin Service',
|
|
169
|
+
5 : 'Notification Service',
|
|
170
|
+
6 : 'EKYC Service',
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import * as CONSTANTS from "../constants/constant.js";
|
|
2
|
+
import { logger } from "../utils/logger.js";
|
|
3
|
+
import crypto from "crypto";
|
|
4
|
+
import axios from "axios";
|
|
5
|
+
const secret_key = "P(0p!e@e$k";
|
|
6
|
+
const secret_iv = "Peop!eDe$k";
|
|
7
|
+
const algorithm = "aes-256-cbc";
|
|
8
|
+
const key = crypto.createHash("sha256").update(secret_key).digest();
|
|
9
|
+
const iv = crypto.createHash("sha256").update(secret_iv).digest().subarray(0, 16);
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
export const GetGender = (gender) => {
|
|
13
|
+
|
|
14
|
+
switch (parseInt(gender)) {
|
|
15
|
+
case CONSTANTS.GENDERS[1]:
|
|
16
|
+
return 'Male';
|
|
17
|
+
break;
|
|
18
|
+
|
|
19
|
+
case CONSTANTS.GENDERS[2]:
|
|
20
|
+
return 'Female';
|
|
21
|
+
break;
|
|
22
|
+
|
|
23
|
+
case CONSTANTS.GENDERS[3]:
|
|
24
|
+
return 'Other';
|
|
25
|
+
break;
|
|
26
|
+
|
|
27
|
+
default:
|
|
28
|
+
return 'Unknown';
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const CalculateAge = (dob) => {
|
|
35
|
+
|
|
36
|
+
const birthDate = new Date(dob);
|
|
37
|
+
const today = new Date();
|
|
38
|
+
|
|
39
|
+
let age = today.getFullYear() - birthDate.getFullYear();
|
|
40
|
+
const monthDiff = today.getMonth() - birthDate.getMonth();
|
|
41
|
+
|
|
42
|
+
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
|
|
43
|
+
age--;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return age;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
// Base64 → Base64URL
|
|
51
|
+
const toBase64Url = (str) =>
|
|
52
|
+
str.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
53
|
+
|
|
54
|
+
// Base64URL → Base64
|
|
55
|
+
const fromBase64Url = (str) =>
|
|
56
|
+
str.replace(/-/g, "+").replace(/_/g, "/");
|
|
57
|
+
|
|
58
|
+
export const encryptId = (value) => {
|
|
59
|
+
const cipher = crypto.createCipheriv(algorithm, key, iv);
|
|
60
|
+
let encrypted = cipher.update(String(value), "utf8", "base64");
|
|
61
|
+
encrypted += cipher.final("base64");
|
|
62
|
+
|
|
63
|
+
return toBase64Url(encrypted);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const decryptId = (encryptedValue) => {
|
|
67
|
+
const decipher = crypto.createDecipheriv(algorithm, key, iv);
|
|
68
|
+
const base64 = fromBase64Url(encryptedValue);
|
|
69
|
+
|
|
70
|
+
let decrypted = decipher.update(base64, "base64", "utf8");
|
|
71
|
+
decrypted += decipher.final("utf8");
|
|
72
|
+
|
|
73
|
+
return decrypted;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const GetUserName = async (user_id) => {
|
|
77
|
+
try {
|
|
78
|
+
const response = await axios.post(
|
|
79
|
+
`${CONSTANTS.SERVICE_URLS.user_services}/fetch-user`,
|
|
80
|
+
{ user_id }
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const details = response?.data?.data?.details;
|
|
84
|
+
|
|
85
|
+
if (!details) return null;
|
|
86
|
+
|
|
87
|
+
return `${details.first_name} ${details.last_name}`;
|
|
88
|
+
|
|
89
|
+
} catch (exception) {
|
|
90
|
+
logger.error("Get User Name Error:", exception);
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const Displaydateformat = (date) => {
|
|
96
|
+
try {
|
|
97
|
+
if (!date) return null;
|
|
98
|
+
|
|
99
|
+
const d = new Date(date);
|
|
100
|
+
|
|
101
|
+
if (isNaN(d)) return null;
|
|
102
|
+
|
|
103
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
104
|
+
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
105
|
+
const year = d.getFullYear();
|
|
106
|
+
|
|
107
|
+
return `${day}-${month}-${year}`;
|
|
108
|
+
} catch (exception) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const Displaydatetimeformat = (date) => {
|
|
114
|
+
try {
|
|
115
|
+
if (!date) return null;
|
|
116
|
+
const d = new Date(date);
|
|
117
|
+
|
|
118
|
+
if (isNaN(d)) return null;
|
|
119
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
120
|
+
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
121
|
+
const year = d.getFullYear();
|
|
122
|
+
const hours = String(d.getHours()).padStart(2, "0");
|
|
123
|
+
const minutes = String(d.getMinutes()).padStart(2, "0");
|
|
124
|
+
return `${day}-${month}-${year} ${hours}:${minutes}`;
|
|
125
|
+
} catch (exception) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
package/src/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./helpers/helpers.js";
|
|
2
|
+
export * from "./constants/constant.js";
|
|
3
|
+
export * from "./middleware/VerifyAdmin.js";
|
|
4
|
+
export * from "./kafka/client.js";
|
|
5
|
+
export * from "./kafka/producer.js";
|
|
6
|
+
export * from "./kafka/consumer.js";
|
|
7
|
+
export * from "./kafka/topics.js";
|
|
8
|
+
export * from "./kafka/eventlistener.js";
|
|
9
|
+
export { logger } from "./utils/logger.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Kafka, logLevel } from "kafkajs";
|
|
2
|
+
|
|
3
|
+
let kafka;
|
|
4
|
+
export function getKafka(brokers = (process.env.KAFKA_BROKERS || "localhost:9092").split(",")) {
|
|
5
|
+
if (!kafka) {
|
|
6
|
+
kafka = new Kafka({
|
|
7
|
+
clientId: process.env.KAFKA_CLIENT_ID || "generic-service",
|
|
8
|
+
brokers,
|
|
9
|
+
logLevel: logLevel.NOTHING,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
return kafka;
|
|
13
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getKafka } from "./client.js";
|
|
2
|
+
|
|
3
|
+
export async function createConsumer(groupId) {
|
|
4
|
+
const consumer = getKafka().consumer({ groupId });
|
|
5
|
+
await consumer.connect();
|
|
6
|
+
console.log(`Kafka consumer connected (group: ${groupId})`);
|
|
7
|
+
return consumer;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function subscribeRun(consumer, topic, onMessage, fromBeginning = false) {
|
|
11
|
+
await consumer.subscribe({ topic, fromBeginning });
|
|
12
|
+
await consumer.run({
|
|
13
|
+
eachMessage: async ({ topic, partition, message }) => {
|
|
14
|
+
try {
|
|
15
|
+
await onMessage({ topic, partition, message });
|
|
16
|
+
} catch (err) {
|
|
17
|
+
console.error("Consumer handler error:", err.message);
|
|
18
|
+
// TODO: push to DLQ if needed
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function shutdownConsumer(consumer) {
|
|
25
|
+
if (consumer) {
|
|
26
|
+
await consumer.disconnect();
|
|
27
|
+
console.log("Kafka consumer disconnected");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createConsumer, subscribeRun } from "./consumer.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Simplified helper to listen to a Kafka topic and handle events.
|
|
5
|
+
* @param {string} groupId - Kafka consumer group ID (unique per service).
|
|
6
|
+
* @param {string} topic - Kafka topic name to subscribe to.
|
|
7
|
+
* @param {function} handler - Function called for each event message.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export async function listenToEvent(groupId, topic, handler) {
|
|
11
|
+
try {
|
|
12
|
+
const consumer = await createConsumer(groupId);
|
|
13
|
+
|
|
14
|
+
await subscribeRun(consumer, topic, async ({ message }) => {
|
|
15
|
+
const key = message.key?.toString();
|
|
16
|
+
const value = message.value?.toString();
|
|
17
|
+
|
|
18
|
+
if (!key || !value) return console.warn("Skipped empty Kafka message");
|
|
19
|
+
|
|
20
|
+
const data = JSON.parse(value);
|
|
21
|
+
await handler(key, data);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
console.log(`Listening to Kafka topic: ${topic} (group: ${groupId})`);
|
|
25
|
+
return consumer;
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.error(`Error initializing Kafka consumer for ${topic}:`, err.message);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getKafka } from "./client.js";
|
|
2
|
+
let producer;
|
|
3
|
+
export async function getProducer() {
|
|
4
|
+
if (!producer) {
|
|
5
|
+
producer = getKafka().producer({ allowAutoTopicCreation: true });
|
|
6
|
+
await producer.connect();
|
|
7
|
+
console.log("Kafka producer connected");
|
|
8
|
+
}
|
|
9
|
+
return producer;
|
|
10
|
+
}
|
|
11
|
+
export async function publish(topic, messages, headers = {}) {
|
|
12
|
+
const p = await getProducer();
|
|
13
|
+
await p.send({ topic, messages, headers });
|
|
14
|
+
}
|
|
15
|
+
export async function shutdownProducer() {
|
|
16
|
+
if (producer) {
|
|
17
|
+
await producer.disconnect();
|
|
18
|
+
producer = null;
|
|
19
|
+
console.log("Kafka producer disconnected");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const Topics = {
|
|
2
|
+
USER_EVENTS : 'user-events',
|
|
3
|
+
EKYC_EVENTS: 'ekyc-events',
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export const Keys = {
|
|
7
|
+
USER_REGISTERED : 'user-registered',
|
|
8
|
+
USER_EMAIL_OTP: 'user-email-otp',
|
|
9
|
+
USER_MOBILE_OTP: 'user-mobile-otp',
|
|
10
|
+
USER_PASSWORD_CHANGED: 'user-password-changed',
|
|
11
|
+
USER_FCM_REGISTER: 'user-fcm-store',
|
|
12
|
+
USER_DETAILS_UPDATE: 'user-details-update',
|
|
13
|
+
EKYC_DOCUMENT_UPLOAD: 'ekyc-user-document-upload',
|
|
14
|
+
EKYC_DOCUMENT_DELETED: 'ekyc-user-document-delete',
|
|
15
|
+
ERRORS : 'errors',
|
|
16
|
+
AUDIT_EVENTS : 'audit-event',
|
|
17
|
+
EKYC_VERIFICATION_COMPLETED : 'ekyc-verification-completed',
|
|
18
|
+
|
|
19
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import jwt from "jsonwebtoken";
|
|
2
|
+
import dotenv from "dotenv";
|
|
3
|
+
dotenv.config();
|
|
4
|
+
|
|
5
|
+
export const VerifyAdmin = (req, res, next) => {
|
|
6
|
+
try {
|
|
7
|
+
const authHeader = req.headers.authorization;
|
|
8
|
+
|
|
9
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
10
|
+
return res.status(401).json({ message: "Authorization token missing" });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const token = authHeader.split(" ")[1];
|
|
14
|
+
|
|
15
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
16
|
+
|
|
17
|
+
if (!decoded?.admin) {
|
|
18
|
+
return res.status(403).json({ message: "Access denied: Admins only" });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
req.user = decoded;
|
|
22
|
+
next();
|
|
23
|
+
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error("Token verification failed:", err.message);
|
|
26
|
+
return res.status(401).json({ message: "Invalid or expired token" });
|
|
27
|
+
}
|
|
28
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createLogger, format, transports } from "winston";
|
|
2
|
+
import DailyRotateFile from "winston-daily-rotate-file";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
const logFormat = format.printf(
|
|
6
|
+
({ level, message, timestamp, stack }) =>
|
|
7
|
+
`${timestamp} [${level}] : ${stack || message}`
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
export const logger = createLogger({
|
|
11
|
+
level: "info",
|
|
12
|
+
format: format.combine(
|
|
13
|
+
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
|
|
14
|
+
format.errors({ stack: true }),
|
|
15
|
+
logFormat
|
|
16
|
+
),
|
|
17
|
+
transports: [
|
|
18
|
+
new transports.Console({
|
|
19
|
+
format: format.combine(format.colorize(), logFormat),
|
|
20
|
+
}),
|
|
21
|
+
|
|
22
|
+
new DailyRotateFile({
|
|
23
|
+
filename: path.join("logs", "error-%DATE%.log"),
|
|
24
|
+
datePattern: "YYYY-MM-DD",
|
|
25
|
+
level: "error",
|
|
26
|
+
maxSize: "20m",
|
|
27
|
+
maxFiles: "14d",
|
|
28
|
+
}),
|
|
29
|
+
|
|
30
|
+
new DailyRotateFile({
|
|
31
|
+
filename: path.join("logs", "combined-%DATE%.log"),
|
|
32
|
+
datePattern: "YYYY-MM-DD",
|
|
33
|
+
maxSize: "20m",
|
|
34
|
+
maxFiles: "14d",
|
|
35
|
+
}),
|
|
36
|
+
],
|
|
37
|
+
});
|