mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 07:20:12 +01:00
Add support for MongoDB 3-8, detecting which one is in use.
Thanks to xet7 !
This commit is contained in:
parent
1f5a549589
commit
74ccfea570
11 changed files with 1650 additions and 7 deletions
306
models/lib/meteorMongoIntegration.js
Normal file
306
models/lib/meteorMongoIntegration.js
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import { DDP } from 'meteor/ddp';
|
||||
import { mongodbConnectionManager } from './mongodbConnectionManager';
|
||||
import { mongodbDriverManager } from './mongodbDriverManager';
|
||||
|
||||
/**
|
||||
* Meteor MongoDB Integration
|
||||
*
|
||||
* This module integrates the MongoDB driver manager with Meteor's
|
||||
* built-in MongoDB connection system to provide automatic driver
|
||||
* selection and version detection.
|
||||
*
|
||||
* Features:
|
||||
* - Hooks into Meteor's MongoDB connection process
|
||||
* - Automatic driver selection based on detected version
|
||||
* - Fallback mechanism for connection failures
|
||||
* - Integration with Meteor's DDP and reactive systems
|
||||
*/
|
||||
|
||||
class MeteorMongoIntegration {
|
||||
constructor() {
|
||||
this.originalMongoConnect = null;
|
||||
this.originalMongoCollection = null;
|
||||
this.isInitialized = false;
|
||||
this.connectionString = null;
|
||||
this.customConnection = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the integration
|
||||
* @param {string} connectionString - MongoDB connection string
|
||||
*/
|
||||
initialize(connectionString) {
|
||||
if (this.isInitialized) {
|
||||
console.log('Meteor MongoDB Integration already initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
this.connectionString = connectionString;
|
||||
console.log('Initializing Meteor MongoDB Integration...');
|
||||
|
||||
// Store original methods
|
||||
this.originalMongoConnect = Meteor.connect;
|
||||
this.originalMongoCollection = Mongo.Collection;
|
||||
|
||||
// Override Meteor's connection method
|
||||
this.overrideMeteorConnection();
|
||||
|
||||
// Override Mongo.Collection to use our connection manager
|
||||
this.overrideMongoCollection();
|
||||
|
||||
this.isInitialized = true;
|
||||
console.log('Meteor MongoDB Integration initialized successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Override Meteor's connection method
|
||||
*/
|
||||
overrideMeteorConnection() {
|
||||
const self = this;
|
||||
|
||||
// Override Meteor.connect if it exists
|
||||
if (typeof Meteor.connect === 'function') {
|
||||
Meteor.connect = async function(url, options) {
|
||||
try {
|
||||
console.log('Meteor.connect called, using custom MongoDB connection manager');
|
||||
return await self.createCustomConnection(url, options);
|
||||
} catch (error) {
|
||||
console.error('Custom connection failed, falling back to original method:', error.message);
|
||||
return self.originalMongoConnect.call(this, url, options);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override Mongo.Collection to use our connection manager
|
||||
*/
|
||||
overrideMongoCollection() {
|
||||
const self = this;
|
||||
const originalCollection = Mongo.Collection;
|
||||
|
||||
// Override Mongo.Collection constructor
|
||||
Mongo.Collection = function(name, options = {}) {
|
||||
// If we have a custom connection, use it
|
||||
if (self.customConnection) {
|
||||
options.connection = self.customConnection;
|
||||
}
|
||||
|
||||
// Create the collection with original constructor
|
||||
const collection = new originalCollection(name, options);
|
||||
|
||||
// Add our custom methods
|
||||
self.enhanceCollection(collection);
|
||||
|
||||
return collection;
|
||||
};
|
||||
|
||||
// Copy static methods from original constructor
|
||||
Object.setPrototypeOf(Mongo.Collection, originalCollection);
|
||||
Object.assign(Mongo.Collection, originalCollection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a custom MongoDB connection
|
||||
* @param {string} url - MongoDB connection URL
|
||||
* @param {Object} options - Connection options
|
||||
* @returns {Promise<Object>} - MongoDB connection object
|
||||
*/
|
||||
async createCustomConnection(url, options = {}) {
|
||||
try {
|
||||
console.log('Creating custom MongoDB connection...');
|
||||
|
||||
// Use our connection manager
|
||||
const connection = await mongodbConnectionManager.createConnection(url, options);
|
||||
|
||||
// Store the custom connection
|
||||
this.customConnection = connection;
|
||||
|
||||
// Create a Meteor-compatible connection object
|
||||
const meteorConnection = this.createMeteorCompatibleConnection(connection);
|
||||
|
||||
console.log('Custom MongoDB connection created successfully');
|
||||
return meteorConnection;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to create custom MongoDB connection:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Meteor-compatible connection object
|
||||
* @param {Object} connection - MongoDB connection object
|
||||
* @returns {Object} - Meteor-compatible connection
|
||||
*/
|
||||
createMeteorCompatibleConnection(connection) {
|
||||
const self = this;
|
||||
|
||||
return {
|
||||
// Basic connection properties
|
||||
_driver: connection,
|
||||
_name: 'custom-mongodb-connection',
|
||||
|
||||
// Collection creation method
|
||||
createCollection: function(name, options = {}) {
|
||||
const db = connection.db();
|
||||
return db.collection(name);
|
||||
},
|
||||
|
||||
// Database access
|
||||
db: function(name = 'meteor') {
|
||||
return connection.db(name);
|
||||
},
|
||||
|
||||
// Connection status
|
||||
status: function() {
|
||||
return {
|
||||
status: 'connected',
|
||||
connected: true,
|
||||
retryCount: 0
|
||||
};
|
||||
},
|
||||
|
||||
// Close connection
|
||||
close: async function() {
|
||||
try {
|
||||
await connection.close();
|
||||
self.customConnection = null;
|
||||
console.log('Meteor-compatible connection closed');
|
||||
} catch (error) {
|
||||
console.error('Error closing Meteor-compatible connection:', error.message);
|
||||
}
|
||||
},
|
||||
|
||||
// Raw connection access
|
||||
rawConnection: connection
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhance a collection with additional methods
|
||||
* @param {Object} collection - Mongo.Collection instance
|
||||
*/
|
||||
enhanceCollection(collection) {
|
||||
const self = this;
|
||||
|
||||
// Add connection info method
|
||||
collection.getConnectionInfo = function() {
|
||||
if (self.customConnection) {
|
||||
const stats = mongodbConnectionManager.getConnectionStats();
|
||||
const driverStats = mongodbDriverManager.getConnectionStats();
|
||||
return {
|
||||
connectionType: 'custom',
|
||||
driver: driverStats.currentDriver,
|
||||
version: driverStats.detectedVersion,
|
||||
connectionStats: stats,
|
||||
driverStats: driverStats
|
||||
};
|
||||
}
|
||||
return {
|
||||
connectionType: 'default',
|
||||
driver: 'meteor-default',
|
||||
version: 'unknown'
|
||||
};
|
||||
};
|
||||
|
||||
// Add version detection method
|
||||
collection.detectMongoDBVersion = async function() {
|
||||
try {
|
||||
if (self.customConnection) {
|
||||
const admin = self.customConnection.db().admin();
|
||||
const buildInfo = await admin.buildInfo();
|
||||
return buildInfo.version;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Error detecting MongoDB version:', error.message);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection statistics
|
||||
* @returns {Object} - Connection statistics
|
||||
*/
|
||||
getStats() {
|
||||
return {
|
||||
isInitialized: this.isInitialized,
|
||||
hasCustomConnection: !!this.customConnection,
|
||||
connectionString: this.connectionString,
|
||||
connectionStats: mongodbConnectionManager.getConnectionStats(),
|
||||
driverStats: mongodbDriverManager.getConnectionStats()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the integration
|
||||
*/
|
||||
reset() {
|
||||
if (this.originalMongoConnect) {
|
||||
Meteor.connect = this.originalMongoConnect;
|
||||
}
|
||||
|
||||
if (this.originalMongoCollection) {
|
||||
Mongo.Collection = this.originalMongoCollection;
|
||||
}
|
||||
|
||||
this.isInitialized = false;
|
||||
this.connectionString = null;
|
||||
this.customConnection = null;
|
||||
|
||||
mongodbConnectionManager.reset();
|
||||
mongodbDriverManager.reset();
|
||||
|
||||
console.log('Meteor MongoDB Integration reset');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the connection
|
||||
* @returns {Promise<Object>} - Test results
|
||||
*/
|
||||
async testConnection() {
|
||||
try {
|
||||
if (!this.customConnection) {
|
||||
throw new Error('No custom connection available');
|
||||
}
|
||||
|
||||
const db = this.customConnection.db();
|
||||
const result = await db.admin().ping();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result,
|
||||
driver: mongodbDriverManager.selectedDriver,
|
||||
version: mongodbDriverManager.detectedVersion
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
driver: mongodbDriverManager.selectedDriver,
|
||||
version: mongodbDriverManager.detectedVersion
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create singleton instance
|
||||
const meteorMongoIntegration = new MeteorMongoIntegration();
|
||||
|
||||
// Export for use in other modules
|
||||
export { meteorMongoIntegration, MeteorMongoIntegration };
|
||||
|
||||
// Auto-initialize if MONGO_URL is available
|
||||
if (Meteor.isServer && process.env.MONGO_URL) {
|
||||
console.log('Auto-initializing Meteor MongoDB Integration with MONGO_URL');
|
||||
meteorMongoIntegration.initialize(process.env.MONGO_URL);
|
||||
}
|
||||
|
||||
// Log initialization
|
||||
if (Meteor.isServer) {
|
||||
console.log('Meteor MongoDB Integration module loaded');
|
||||
}
|
||||
294
models/lib/mongodbConnectionManager.js
Normal file
294
models/lib/mongodbConnectionManager.js
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import { mongodbDriverManager } from './mongodbDriverManager';
|
||||
|
||||
/**
|
||||
* MongoDB Connection Manager
|
||||
*
|
||||
* This module handles MongoDB connections with automatic driver selection
|
||||
* based on detected MongoDB server version and wire protocol compatibility.
|
||||
*
|
||||
* Features:
|
||||
* - Automatic driver selection based on MongoDB version
|
||||
* - Connection retry with different drivers on wire protocol errors
|
||||
* - Fallback mechanism for unsupported versions
|
||||
* - Connection pooling and management
|
||||
*/
|
||||
|
||||
class MongoDBConnectionManager {
|
||||
constructor() {
|
||||
this.connections = new Map();
|
||||
this.connectionConfigs = new Map();
|
||||
this.retryAttempts = 3;
|
||||
this.retryDelay = 1000; // 1 second
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a MongoDB connection with automatic driver selection
|
||||
* @param {string} connectionString - MongoDB connection string
|
||||
* @param {Object} options - Connection options
|
||||
* @returns {Promise<Object>} - MongoDB connection object
|
||||
*/
|
||||
async createConnection(connectionString, options = {}) {
|
||||
const connectionId = this.generateConnectionId(connectionString);
|
||||
|
||||
// Check if we already have a working connection
|
||||
if (this.connections.has(connectionId)) {
|
||||
const existingConnection = this.connections.get(connectionId);
|
||||
if (existingConnection.status === 'connected') {
|
||||
return existingConnection;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to connect with automatic driver selection
|
||||
return await this.connectWithDriverSelection(connectionString, options, connectionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect with automatic driver selection and retry logic
|
||||
* @param {string} connectionString - MongoDB connection string
|
||||
* @param {Object} options - Connection options
|
||||
* @param {string} connectionId - Connection identifier
|
||||
* @returns {Promise<Object>} - MongoDB connection object
|
||||
*/
|
||||
async connectWithDriverSelection(connectionString, options, connectionId) {
|
||||
let lastError = null;
|
||||
let currentDriver = null;
|
||||
|
||||
// First, try with the default driver (if we have a detected version)
|
||||
if (mongodbDriverManager.detectedVersion) {
|
||||
currentDriver = mongodbDriverManager.getDriverForVersion(mongodbDriverManager.detectedVersion);
|
||||
} else {
|
||||
// Start with the most recent driver
|
||||
currentDriver = 'mongodb8legacy';
|
||||
}
|
||||
|
||||
// Try connection with different drivers
|
||||
for (let attempt = 0; attempt < this.retryAttempts; attempt++) {
|
||||
try {
|
||||
console.log(`Attempting MongoDB connection with driver: ${currentDriver} (attempt ${attempt + 1})`);
|
||||
|
||||
const connection = await this.connectWithDriver(currentDriver, connectionString, options);
|
||||
|
||||
// Record successful connection
|
||||
mongodbDriverManager.recordConnectionAttempt(
|
||||
currentDriver,
|
||||
mongodbDriverManager.detectedVersion || 'unknown',
|
||||
true
|
||||
);
|
||||
|
||||
// Store connection
|
||||
this.connections.set(connectionId, {
|
||||
connection,
|
||||
driver: currentDriver,
|
||||
version: mongodbDriverManager.detectedVersion || 'unknown',
|
||||
status: 'connected',
|
||||
connectionString,
|
||||
options,
|
||||
createdAt: new Date()
|
||||
});
|
||||
|
||||
return connection;
|
||||
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
console.error(`Connection attempt ${attempt + 1} failed with driver ${currentDriver}:`, error.message);
|
||||
|
||||
// Try to detect MongoDB version from error
|
||||
const detectedVersion = mongodbDriverManager.detectVersionFromError(error);
|
||||
if (detectedVersion && detectedVersion !== 'unknown') {
|
||||
mongodbDriverManager.detectedVersion = detectedVersion;
|
||||
currentDriver = mongodbDriverManager.getDriverForVersion(detectedVersion);
|
||||
console.log(`Detected MongoDB version ${detectedVersion}, switching to driver ${currentDriver}`);
|
||||
} else {
|
||||
// Try next fallback driver
|
||||
const nextDriver = mongodbDriverManager.getNextFallbackDriver();
|
||||
if (nextDriver) {
|
||||
currentDriver = nextDriver;
|
||||
console.log(`Trying fallback driver: ${currentDriver}`);
|
||||
} else {
|
||||
console.error('No more fallback drivers available');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Record failed attempt
|
||||
mongodbDriverManager.recordConnectionAttempt(
|
||||
currentDriver,
|
||||
detectedVersion || 'unknown',
|
||||
false,
|
||||
error
|
||||
);
|
||||
|
||||
// Wait before retry
|
||||
if (attempt < this.retryAttempts - 1) {
|
||||
await this.delay(this.retryDelay * (attempt + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All attempts failed
|
||||
throw new Error(`Failed to connect to MongoDB after ${this.retryAttempts} attempts. Last error: ${lastError?.message}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect using a specific driver
|
||||
* @param {string} driverName - Driver package name
|
||||
* @param {string} connectionString - MongoDB connection string
|
||||
* @param {Object} options - Connection options
|
||||
* @returns {Promise<Object>} - MongoDB connection object
|
||||
*/
|
||||
async connectWithDriver(driverName, connectionString, options) {
|
||||
try {
|
||||
// Dynamically import the driver
|
||||
const driver = await import(driverName);
|
||||
const MongoClient = driver.MongoClient;
|
||||
|
||||
// Set default options
|
||||
const defaultOptions = {
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true,
|
||||
maxPoolSize: 10,
|
||||
serverSelectionTimeoutMS: 5000,
|
||||
socketTimeoutMS: 45000,
|
||||
...options
|
||||
};
|
||||
|
||||
// Create connection
|
||||
const client = new MongoClient(connectionString, defaultOptions);
|
||||
await client.connect();
|
||||
|
||||
// Test the connection
|
||||
await client.db('admin').admin().ping();
|
||||
|
||||
return client;
|
||||
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to connect with driver ${driverName}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection by ID
|
||||
* @param {string} connectionId - Connection identifier
|
||||
* @returns {Object|null} - Connection object or null
|
||||
*/
|
||||
getConnection(connectionId) {
|
||||
return this.connections.get(connectionId) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a connection
|
||||
* @param {string} connectionId - Connection identifier
|
||||
* @returns {Promise<boolean>} - Whether connection was closed successfully
|
||||
*/
|
||||
async closeConnection(connectionId) {
|
||||
const connection = this.connections.get(connectionId);
|
||||
if (connection && connection.connection) {
|
||||
try {
|
||||
await connection.connection.close();
|
||||
this.connections.delete(connectionId);
|
||||
console.log(`Closed MongoDB connection: ${connectionId}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`Error closing MongoDB connection ${connectionId}:`, error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all connections
|
||||
* @returns {Promise<number>} - Number of connections closed
|
||||
*/
|
||||
async closeAllConnections() {
|
||||
let closedCount = 0;
|
||||
const connectionIds = Array.from(this.connections.keys());
|
||||
|
||||
for (const connectionId of connectionIds) {
|
||||
if (await this.closeConnection(connectionId)) {
|
||||
closedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Closed ${closedCount} MongoDB connections`);
|
||||
return closedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection statistics
|
||||
* @returns {Object} - Connection statistics
|
||||
*/
|
||||
getConnectionStats() {
|
||||
const connections = Array.from(this.connections.values());
|
||||
const connected = connections.filter(conn => conn.status === 'connected').length;
|
||||
const disconnected = connections.length - connected;
|
||||
|
||||
return {
|
||||
total: connections.length,
|
||||
connected,
|
||||
disconnected,
|
||||
connections: connections.map(conn => ({
|
||||
id: this.getConnectionIdFromConnection(conn),
|
||||
driver: conn.driver,
|
||||
version: conn.version,
|
||||
status: conn.status,
|
||||
createdAt: conn.createdAt
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique connection ID
|
||||
* @param {string} connectionString - MongoDB connection string
|
||||
* @returns {string} - Unique connection ID
|
||||
*/
|
||||
generateConnectionId(connectionString) {
|
||||
// Create a hash of the connection string for unique ID
|
||||
let hash = 0;
|
||||
for (let i = 0; i < connectionString.length; i++) {
|
||||
const char = connectionString.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash; // Convert to 32-bit integer
|
||||
}
|
||||
return `mongodb_${Math.abs(hash)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection ID from connection object
|
||||
* @param {Object} connection - Connection object
|
||||
* @returns {string} - Connection ID
|
||||
*/
|
||||
getConnectionIdFromConnection(connection) {
|
||||
return this.generateConnectionId(connection.connectionString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to delay execution
|
||||
* @param {number} ms - Milliseconds to delay
|
||||
* @returns {Promise} - Promise that resolves after delay
|
||||
*/
|
||||
delay(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all connections and driver manager state
|
||||
*/
|
||||
reset() {
|
||||
this.connections.clear();
|
||||
this.connectionConfigs.clear();
|
||||
mongodbDriverManager.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Create singleton instance
|
||||
const mongodbConnectionManager = new MongoDBConnectionManager();
|
||||
|
||||
// Export for use in other modules
|
||||
export { mongodbConnectionManager, MongoDBConnectionManager };
|
||||
|
||||
// Log initialization
|
||||
if (Meteor.isServer) {
|
||||
console.log('MongoDB Connection Manager initialized');
|
||||
}
|
||||
277
models/lib/mongodbDriverManager.js
Normal file
277
models/lib/mongodbDriverManager.js
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
/**
|
||||
* MongoDB Driver Manager
|
||||
*
|
||||
* This module provides automatic MongoDB version detection and driver selection
|
||||
* to support MongoDB versions 3.0 through 8.0 with compatible Node.js drivers.
|
||||
*
|
||||
* Features:
|
||||
* - Automatic MongoDB version detection from wire protocol errors
|
||||
* - Dynamic driver selection based on detected version
|
||||
* - Fallback mechanism for unsupported versions
|
||||
* - Connection retry with different drivers
|
||||
*/
|
||||
|
||||
// MongoDB driver compatibility matrix
|
||||
const DRIVER_COMPATIBILITY = {
|
||||
'3.0': { driver: 'mongodb3legacy', version: '3.7.4', minServer: '3.0', maxServer: '3.6' },
|
||||
'3.2': { driver: 'mongodb3legacy', version: '3.7.4', minServer: '3.0', maxServer: '3.6' },
|
||||
'3.4': { driver: 'mongodb3legacy', version: '3.7.4', minServer: '3.0', maxServer: '3.6' },
|
||||
'3.6': { driver: 'mongodb3legacy', version: '3.7.4', minServer: '3.0', maxServer: '3.6' },
|
||||
'4.0': { driver: 'mongodb4legacy', version: '4.17.2', minServer: '4.0', maxServer: '4.4' },
|
||||
'4.2': { driver: 'mongodb4legacy', version: '4.17.2', minServer: '4.0', maxServer: '4.4' },
|
||||
'4.4': { driver: 'mongodb4legacy', version: '4.17.2', minServer: '4.0', maxServer: '4.4' },
|
||||
'5.0': { driver: 'mongodb5legacy', version: '5.9.2', minServer: '5.0', maxServer: '5.0' },
|
||||
'6.0': { driver: 'mongodb6legacy', version: '6.3.0', minServer: '6.0', maxServer: '6.0' },
|
||||
'7.0': { driver: 'mongodb7legacy', version: '7.0.1', minServer: '7.0', maxServer: '7.0' },
|
||||
'8.0': { driver: 'mongodb8legacy', version: '6.9.0', minServer: '8.0', maxServer: '8.0' }
|
||||
};
|
||||
|
||||
// Wire protocol error patterns for version detection
|
||||
const VERSION_ERROR_PATTERNS = {
|
||||
// MongoDB 3.x wire protocol errors
|
||||
'3.0': [
|
||||
/unsupported wire protocol version/i,
|
||||
/wire protocol version 0/i,
|
||||
/protocol version 0/i
|
||||
],
|
||||
'3.2': [
|
||||
/wire protocol version 1/i,
|
||||
/protocol version 1/i
|
||||
],
|
||||
'3.4': [
|
||||
/wire protocol version 2/i,
|
||||
/protocol version 2/i
|
||||
],
|
||||
'3.6': [
|
||||
/wire protocol version 3/i,
|
||||
/protocol version 3/i
|
||||
],
|
||||
// MongoDB 4.x wire protocol errors
|
||||
'4.0': [
|
||||
/wire protocol version 4/i,
|
||||
/protocol version 4/i
|
||||
],
|
||||
'4.2': [
|
||||
/wire protocol version 5/i,
|
||||
/protocol version 5/i
|
||||
],
|
||||
'4.4': [
|
||||
/wire protocol version 6/i,
|
||||
/protocol version 6/i
|
||||
],
|
||||
// MongoDB 5.x wire protocol errors
|
||||
'5.0': [
|
||||
/wire protocol version 7/i,
|
||||
/protocol version 7/i
|
||||
],
|
||||
// MongoDB 6.x wire protocol errors
|
||||
'6.0': [
|
||||
/wire protocol version 8/i,
|
||||
/protocol version 8/i
|
||||
],
|
||||
// MongoDB 7.x wire protocol errors
|
||||
'7.0': [
|
||||
/wire protocol version 9/i,
|
||||
/protocol version 9/i
|
||||
],
|
||||
// MongoDB 8.x wire protocol errors
|
||||
'8.0': [
|
||||
/wire protocol version 10/i,
|
||||
/protocol version 10/i
|
||||
]
|
||||
};
|
||||
|
||||
// Generic error patterns that might indicate version incompatibility
|
||||
const GENERIC_VERSION_ERRORS = [
|
||||
/unsupported wire protocol/i,
|
||||
/wire protocol version/i,
|
||||
/protocol version/i,
|
||||
/unsupported server version/i,
|
||||
/server version/i,
|
||||
/incompatible wire protocol/i,
|
||||
/wire protocol mismatch/i
|
||||
];
|
||||
|
||||
class MongoDBDriverManager {
|
||||
constructor() {
|
||||
this.detectedVersion = null;
|
||||
this.selectedDriver = null;
|
||||
this.connectionAttempts = [];
|
||||
this.fallbackDrivers = ['mongodb6legacy', 'mongodb4legacy', 'mongodb3legacy'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect MongoDB version from wire protocol errors
|
||||
* @param {Error} error - The connection error
|
||||
* @returns {string|null} - Detected MongoDB version or null
|
||||
*/
|
||||
detectVersionFromError(error) {
|
||||
if (!error || !error.message) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const errorMessage = error.message.toLowerCase();
|
||||
|
||||
// Check specific version patterns
|
||||
for (const [version, patterns] of Object.entries(VERSION_ERROR_PATTERNS)) {
|
||||
for (const pattern of patterns) {
|
||||
if (pattern.test(errorMessage)) {
|
||||
console.log(`MongoDB version detected from error: ${version}`);
|
||||
return version;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for generic version errors
|
||||
for (const pattern of GENERIC_VERSION_ERRORS) {
|
||||
if (pattern.test(errorMessage)) {
|
||||
console.log('Generic MongoDB version error detected, will try fallback drivers');
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate driver for a MongoDB version
|
||||
* @param {string} version - MongoDB version
|
||||
* @returns {string} - Driver package name
|
||||
*/
|
||||
getDriverForVersion(version) {
|
||||
if (DRIVER_COMPATIBILITY[version]) {
|
||||
return DRIVER_COMPATIBILITY[version].driver;
|
||||
}
|
||||
|
||||
// Fallback logic for unknown versions
|
||||
if (version === 'unknown') {
|
||||
return this.fallbackDrivers[0]; // Start with most recent fallback
|
||||
}
|
||||
|
||||
// Try to determine from version string
|
||||
const majorVersion = version.split('.')[0];
|
||||
if (majorVersion === '3') {
|
||||
return 'mongodb3legacy';
|
||||
} else if (majorVersion === '4') {
|
||||
return 'mongodb4legacy';
|
||||
} else if (majorVersion === '5') {
|
||||
return 'mongodb5legacy';
|
||||
} else if (majorVersion === '6') {
|
||||
return 'mongodb6legacy';
|
||||
} else if (majorVersion === '7') {
|
||||
return 'mongodb7legacy';
|
||||
} else if (majorVersion === '8') {
|
||||
return 'mongodb8legacy';
|
||||
}
|
||||
|
||||
return this.fallbackDrivers[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next fallback driver
|
||||
* @returns {string|null} - Next fallback driver or null if none left
|
||||
*/
|
||||
getNextFallbackDriver() {
|
||||
const currentIndex = this.fallbackDrivers.indexOf(this.selectedDriver);
|
||||
if (currentIndex >= 0 && currentIndex < this.fallbackDrivers.length - 1) {
|
||||
return this.fallbackDrivers[currentIndex + 1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a connection attempt
|
||||
* @param {string} driver - Driver used
|
||||
* @param {string} version - MongoDB version attempted
|
||||
* @param {boolean} success - Whether connection was successful
|
||||
* @param {Error} error - Error if connection failed
|
||||
*/
|
||||
recordConnectionAttempt(driver, version, success, error = null) {
|
||||
this.connectionAttempts.push({
|
||||
driver,
|
||||
version,
|
||||
success,
|
||||
error: error ? error.message : null,
|
||||
timestamp: new Date()
|
||||
});
|
||||
|
||||
if (success) {
|
||||
this.selectedDriver = driver;
|
||||
this.detectedVersion = version;
|
||||
console.log(`Successfully connected using ${driver} for MongoDB ${version}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection statistics
|
||||
* @returns {Object} - Connection statistics
|
||||
*/
|
||||
getConnectionStats() {
|
||||
const total = this.connectionAttempts.length;
|
||||
const successful = this.connectionAttempts.filter(attempt => attempt.success).length;
|
||||
const failed = total - successful;
|
||||
|
||||
return {
|
||||
total,
|
||||
successful,
|
||||
failed,
|
||||
currentDriver: this.selectedDriver,
|
||||
detectedVersion: this.detectedVersion,
|
||||
attempts: this.connectionAttempts
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset connection state
|
||||
*/
|
||||
reset() {
|
||||
this.detectedVersion = null;
|
||||
this.selectedDriver = null;
|
||||
this.connectionAttempts = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get driver information
|
||||
* @param {string} driver - Driver package name
|
||||
* @returns {Object} - Driver information
|
||||
*/
|
||||
getDriverInfo(driver) {
|
||||
for (const [version, info] of Object.entries(DRIVER_COMPATIBILITY)) {
|
||||
if (info.driver === driver) {
|
||||
return { version, ...info };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all supported MongoDB versions
|
||||
* @returns {Array} - Array of supported versions
|
||||
*/
|
||||
getSupportedVersions() {
|
||||
return Object.keys(DRIVER_COMPATIBILITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a MongoDB version is supported
|
||||
* @param {string} version - MongoDB version to check
|
||||
* @returns {boolean} - Whether version is supported
|
||||
*/
|
||||
isVersionSupported(version) {
|
||||
return version in DRIVER_COMPATIBILITY;
|
||||
}
|
||||
}
|
||||
|
||||
// Create singleton instance
|
||||
const mongodbDriverManager = new MongoDBDriverManager();
|
||||
|
||||
// Export for use in other modules
|
||||
export { mongodbDriverManager, MongoDBDriverManager };
|
||||
|
||||
// Log initialization
|
||||
if (Meteor.isServer) {
|
||||
console.log('MongoDB Driver Manager initialized');
|
||||
console.log(`Supported MongoDB versions: ${mongodbDriverManager.getSupportedVersions().join(', ')}`);
|
||||
}
|
||||
|
|
@ -1836,7 +1836,7 @@ if (Meteor.isServer) {
|
|||
},
|
||||
});
|
||||
Accounts.onCreateUser((options, user) => {
|
||||
const userCount = ReactiveCache.getUsers({}, {}, true).count();
|
||||
const userCount = ReactiveCache.getUsers({}, {}, true).countDocuments();
|
||||
user.isAdmin = userCount === 0;
|
||||
|
||||
if (user.services.oidc) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue