UNPKG

survey-mcp-server

Version:

Survey management server handling survey creation, response collection, analysis, and reporting with database access for data management

335 lines 13 kB
import { MongoClient } from 'mongodb'; import { config } from './config.js'; import { logger } from './logger.js'; export class MongoDBConnection { constructor(connectionString, databaseName) { this.client = null; this.db = null; this.connectionString = connectionString; this.databaseName = databaseName; } async connect() { if (this.client) { return; // Already connected } try { this.client = new MongoClient(this.connectionString); await this.client.connect(); this.db = this.client.db(this.databaseName); logger.info(`Connected to MongoDB database: ${this.databaseName}`); } catch (error) { logger.error(`Failed to connect to MongoDB database ${this.databaseName}:`, error); throw error; } } async disconnect() { if (this.client) { await this.client.close(); this.client = null; this.db = null; logger.info(`Disconnected from MongoDB database: ${this.databaseName}`); } } getDatabase() { if (!this.db) { throw new Error(`Database ${this.databaseName} is not connected`); } return this.db; } getCollection(name) { return this.getDatabase().collection(name); } } export class MongoDBClient { constructor() { this.connections = new Map(); this.initialized = false; // Don't initialize connections immediately // They will be initialized on first use or explicit initialization } static getInstance() { if (!MongoDBClient.instance) { MongoDBClient.instance = new MongoDBClient(); } return MongoDBClient.instance; } async initialize() { try { logger.info('Initializing MongoDB client with config:', { defaultUri: config.mongodb.uri ? 'configured' : 'missing', defaultDbName: config.mongodb.dbName, devSyiaApiUri: config.devSyiaApi.uri ? 'configured' : 'missing', devSyiaApiDbName: config.devSyiaApi.dbName, syiaEtlDevUri: config.syiaEtlDev.uri ? 'configured' : 'missing', syiaEtlDevDbName: config.syiaEtlDev.dbName }); // Initialize connections with current config this.connections.clear(); this.connections.set('default', new MongoDBConnection(config.mongodb.uri, config.mongodb.dbName)); this.connections.set('devSyiaApi', new MongoDBConnection(config.devSyiaApi.uri, config.devSyiaApi.dbName)); this.connections.set('syiaEtlDev', new MongoDBConnection(config.syiaEtlDev.uri, config.syiaEtlDev.dbName)); // Actually connect to all databases during initialization const connectionPromises = []; for (const [name, connection] of this.connections.entries()) { logger.info(`Connecting to MongoDB database: ${name}`); connectionPromises.push(connection.connect().then(() => { logger.info(`Successfully connected to MongoDB database: ${name}`); }).catch((error) => { logger.error(`Failed to connect to MongoDB database ${name}:`, error); throw error; })); } // Wait for all connections to complete await Promise.all(connectionPromises); this.initialized = true; logger.info('MongoDB client initialized successfully with all database connections established'); } catch (error) { logger.error('Failed to initialize MongoDB client:', error); this.initialized = false; throw error; } } async reinitialize() { logger.info('Reinitializing MongoDB client...'); await this.disconnect(); this.connections.clear(); this.initialized = false; await this.initialize(); logger.info('MongoDB client reinitialized successfully'); } async ensureInitialized() { if (!this.initialized) { await this.initialize(); } } isInitialized() { return this.initialized; } async getConnection(name = 'default') { await this.ensureInitialized(); const connection = this.connections.get(name); if (!connection) { throw new Error(`MongoDB connection '${name}' not found`); } await connection.connect(); return connection; } async getDatabase(connectionName = 'default') { const connection = await this.getConnection(connectionName); return connection.getDatabase(); } async getCollection(collectionName, connectionName = 'default') { const connection = await this.getConnection(connectionName); return connection.getCollection(collectionName); } // Legacy property for backward compatibility get db() { if (!this.initialized) { throw new Error('MongoDB client not initialized. Call initialize() first.'); } const connection = this.connections.get('default'); if (!connection) { throw new Error('Default MongoDB connection not found'); } return connection.getDatabase(); } async disconnect(connectionName) { if (connectionName) { const connection = this.connections.get(connectionName); if (connection) { await connection.disconnect(); } } else { // Disconnect all connections for (const connection of this.connections.values()) { await connection.disconnect(); } } } } // Helper function to get a MongoDB client with specific connection string export async function getMongoClient(connectionString, databaseName) { try { const client = new MongoClient(connectionString); await client.connect(); logger.info(`Connected to MongoDB: ${databaseName || 'Unknown DB'}`); return client; } catch (error) { logger.error('Failed to connect to MongoDB:', error); throw error; } } // Helper function to get component data async function getComponentData(componentId) { const match = componentId.match(/^(\d+)_(\d+)_(\d+)$/); if (!match) { return `⚠️ Invalid component_id format: ${componentId}`; } const [, componentNumber, questionNumber, imo] = match; const componentNo = `${componentNumber}_${questionNumber}_${imo}`; try { const client = MongoDBClient.getInstance(); const db = await client.getDatabase('syiaEtlDev'); const collection = db.collection('vesselinfocomponents'); const doc = await collection.findOne({ componentNo }); if (!doc) { return `⚠️ No component found for ID: ${componentId}`; } if (!doc.data) { return "No data found in the table component"; } if (!doc.data.headers || !Array.isArray(doc.data.headers)) { logger.error(`Invalid headers in component data: ${componentId}`); return "Invalid headers in table component"; } if (!doc.data.body || !Array.isArray(doc.data.body)) { logger.error(`Invalid body in component data: ${componentId}`); return "Invalid body in table component"; } // Extract headers excluding lineitem const headers = doc.data.headers .filter((h) => h && h.name !== "lineitem") .map((h) => h.name); const rows = doc.data.body; // Build markdown table let md = "| " + headers.join(" | ") + " |\n"; md += "| " + headers.map(() => "---").join(" | ") + " |\n"; for (const [rowIndex, row] of rows.entries()) { try { if (!Array.isArray(row)) continue; const formattedRow = row .filter((cell) => cell && !cell.lineitem) .map((cell) => { try { if (cell.value && cell.link) { return `[${cell.value}](${cell.link})`; } else if (cell.status && cell.color) { return cell.status; } return String(cell); } catch (cellError) { return ''; } }); if (formattedRow.length > 0) { md += "| " + formattedRow.join(" | ") + " |\n"; } } catch (rowError) { logger.error(`Error processing row ${rowIndex}:`, rowError); continue; } } return md; } catch (error) { logger.error(`Error getting component data:`, error); throw error; } } // Helper function to add component data async function addComponentData(answer, imo) { // The actual URL format in the response const pattern = /httpsdev\.syia\.ai\/chat\/ag-grid-table\?component=(\d+_\d+)/g; const matches = Array.from(answer.matchAll(pattern)); let result = answer; for (const match of matches) { const component = match[1]; try { logger.info(`Processing component ${component}_${imo}`); const replacement = await getComponentData(`${component}_${imo}`); // Replace the full URL with the table result = result.replace(match[0], replacement); logger.info(`Successfully replaced component ${component}_${imo}`); } catch (error) { logger.error(`Error replacing component ${component}_${imo}:`, error); } } return result; } // Helper function for vessel QnA operations export async function fetchQaDetails(imo, questionNo) { try { const client = MongoDBClient.getInstance(); const db = await client.getDatabase('syiaEtlDev'); const collection = db.collection('vesselinfos'); const query = { imo: imo, questionNo: parseInt(questionNo) }; const projection = { '_id': 0, 'imo': 1, 'vesselName': 1, 'refreshDate': 1, 'answer': 1 }; const result = await collection.findOne(query, { projection }); if (!result) { return { imo: imo, vesselName: null, refreshDate: null, answer: null }; } // Format refresh date if (result.refreshDate instanceof Date) { const dateStr = result.refreshDate.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' }); result.refreshDate = dateStr; } // Process answer to include component data if (result.answer) { result.answer = await addComponentData(result.answer, imo); } // Add snapshot link try { result.link = await getVesselQnaSnapshot(String(imo), questionNo); } catch (error) { logger.warn(`Failed to get QnA snapshot for IMO ${imo}, question ${questionNo}:`, error); result.link = null; } return result; } catch (error) { logger.error(`Error fetching QA details for IMO ${imo}, question ${questionNo}:`, error); throw error; } } // Helper function to get vessel QnA snapshot async function getVesselQnaSnapshot(imo, questionNo) { try { const axios = await import('axios'); const snapshotUrl = `https://app-api.siya.com/v1.0/vessel-info/qna-snapshot/${imo}/${questionNo}`; const jwtToken = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7ImlkIjoiNjRkMzdhMDM1Mjk5YjFlMDQxOTFmOTJhIiwiZmlyc3ROYW1lIjoiU3lpYSIsImxhc3ROYW1lIjoiRGV2IiwiZW1haWwiOiJkZXZAc3lpYS5haSIsInJvbGUiOiJhZG1pbiIsInJvbGVJZCI6IjVmNGUyODFkZDE4MjM0MzY4NDE1ZjViZiIsImlhdCI6MTc0MDgwODg2OH0sImlhdCI6MTc0MDgwODg2OCwiZXhwIjoxNzcyMzQ0ODY4fQ.1grxEO0aO7wfkSNDzpLMHXFYuXjaA1bBguw2SJS9r2M"; const response = await axios.default.get(snapshotUrl, { headers: { Authorization: jwtToken } }); if (response.data && response.data.resultData) { return response.data.resultData; } return response.data; } catch (error) { logger.warn(`Failed to get QnA snapshot for IMO ${imo}, question ${questionNo}:`, error); return null; } } // Export singleton instance - but don't initialize it yet export const mongoClient = MongoDBClient.getInstance(); //# sourceMappingURL=mongodb.js.map