@triophore/falconjs 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/FalconAuthPlugin.js +473 -0
- package/LICENSE +21 -0
- package/README.md +2 -0
- package/core/auth.js +200 -0
- package/core/cache/redis_cacher.js +7 -0
- package/core/check_collection.js +9 -0
- package/core/crypto/encrypt_decrypt.js +19 -0
- package/core/errors.js +48 -0
- package/core/logger/log4js.js +89 -0
- package/core/logo.js +3 -0
- package/core/mongo/generateModelfromJsonFile.js +128 -0
- package/core/mongo/mongoSchmeFromJson.js +90 -0
- package/core/parse_num.js +8 -0
- package/core/rannum.js +33 -0
- package/core/ranstring.js +33 -0
- package/core/recursive-require-call.js +121 -0
- package/core/uitls/mongoose_to_joi.js +72 -0
- package/core/uitls/return.js +7 -0
- package/falcon.js +1644 -0
- package/falconAuthPlugin.js +17 -0
- package/falconBaseService.js +532 -0
- package/falconBaseWorker.js +540 -0
- package/index.js +4 -0
- package/out/Falcon.html +777 -0
- package/out/falcon.js.html +525 -0
- package/out/fonts/OpenSans-Bold-webfont.eot +0 -0
- package/out/fonts/OpenSans-Bold-webfont.svg +1830 -0
- package/out/fonts/OpenSans-Bold-webfont.woff +0 -0
- package/out/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
- package/out/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
- package/out/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
- package/out/fonts/OpenSans-Italic-webfont.eot +0 -0
- package/out/fonts/OpenSans-Italic-webfont.svg +1830 -0
- package/out/fonts/OpenSans-Italic-webfont.woff +0 -0
- package/out/fonts/OpenSans-Light-webfont.eot +0 -0
- package/out/fonts/OpenSans-Light-webfont.svg +1831 -0
- package/out/fonts/OpenSans-Light-webfont.woff +0 -0
- package/out/fonts/OpenSans-LightItalic-webfont.eot +0 -0
- package/out/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
- package/out/fonts/OpenSans-LightItalic-webfont.woff +0 -0
- package/out/fonts/OpenSans-Regular-webfont.eot +0 -0
- package/out/fonts/OpenSans-Regular-webfont.svg +1831 -0
- package/out/fonts/OpenSans-Regular-webfont.woff +0 -0
- package/out/index.html +65 -0
- package/out/scripts/linenumber.js +25 -0
- package/out/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/out/scripts/prettify/lang-css.js +2 -0
- package/out/scripts/prettify/prettify.js +28 -0
- package/out/styles/jsdoc-default.css +358 -0
- package/out/styles/prettify-jsdoc.css +111 -0
- package/out/styles/prettify-tomorrow.css +132 -0
- package/package.json +106 -0
- package/settings.js +1 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class falconAuthPlugin {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.name = "falconAuthPlugin";
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
init() {
|
|
7
|
+
// Initialize the plugin
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
authenticate(username, password) {
|
|
11
|
+
// Authenticate the user
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
authorize(user, resource, action) {
|
|
15
|
+
// Authorize the user for the given resource and action
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
require('dotenv').config({ quiet: true, debug: false });
|
|
2
|
+
const mqtt = require("mqtt");
|
|
3
|
+
const config = process.env;
|
|
4
|
+
const crypto = require('crypto');
|
|
5
|
+
const mongoose = require('mongoose');
|
|
6
|
+
const bcrypt = require('bcrypt');
|
|
7
|
+
const mongoosePaginate = require('mongoose-paginate-v2');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const JWT = require('jsonwebtoken');
|
|
11
|
+
const { v4: uuidv4 } = require('uuid');
|
|
12
|
+
const redis = require("redis");
|
|
13
|
+
const axios = require("axios");
|
|
14
|
+
const { ValidationError, DatabaseError } = require('./core/errors');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Base class for Falcon services that run as separate processes.
|
|
18
|
+
* Provides MQTT communication, database access, and model loading.
|
|
19
|
+
* Services are long-running processes that handle specific business logic.
|
|
20
|
+
*
|
|
21
|
+
* @class FalconBaseService
|
|
22
|
+
* @example
|
|
23
|
+
* class EmailService extends FalconBaseService {
|
|
24
|
+
* constructor() {
|
|
25
|
+
* super('email');
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* async onMessage(topic, msg) {
|
|
29
|
+
* if (topic === 'service_email') {
|
|
30
|
+
* await this.sendEmail(JSON.parse(msg));
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* async run() {
|
|
35
|
+
* console.log('Email service running...');
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
*
|
|
39
|
+
* const service = new EmailService();
|
|
40
|
+
* service.init();
|
|
41
|
+
*/
|
|
42
|
+
class FalconBaseService {
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Creates a new Falcon service instance.
|
|
46
|
+
* Automatically loads settings, sets up graceful shutdown, and initializes context.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} serviceId - Service identifier used for MQTT topics and logging
|
|
49
|
+
* @throws {Error} Throws if service ID is not provided
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* class MyService extends FalconBaseService {
|
|
53
|
+
* constructor() {
|
|
54
|
+
* super('my-service'); // Creates topics: service_my-service, service_my-service_ping
|
|
55
|
+
* }
|
|
56
|
+
* }
|
|
57
|
+
*/
|
|
58
|
+
constructor(serviceId) {
|
|
59
|
+
if (!serviceId || typeof serviceId !== 'string') {
|
|
60
|
+
throw new ValidationError('serviceId must be a non-empty string');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.serviceId = serviceId;
|
|
64
|
+
this.models = {};
|
|
65
|
+
this.CONTEXT = { models: {} };
|
|
66
|
+
this.db = null;
|
|
67
|
+
this.mqttClient = null;
|
|
68
|
+
this.redisClient = null;
|
|
69
|
+
|
|
70
|
+
// Load settings from CWD
|
|
71
|
+
try {
|
|
72
|
+
const settingsPath = path.join(process.cwd(), 'settings.js');
|
|
73
|
+
if (fs.existsSync(settingsPath)) {
|
|
74
|
+
this.SETTINGS = require(settingsPath).settings;
|
|
75
|
+
} else {
|
|
76
|
+
console.warn("Settings file not found in CWD");
|
|
77
|
+
this.SETTINGS = { models: [] };
|
|
78
|
+
}
|
|
79
|
+
} catch (e) {
|
|
80
|
+
console.error("Error loading settings:", e);
|
|
81
|
+
this.SETTINGS = { models: [] };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.setupGracefulShutdown();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Checks if running in development mode.
|
|
89
|
+
* @private
|
|
90
|
+
* @returns {boolean}
|
|
91
|
+
*/
|
|
92
|
+
#isDev() {
|
|
93
|
+
return process.env.MODE === "DEV";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Loads a model by name from various possible locations.
|
|
98
|
+
* Searches in: models/mongo/, models/, services/models/
|
|
99
|
+
*
|
|
100
|
+
* @private
|
|
101
|
+
* @param {string} name - Model name without .js extension
|
|
102
|
+
* @throws {Error} Logs error if model loading fails but doesn't throw
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* // Searches for user.js in:
|
|
106
|
+
* // - /project/models/mongo/user.js
|
|
107
|
+
* // - /project/models/user.js
|
|
108
|
+
* // - /project/services/models/user.js
|
|
109
|
+
* await this.#load_model_name('user');
|
|
110
|
+
*/
|
|
111
|
+
async #load_model_name(name) {
|
|
112
|
+
const cwd = process.cwd();
|
|
113
|
+
const potentialPaths = [
|
|
114
|
+
path.join(cwd, "models", "mongo", name + ".js"),
|
|
115
|
+
path.join(cwd, "models", name + ".js"),
|
|
116
|
+
path.join(cwd, "services", "models", name + ".js")
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
let modelPath = null;
|
|
120
|
+
for (const p of potentialPaths) {
|
|
121
|
+
if (fs.existsSync(p)) {
|
|
122
|
+
modelPath = p;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (modelPath) {
|
|
128
|
+
try {
|
|
129
|
+
const _temp = await require(modelPath)(mongoose);
|
|
130
|
+
this.models[name] = _temp;
|
|
131
|
+
this.CONTEXT["models"][name] = _temp;
|
|
132
|
+
console.info(`${name} model registered from ${modelPath}`);
|
|
133
|
+
} catch (e) {
|
|
134
|
+
console.error(`Failed to load model ${name} from ${modelPath}:`, e);
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
console.warn(`Model ${name} not found in any standard location.`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Connects to MongoDB database using MONGODB_URL environment variable.
|
|
143
|
+
* Sets up Mongoose with debug logging in development mode.
|
|
144
|
+
*
|
|
145
|
+
* @private
|
|
146
|
+
* @async
|
|
147
|
+
* @throws {Error} Throws if MongoDB connection fails
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* // Requires MONGODB_URL environment variable
|
|
151
|
+
* // mongodb://localhost:27017/mydb
|
|
152
|
+
* await this.#startMongoDB();
|
|
153
|
+
*/
|
|
154
|
+
async #startMongoDB() {
|
|
155
|
+
if (!config.MONGODB_URL) {
|
|
156
|
+
console.warn("MONGODB_URL not configured, skipping database connection");
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (this.#isDev()) {
|
|
161
|
+
mongoose.set("debug", true);
|
|
162
|
+
console.debug("Mongoose ODM is set in Debug Mode");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
await mongoose.connect(config.MONGODB_URL, {});
|
|
167
|
+
mongoose.set("debug", (collectionName, method, query, doc) => {
|
|
168
|
+
console.info(`MONGOOSE ==> ${collectionName}.${method}`, JSON.stringify(query), doc);
|
|
169
|
+
});
|
|
170
|
+
this.db = mongoose.connection.db;
|
|
171
|
+
this.CONTEXT["db"] = this.db;
|
|
172
|
+
this.CONTEXT["mongoose"] = mongoose;
|
|
173
|
+
console.info("MongoDB connected");
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error("MongoDB connection failed:", error);
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Connects to Redis if enabled via REDIS_ENABLE=true and REDIS_URL environment variables.
|
|
182
|
+
* Redis connection is optional and service will continue without it if not configured.
|
|
183
|
+
*
|
|
184
|
+
* @private
|
|
185
|
+
* @async
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* // Requires environment variables:
|
|
189
|
+
* // REDIS_ENABLE=true
|
|
190
|
+
* // REDIS_URL=redis://localhost:6379
|
|
191
|
+
* await this.#startRedis();
|
|
192
|
+
*/
|
|
193
|
+
async #startRedis() {
|
|
194
|
+
if (config.REDIS_ENABLE === "true" && config.REDIS_URL) {
|
|
195
|
+
try {
|
|
196
|
+
this.redis_client = await redis.createClient({ url: config.REDIS_URL }).connect();
|
|
197
|
+
this.CONTEXT["redis"] = this.redis_client;
|
|
198
|
+
console.info("Redis connected");
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error("Redis connection failed:", error);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Connects to MQTT broker using MQTT_URL environment variable.
|
|
207
|
+
* Sets up event handlers for connect, message, and error events.
|
|
208
|
+
*
|
|
209
|
+
* @private
|
|
210
|
+
* @async
|
|
211
|
+
* @throws {Error} Throws if MQTT connection fails
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* // Requires MQTT_URL environment variable
|
|
215
|
+
* // mqtt://localhost:1883
|
|
216
|
+
* await this.#startMQTT();
|
|
217
|
+
*/
|
|
218
|
+
async #startMQTT() {
|
|
219
|
+
if (!config.MQTT_URL) {
|
|
220
|
+
console.warn("MQTT_URL not configured, skipping MQTT connection");
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
this.mqtt_client = mqtt.connect(config.MQTT_URL);
|
|
226
|
+
this.mqtt_client.on("connect", this.onConnect.bind(this));
|
|
227
|
+
this.mqtt_client.on("message", this.#onMessage.bind(this));
|
|
228
|
+
this.mqtt_client.on("error", (err) => {
|
|
229
|
+
console.error("MQTT connection error:", err);
|
|
230
|
+
});
|
|
231
|
+
this.CONTEXT["mqtt_client"] = this.mqtt_client;
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error("MQTT connection failed:", error);
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Loads all models specified in settings.models array.
|
|
240
|
+
* Models are loaded from various locations and registered in this.models and this.CONTEXT.models.
|
|
241
|
+
*
|
|
242
|
+
* @private
|
|
243
|
+
* @async
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* // settings.js: { models: ['user', 'product', 'order'] }
|
|
247
|
+
* await this.#loadModels();
|
|
248
|
+
* // Now available as: this.models.user, this.models.product, etc.
|
|
249
|
+
*/
|
|
250
|
+
async #loadModels() {
|
|
251
|
+
if (!this.SETTINGS.models || this.SETTINGS.models.length === 0) {
|
|
252
|
+
console.info("No models configured");
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
console.info("-----------------Registering Models----------------");
|
|
257
|
+
for (const model of this.SETTINGS.models) {
|
|
258
|
+
await this.#load_model_name(model);
|
|
259
|
+
}
|
|
260
|
+
console.info("-----------------Models Registered----------------");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Publishes data to websocket clients via the 'websocket_broadcast' MQTT topic.
|
|
265
|
+
* The main Falcon server listens to this topic and forwards to connected websocket clients.
|
|
266
|
+
*
|
|
267
|
+
* @param {*} data - Data to broadcast (will be JSON stringified)
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* this.publishToWebsocket({
|
|
271
|
+
* type: 'notification',
|
|
272
|
+
* message: 'Service started',
|
|
273
|
+
* timestamp: new Date()
|
|
274
|
+
* });
|
|
275
|
+
*/
|
|
276
|
+
publishToWebsocket(data) {
|
|
277
|
+
if (this.mqttClient) {
|
|
278
|
+
this.mqttClient.publish('websocket_broadcast', JSON.stringify(data));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Emits data to a specific WebSocket room/group via MQTT.
|
|
284
|
+
*
|
|
285
|
+
* @param {string} room - Room name to emit to
|
|
286
|
+
* @param {string} event - Event name
|
|
287
|
+
* @param {*} payload - Data payload
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* this.emitToGroup('user_123', 'notification', { message: 'Hello!' });
|
|
291
|
+
*/
|
|
292
|
+
emitToGroup(room, event, payload) {
|
|
293
|
+
if (this.mqttClient) {
|
|
294
|
+
this.mqttClient.publish('websocket_emit_group', JSON.stringify({
|
|
295
|
+
room,
|
|
296
|
+
event,
|
|
297
|
+
payload
|
|
298
|
+
}));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Emits data to a specific WebSocket client by socket ID via MQTT.
|
|
304
|
+
*
|
|
305
|
+
* @param {string} socketId - Socket ID to emit to
|
|
306
|
+
* @param {string} event - Event name
|
|
307
|
+
* @param {*} payload - Data payload
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* this.emitToSocket('socket_abc123', 'private_message', { text: 'Hello!' });
|
|
311
|
+
*/
|
|
312
|
+
emitToSocket(socketId, event, payload) {
|
|
313
|
+
if (this.mqttClient) {
|
|
314
|
+
this.mqttClient.publish('websocket_emit_socket', JSON.stringify({
|
|
315
|
+
socketId,
|
|
316
|
+
event,
|
|
317
|
+
payload
|
|
318
|
+
}));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Publishes data to a specific MQTT topic.
|
|
324
|
+
* Data is automatically JSON stringified before publishing.
|
|
325
|
+
*
|
|
326
|
+
* @param {*} data - Data to publish (objects will be JSON stringified)
|
|
327
|
+
* @param {string} topic - MQTT topic to publish to
|
|
328
|
+
*
|
|
329
|
+
* @example
|
|
330
|
+
* // Send status update
|
|
331
|
+
* this.publish({ status: 'completed', jobId: 123 }, 'job_status');
|
|
332
|
+
*
|
|
333
|
+
* // Send to another service
|
|
334
|
+
* this.publish({ action: 'process' }, 'service_image-processor');
|
|
335
|
+
*/
|
|
336
|
+
publish(data, topic) {
|
|
337
|
+
if (this.mqtt_client) {
|
|
338
|
+
this.mqtt_client.publish(topic, JSON.stringify(data));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Called when MQTT connection is established.
|
|
344
|
+
* Automatically subscribes to service-specific topics for communication and health checks.
|
|
345
|
+
* Override in subclasses to add custom subscriptions or initialization logic.
|
|
346
|
+
*
|
|
347
|
+
* @async
|
|
348
|
+
* @virtual
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* async onConnect() {
|
|
352
|
+
* await super.onConnect(); // Call parent to get default subscriptions
|
|
353
|
+
* await this.mqtt_client.subscribe('custom_topic');
|
|
354
|
+
* console.log('Custom service connected');
|
|
355
|
+
* }
|
|
356
|
+
*/
|
|
357
|
+
async onConnect() {
|
|
358
|
+
console.info(`Service ${this.service_id} connected to MQTT`);
|
|
359
|
+
// Subscribe to service-specific topic
|
|
360
|
+
await this.mqtt_client.subscribe(`service_${this.service_id}`);
|
|
361
|
+
await this.mqtt_client.subscribe(`service_${this.service_id}_ping`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Internal MQTT message handler.
|
|
366
|
+
* @private
|
|
367
|
+
*/
|
|
368
|
+
async #onMessage(topic, msg) {
|
|
369
|
+
// Handle ping/pong for health checks
|
|
370
|
+
if (topic === `service_${this.service_id}_ping`) {
|
|
371
|
+
this.publish('pong', `service_${this.service_id}_pong`);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
await this.onMessage(topic, msg);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Override this method to handle incoming MQTT messages.
|
|
380
|
+
* Called for all subscribed topics except ping messages (handled automatically).
|
|
381
|
+
*
|
|
382
|
+
* @async
|
|
383
|
+
* @virtual
|
|
384
|
+
* @param {string} topic - MQTT topic that received the message
|
|
385
|
+
* @param {Buffer} msg - Raw message buffer (use msg.toString() to get string)
|
|
386
|
+
*
|
|
387
|
+
* @example
|
|
388
|
+
* async onMessage(topic, msg) {
|
|
389
|
+
* const data = JSON.parse(msg.toString());
|
|
390
|
+
*
|
|
391
|
+
* switch (topic) {
|
|
392
|
+
* case 'service_email':
|
|
393
|
+
* await this.sendEmail(data);
|
|
394
|
+
* break;
|
|
395
|
+
* case 'service_email_bulk':
|
|
396
|
+
* await this.sendBulkEmail(data);
|
|
397
|
+
* break;
|
|
398
|
+
* }
|
|
399
|
+
* }
|
|
400
|
+
*/
|
|
401
|
+
async onMessage(topic, msg) {
|
|
402
|
+
// Override in subclasses
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Override this method with your service's main business logic.
|
|
407
|
+
* Called after all initialization (database, MQTT, models) is complete.
|
|
408
|
+
* This method should contain the core functionality of your service.
|
|
409
|
+
*
|
|
410
|
+
* @async
|
|
411
|
+
* @virtual
|
|
412
|
+
*
|
|
413
|
+
* @example
|
|
414
|
+
* async run() {
|
|
415
|
+
* console.log('Email service starting...');
|
|
416
|
+
*
|
|
417
|
+
* // Set up periodic tasks
|
|
418
|
+
* setInterval(() => {
|
|
419
|
+
* this.checkPendingEmails();
|
|
420
|
+
* }, 30000);
|
|
421
|
+
*
|
|
422
|
+
* // Service is now ready to handle requests
|
|
423
|
+
* this.publish({ status: 'ready' }, 'service_status');
|
|
424
|
+
* }
|
|
425
|
+
*/
|
|
426
|
+
async run() {
|
|
427
|
+
// Override in subclasses
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Initializes the service with all dependencies in the correct order.
|
|
432
|
+
* Sets up MongoDB, Redis, models, MQTT connection, and then calls run().
|
|
433
|
+
* This method should be called to start the service.
|
|
434
|
+
*
|
|
435
|
+
* @async
|
|
436
|
+
* @throws {Error} Throws if any initialization step fails
|
|
437
|
+
*
|
|
438
|
+
* @example
|
|
439
|
+
* const emailService = new EmailService();
|
|
440
|
+
* await emailService.init(); // Service is now running
|
|
441
|
+
*/
|
|
442
|
+
async init() {
|
|
443
|
+
console.info(`Initializing service: ${this.service_id}`);
|
|
444
|
+
await this.#startMongoDB();
|
|
445
|
+
await this.#startRedis();
|
|
446
|
+
await this.#loadModels();
|
|
447
|
+
await this.#startMQTT();
|
|
448
|
+
await this.run();
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Starts the service (alias for run).
|
|
453
|
+
*/
|
|
454
|
+
async start() {
|
|
455
|
+
await this.run();
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Gracefully cleans up all service resources.
|
|
460
|
+
* Closes MQTT connection, Redis connection, and MongoDB connection in that order.
|
|
461
|
+
* Called automatically during graceful shutdown or can be called manually.
|
|
462
|
+
*
|
|
463
|
+
* @async
|
|
464
|
+
*
|
|
465
|
+
* @example
|
|
466
|
+
* // Manual cleanup
|
|
467
|
+
* await service.cleanup();
|
|
468
|
+
*
|
|
469
|
+
* // Automatic cleanup on process signals (already set up in constructor)
|
|
470
|
+
* process.on('SIGTERM', async () => {
|
|
471
|
+
* await service.cleanup();
|
|
472
|
+
* process.exit(0);
|
|
473
|
+
* });
|
|
474
|
+
*/
|
|
475
|
+
async cleanup() {
|
|
476
|
+
console.info(`Cleaning up service: ${this.service_id}`);
|
|
477
|
+
|
|
478
|
+
if (this.mqtt_client && this.mqtt_client.connected) {
|
|
479
|
+
await this.mqtt_client.end();
|
|
480
|
+
this.mqtt_client = null;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (this.redis_client && this.redis_client.isOpen) {
|
|
484
|
+
await this.redis_client.quit();
|
|
485
|
+
this.redis_client = null;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (this.db && mongoose.connection.readyState === 1) {
|
|
489
|
+
await mongoose.connection.close();
|
|
490
|
+
this.db = null;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Sets up graceful shutdown handlers for process signals and uncaught exceptions.
|
|
496
|
+
* Automatically handles SIGTERM, SIGINT, uncaughtException, and unhandledRejection.
|
|
497
|
+
* Called automatically in constructor - no need to call manually.
|
|
498
|
+
*
|
|
499
|
+
* @private
|
|
500
|
+
*
|
|
501
|
+
* @example
|
|
502
|
+
* // Automatically handles these scenarios:
|
|
503
|
+
* // - Docker container stop (SIGTERM)
|
|
504
|
+
* // - Ctrl+C in terminal (SIGINT)
|
|
505
|
+
* // - Unhandled promise rejections
|
|
506
|
+
* // - Uncaught exceptions
|
|
507
|
+
*
|
|
508
|
+
* // All trigger cleanup() before process exit
|
|
509
|
+
*/
|
|
510
|
+
setupGracefulShutdown() {
|
|
511
|
+
const shutdown = async (signal) => {
|
|
512
|
+
console.log(`Service ${this.service_id} received ${signal}, shutting down...`);
|
|
513
|
+
await this.cleanup();
|
|
514
|
+
process.exit(0);
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
518
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
519
|
+
process.on('uncaughtException', async (err) => {
|
|
520
|
+
console.error('Uncaught Exception:', err);
|
|
521
|
+
await this.cleanup();
|
|
522
|
+
process.exit(1);
|
|
523
|
+
});
|
|
524
|
+
process.on('unhandledRejection', async (reason) => {
|
|
525
|
+
console.error('Unhandled Rejection:', reason);
|
|
526
|
+
await this.cleanup();
|
|
527
|
+
process.exit(1);
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
module.exports = FalconBaseService;
|