cloud-ide-model-schema
Version:
Pachage for schema management of Cloud IDEsys LMS
490 lines (489 loc) • 29.3 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.customUUID = void 0;
exports.connectDB = connectDB;
var mongoose = require("mongoose");
require("dotenv/config");
var fs = require("fs");
var path = require("path");
// Connection state tracking
var isConnecting = false;
var connectionRetryCount = 0;
var MAX_RETRY_ATTEMPTS = 10;
var INITIAL_RETRY_DELAY = 1000; // 1 second
var MAX_RETRY_DELAY = 30000; // 30 seconds
var retryTimeout = null;
var alertEmailSent = false; // Track if alert email has been sent
/**
* Check if MongoDB is already connected
*/
function isConnected() {
return mongoose.connection.readyState === 1; // 1 = connected
}
/**
* Check if MongoDB is connecting
*/
function isConnectingState() {
return mongoose.connection.readyState === 2; // 2 = connecting
}
// Track if listeners have been set up to prevent duplicates
// This prevents the "Possible EventEmitter memory leak detected" warning
// that occurs when multiple modules call connectDB() and listeners are added multiple times
var listenersSetup = false;
/**
* Setup MongoDB connection event listeners (idempotent)
* This function ensures listeners are only added once, even if called multiple times.
*
* IMPORTANT: This prevents EventEmitter memory leaks that occur when:
* - Multiple controller/service files call connectDB() at module import time
* - Reconnection logic calls connectDB() recursively
* - Each call would add duplicate event listeners without this protection
*
* The fix uses a flag to ensure listeners are only added once, regardless of
* how many times connectDB() is called.
*/
function setupConnectionListeners() {
// Only setup once - check flag to ensure no duplicates
if (listenersSetup) {
return;
}
// Keep max listeners at default (10) since we prevent duplicates with the flag
// No need to increase if listeners are only added once
mongoose.connection.setMaxListeners(10);
// Connection successful
mongoose.connection.on('connected', function () {
console.log('✅ MongoDB connected successfully');
connectionRetryCount = 0; // Reset retry count on success
isConnecting = false;
alertEmailSent = false; // Reset alert flag on successful connection
});
// Connection error
mongoose.connection.on('error', function (error) {
console.error('❌ MongoDB connection error:', error.message);
isConnecting = false;
// Don't throw - let retry logic handle it
});
// Connection disconnected
mongoose.connection.on('disconnected', function () {
console.warn('⚠️ MongoDB disconnected. Will attempt to reconnect...');
isConnecting = false;
// Send alert for unexpected disconnection (but not on initial connection failure)
if (connectionRetryCount > 0) {
sendMongoDBConnectionAlert('WARNING: MongoDB Disconnected', {
name: 'MongoDisconnected',
message: 'MongoDB connection was lost unexpectedly'
}).catch(function (alertError) {
console.error('❌ Failed to send disconnection alert:', (alertError === null || alertError === void 0 ? void 0 : alertError.message) || alertError);
});
}
// Attempt reconnection
attemptReconnection();
});
// Connection timeout
mongoose.connection.on('timeout', function () {
console.warn('⚠️ MongoDB connection timeout. Will attempt to reconnect...');
isConnecting = false;
// Attempt reconnection
attemptReconnection();
});
// Connection closed
mongoose.connection.on('close', function () {
console.warn('⚠️ MongoDB connection closed. Will attempt to reconnect...');
isConnecting = false;
// Attempt reconnection
attemptReconnection();
});
// Mark listeners as setup
listenersSetup = true;
}
/**
* Attempt to reconnect to MongoDB with exponential backoff
*/
function attemptReconnection() {
var _this = this;
// Don't attempt if already connecting or connected
if (isConnecting || isConnected()) {
return;
}
// Clear any existing retry timeout
if (retryTimeout) {
clearTimeout(retryTimeout);
retryTimeout = null;
}
// Check retry limit - but allow reset after a longer delay
if (connectionRetryCount >= MAX_RETRY_ATTEMPTS) {
// Send alert email if not already sent
if (!alertEmailSent) {
sendMongoDBConnectionAlert('CRITICAL: Maximum retry attempts reached').catch(function (alertError) {
console.error('❌ Failed to send max retry alert:', (alertError === null || alertError === void 0 ? void 0 : alertError.message) || alertError);
});
alertEmailSent = true;
}
// After max attempts, wait longer before allowing another attempt
// This prevents infinite retries but allows recovery if MongoDB becomes available
var longDelay = MAX_RETRY_DELAY * 2; // 60 seconds
console.log("\u23F3 MongoDB reconnection paused after ".concat(MAX_RETRY_ATTEMPTS, " attempts. Will retry in ").concat(longDelay, "ms..."));
retryTimeout = setTimeout(function () {
retryTimeout = null;
connectionRetryCount = 0; // Reset retry count to allow fresh attempt
console.log('🔄 Resetting retry count. Attempting fresh MongoDB connection...');
connectDB().catch(function () {
// If it fails, start retry cycle again
});
}, longDelay);
return;
}
// Calculate exponential backoff delay
var delay = Math.min(INITIAL_RETRY_DELAY * Math.pow(2, connectionRetryCount), MAX_RETRY_DELAY);
connectionRetryCount++;
console.log("\uD83D\uDD04 Attempting MongoDB reconnection (".concat(connectionRetryCount, "/").concat(MAX_RETRY_ATTEMPTS, ") in ").concat(delay, "ms..."));
retryTimeout = setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
retryTimeout = null;
if (!(!isConnected() && !isConnecting)) return [3 /*break*/, 2];
return [4 /*yield*/, connectDB()];
case 1:
_a.sent(); // Recursive call
_a.label = 2;
case 2: return [2 /*return*/];
}
});
}); }, delay);
}
/**
* Connect to MongoDB with robust error handling and retry logic
* This function is idempotent - safe to call multiple times
*/
function connectDB() {
return __awaiter(this, void 0, void 0, function () {
var mongoUri, closeError_1, options, error_1, errorMessage, errorStack, errorName, closeError_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// If already connected, do nothing
if (isConnected()) {
return [2 /*return*/];
}
if (!(isConnecting || isConnectingState())) return [3 /*break*/, 2];
// Wait 2 seconds and check if connection succeeded
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 2000); })];
case 1:
// Wait 2 seconds and check if connection succeeded
_a.sent();
// If connected now, return
if (isConnected()) {
return [2 /*return*/];
}
// If still connecting after wait, allow new attempt (might be stuck)
if (isConnecting || isConnectingState()) {
console.warn('⚠️ Previous connection attempt seems stuck. Starting fresh connection attempt...');
isConnecting = false; // Reset flag to allow new attempt
}
_a.label = 2;
case 2:
// Setup event listeners (idempotent)
setupConnectionListeners();
mongoUri = process.env.MONGODB_URI;
if (!mongoUri || mongoUri.trim() === '') {
console.error('❌ MONGODB_URI is not set in environment variables');
// Don't throw - server should continue running
return [2 /*return*/];
}
isConnecting = true;
_a.label = 3;
case 3:
_a.trys.push([3, 9, , 15]);
if (!(mongoose.connection.readyState !== 0)) return [3 /*break*/, 7];
_a.label = 4;
case 4:
_a.trys.push([4, 6, , 7]);
return [4 /*yield*/, mongoose.connection.close()];
case 5:
_a.sent();
return [3 /*break*/, 7];
case 6:
closeError_1 = _a.sent();
// Log close errors instead of suppressing them
console.warn('⚠️ Error closing existing MongoDB connection:', (closeError_1 === null || closeError_1 === void 0 ? void 0 : closeError_1.message) || closeError_1);
if (closeError_1 === null || closeError_1 === void 0 ? void 0 : closeError_1.stack) {
console.warn(' Stack:', closeError_1.stack);
}
return [3 /*break*/, 7];
case 7:
options = {
connectTimeoutMS: 15000, // 15 second timeout
socketTimeoutMS: 45000, // 45 second socket timeout
serverSelectionTimeoutMS: 15000, // 15 second server selection timeout
heartbeatFrequencyMS: 10000, // Heartbeat frequency
retryWrites: true,
retryReads: true,
// Buffer commands if connection is not ready
bufferCommands: true,
maxPoolSize: 50, // Maximum number of connections in the pool
minPoolSize: 5, // Minimum number of connections in the pool
};
// Attempt connection
return [4 /*yield*/, mongoose.connect(mongoUri, options)];
case 8:
// Attempt connection
_a.sent();
// If we get here, connection was successful
console.log('✅ Connected to MongoDB');
connectionRetryCount = 0; // Reset retry count on success
isConnecting = false;
return [3 /*break*/, 15];
case 9:
error_1 = _a.sent();
isConnecting = false;
errorMessage = (error_1 === null || error_1 === void 0 ? void 0 : error_1.message) || 'Unknown error';
errorStack = (error_1 === null || error_1 === void 0 ? void 0 : error_1.stack) || 'No stack trace available';
errorName = (error_1 === null || error_1 === void 0 ? void 0 : error_1.name) || 'Error';
console.error('❌ MongoDB connection failed:');
console.error(" Error Name: ".concat(errorName));
console.error(" Error Message: ".concat(errorMessage));
console.error(" Error Stack: ".concat(errorStack));
if (error_1 === null || error_1 === void 0 ? void 0 : error_1.code) {
console.error(" Error Code: ".concat(error_1.code));
}
if (error_1 === null || error_1 === void 0 ? void 0 : error_1.codeName) {
console.error(" Error Code Name: ".concat(error_1.codeName));
}
_a.label = 10;
case 10:
_a.trys.push([10, 13, , 14]);
if (!(mongoose.connection.readyState !== 0)) return [3 /*break*/, 12];
return [4 /*yield*/, mongoose.connection.close()];
case 11:
_a.sent();
_a.label = 12;
case 12: return [3 /*break*/, 14];
case 13:
closeError_2 = _a.sent();
// Log close errors instead of suppressing them
console.error('❌ Error closing MongoDB connection:', (closeError_2 === null || closeError_2 === void 0 ? void 0 : closeError_2.message) || closeError_2);
if (closeError_2 === null || closeError_2 === void 0 ? void 0 : closeError_2.stack) {
console.error(' Stack:', closeError_2.stack);
}
return [3 /*break*/, 14];
case 14:
// Send immediate alert on first connection failure (critical issue)
if (connectionRetryCount === 0) {
console.error('🚨 CRITICAL: MongoDB connection failed on first attempt. Sending immediate alert...');
sendMongoDBConnectionAlert('CRITICAL: Initial connection failure', error_1).catch(function (alertError) {
console.error('❌ Failed to send immediate alert:', (alertError === null || alertError === void 0 ? void 0 : alertError.message) || alertError);
});
}
// Only attempt retry if we haven't exceeded max attempts
if (connectionRetryCount < MAX_RETRY_ATTEMPTS) {
attemptReconnection();
}
else {
// Send alert email if we've reached max attempts and haven't sent it yet
if (!alertEmailSent) {
console.error('🚨 CRITICAL: MongoDB connection failed after maximum retry attempts. Sending alert...');
sendMongoDBConnectionAlert('CRITICAL: Maximum retry attempts reached', error_1).catch(function (alertError) {
console.error('❌ Failed to send max retry alert:', (alertError === null || alertError === void 0 ? void 0 : alertError.message) || alertError);
});
alertEmailSent = true;
}
// After max attempts, schedule a longer wait before retrying
attemptReconnection(); // This will handle the long delay
}
return [3 /*break*/, 15];
case 15: return [2 /*return*/];
}
});
});
}
/**
* Send email alert to Cloud IDE team about MongoDB connection failure
* This function works without requiring MongoDB connection
* @param severity - Alert severity level (e.g., 'CRITICAL', 'WARNING')
* @param error - Optional error object for detailed error information
*/
function sendMongoDBConnectionAlert() {
return __awaiter(this, arguments, void 0, function (severity, error) {
var subject, body, recipientEmail, serverName_1, serverName, mongoUri, errorDetails, emailGatewayUrl, emailGatewayApiKey, senderEmail, axios, emailPayload, emailError_1, error_2;
if (severity === void 0) { severity = 'WARNING'; }
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
subject = '';
body = '';
_a.label = 1;
case 1:
_a.trys.push([1, 8, , 9]);
recipientEmail = process.env.CLOUD_IDE_ALERT_EMAIL || process.env.SYSTEM_ALERT_EMAIL;
if (!recipientEmail) {
console.warn('⚠️ MongoDB alert email not sent: CLOUD_IDE_ALERT_EMAIL or SYSTEM_ALERT_EMAIL not configured');
serverName_1 = process.env.SERVER_NAME || process.env.NODE_ENV || 'Unknown';
subject = "\uD83D\uDEA8 ".concat(severity, ": MongoDB Connection Failure - ").concat(serverName_1);
body = "MongoDB connection alert - email recipient not configured. Please check CLOUD_IDE_ALERT_EMAIL or SYSTEM_ALERT_EMAIL environment variable.";
logAlertToFile(severity, subject, body, error);
return [2 /*return*/];
}
serverName = process.env.SERVER_NAME || process.env.NODE_ENV || 'Unknown';
mongoUri = process.env.MONGODB_URI ?
process.env.MONGODB_URI.replace(/\/\/([^:]+):([^@]+)@/, '//***:***@') : // Mask credentials
'Not configured';
errorDetails = error ? "\nError Details:\n- Error Name: ".concat((error === null || error === void 0 ? void 0 : error.name) || 'Unknown', "\n- Error Message: ").concat((error === null || error === void 0 ? void 0 : error.message) || 'No error message', "\n- Error Code: ").concat((error === null || error === void 0 ? void 0 : error.code) || 'N/A', "\n- Error Code Name: ").concat((error === null || error === void 0 ? void 0 : error.codeName) || 'N/A', "\n").concat((error === null || error === void 0 ? void 0 : error.stack) ? "- Error Stack: ".concat(error.stack.substring(0, 500), "...") : '', "\n ") : '';
subject = "\uD83D\uDEA8 ".concat(severity, ": MongoDB Connection Failure - ").concat(serverName);
body = "\n".concat(severity, ": MongoDB Connection Failure Alert\n\nSystem Information:\n- Server: ").concat(serverName, "\n- Environment: ").concat(process.env.NODE_ENV || 'Unknown', "\n- Timestamp: ").concat(new Date().toISOString(), "\n- MongoDB URI: ").concat(mongoUri, "\n- Severity: ").concat(severity, "\n\nConnection Status:\n- Retry Attempts: ").concat(connectionRetryCount, "/").concat(MAX_RETRY_ATTEMPTS, "\n- Connection State: ").concat(mongoose.connection.readyState === 0 ? 'Disconnected' :
mongoose.connection.readyState === 1 ? 'Connected' :
mongoose.connection.readyState === 2 ? 'Connecting' : 'Unknown', "\n- Is Connecting: ").concat(isConnecting ? 'Yes' : 'No', "\n\n").concat(errorDetails, "\n\nAction Required:\n").concat(severity === 'CRITICAL' ?
'⚠️ CRITICAL: The system cannot connect to MongoDB. Database operations are failing.' :
"The system has failed to connect to MongoDB after ".concat(connectionRetryCount, " retry attempts."), "\n\nPlease check immediately:\n1. MongoDB server status (Atlas cluster, local server, etc.)\n2. Network connectivity and firewall rules\n3. IP whitelist in MongoDB Atlas (if using Atlas)\n4. MongoDB URI configuration and credentials\n5. MongoDB authentication credentials validity\n6. Server resources (memory, CPU, disk space)\n\nCommon Issues:\n- IP address not whitelisted in MongoDB Atlas\n- Network connectivity problems\n- MongoDB server is down or unreachable\n- Incorrect connection string or credentials\n- Firewall blocking MongoDB port (usually 27017)\n\nThe system will continue to retry connection automatically, but database operations may fail until connection is restored.\n\nThis is an automated alert from Cloud IDE System Monitoring.\n ").trim();
emailGatewayUrl = process.env.EMAIL_GATEWAY_URL;
emailGatewayApiKey = process.env.EMAIL_GATEWAY_API_KEY;
senderEmail = process.env.EMAIL_SENDER || process.env.SYSTEM_EMAIL || 'noreply@cloudide.com';
if (!(emailGatewayUrl && emailGatewayApiKey)) return [3 /*break*/, 6];
axios = require('axios');
emailPayload = {
to: recipientEmail,
from: senderEmail,
subject: subject,
body: body,
html: body.replace(/\n/g, '<br>') // Convert newlines to HTML breaks
};
_a.label = 2;
case 2:
_a.trys.push([2, 4, , 5]);
return [4 /*yield*/, axios.post(emailGatewayUrl, emailPayload, {
headers: {
'Content-Type': 'application/json',
'Authorization': "Bearer ".concat(emailGatewayApiKey),
'X-API-Key': emailGatewayApiKey
},
timeout: 10000 // 10 second timeout
})];
case 3:
_a.sent();
console.log("\uD83D\uDCE7 MongoDB connection failure alert email sent to ".concat(recipientEmail));
return [3 /*break*/, 5];
case 4:
emailError_1 = _a.sent();
console.error('❌ Failed to send alert email via gateway:', (emailError_1 === null || emailError_1 === void 0 ? void 0 : emailError_1.message) || emailError_1);
// Fallback: Log to file when email fails
logAlertToFile(severity, subject, body, emailError_1);
return [3 /*break*/, 5];
case 5: return [3 /*break*/, 7];
case 6:
console.warn('⚠️ Alert email not sent: EMAIL_GATEWAY_URL and EMAIL_GATEWAY_API_KEY not configured');
console.warn('⚠️ Please configure email gateway environment variables for MongoDB connection failure alerts');
console.warn("\u26A0\uFE0F Alert details would have been sent to: ".concat(recipientEmail));
// Fallback: Log to file when email gateway not configured
logAlertToFile(severity, subject, body, error);
_a.label = 7;
case 7: return [3 /*break*/, 9];
case 8:
error_2 = _a.sent();
// Don't throw - this is a non-critical operation
console.error('❌ Failed to send MongoDB connection alert email:', (error_2 === null || error_2 === void 0 ? void 0 : error_2.message) || error_2);
// Fallback: Log to file when everything fails
logAlertToFile(severity, subject || 'MongoDB Connection Alert', body || 'MongoDB connection issue detected', error_2);
return [3 /*break*/, 9];
case 9: return [2 /*return*/];
}
});
});
}
/**
* Log alert to file (fallback when email fails)
* This ensures critical alerts are never lost
*/
function logAlertToFile(severity, subject, body, error) {
var _a, _b;
try {
// Log directory - create in project root/logs/alerts
var logDir = path.join(process.cwd(), 'logs', 'alerts');
// Ensure directory exists
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
// Get log file path for today (YYYY-MM-DD format)
var today = new Date();
var dateStr = today.toISOString().split('T')[0];
var logFilePath = path.join(logDir, "mongodb-alerts-".concat(dateStr, ".log"));
// Format log entry
var now = new Date();
var timestamp = now.toISOString();
var dateTime = now.toLocaleString('en-US', {
timeZone: 'UTC',
dateStyle: 'full',
timeStyle: 'long'
});
var logEntry = "\n".concat('='.repeat(80), "\nMONGODB ALERT LOG ENTRY\n").concat('='.repeat(80), "\nTimestamp: ").concat(timestamp, "\nDate & Time: ").concat(dateTime, "\nSeverity: ").concat(severity, "\nSubject: ").concat(subject, "\n\n").concat(body, "\n\n");
// Add error details if provided
if (error) {
logEntry += "\nERROR DETAILS:\n".concat('-'.repeat(80), "\n- Error Name: ").concat((error === null || error === void 0 ? void 0 : error.name) || 'Unknown', "\n- Error Message: ").concat((error === null || error === void 0 ? void 0 : error.message) || 'No error message', "\n- Error Code: ").concat((error === null || error === void 0 ? void 0 : error.code) || 'N/A', "\n- Error Code Name: ").concat((error === null || error === void 0 ? void 0 : error.codeName) || 'N/A', "\n");
if (error === null || error === void 0 ? void 0 : error.stack) {
logEntry += "- Error Stack:\n".concat(error.stack, "\n");
}
if (error === null || error === void 0 ? void 0 : error.response) {
logEntry += "- HTTP Response Status: ".concat(((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status) || 'N/A', "\n");
logEntry += "- HTTP Response Data: ".concat(JSON.stringify(((_b = error === null || error === void 0 ? void 0 : error.response) === null || _b === void 0 ? void 0 : _b.data) || {}, null, 2), "\n");
}
}
logEntry += "".concat('='.repeat(80), "\n\n");
// Append to log file
fs.appendFileSync(logFilePath, logEntry, 'utf8');
console.log("\uD83D\uDCDD Alert logged to file: ".concat(logFilePath));
}
catch (fileError) {
// If file logging fails, at least log to console
console.error('❌ Failed to write alert to log file:', (fileError === null || fileError === void 0 ? void 0 : fileError.message) || fileError);
console.error('📝 Alert details (console fallback):');
console.error(" Severity: ".concat(severity));
console.error(" Subject: ".concat(subject));
console.error(" Body: ".concat(body));
if (error) {
console.error(" Error: ".concat((error === null || error === void 0 ? void 0 : error.message) || error));
}
}
}
// Event listener for success and error
var customUUID = function () {
var _a, _b, _c;
var key = (_c = "".concat(process.env.UUID_PREFIX).concat((_b = (_a = (new mongoose.Types.ObjectId())) === null || _a === void 0 ? void 0 : _a.toHexString()) === null || _b === void 0 ? void 0 : _b.slice(3, 24))) === null || _c === void 0 ? void 0 : _c.toLowerCase();
console.log('Generated custom UUID:', key);
return key;
};
exports.customUUID = customUUID;