import * as plugins from '../plugins.js'; import type * as interfaces from '../interfaces/index.js'; import type { TsView } from '../tsview.classes.tsview.js'; /** * Register MongoDB API handlers */ export async function registerMongoHandlers( typedrouter: plugins.typedrequest.TypedRouter, tsview: TsView ): Promise { // Helper to get the native MongoDB client const getMongoClient = async () => { const db = await tsview.getMongoDb(); if (!db) { throw new Error('MongoDB not configured'); } // Access the underlying MongoDB client through smartdata return (db as any).mongoDbClient; }; // Helper to create ObjectId filter const createIdFilter = (documentId: string) => { // Try to treat as ObjectId string - MongoDB driver will handle conversion try { // Import ObjectId from the mongodb package that smartdata uses const { ObjectId } = require('mongodb'); if (ObjectId.isValid(documentId)) { return { _id: new ObjectId(documentId) }; } } catch { // Fall through to string filter } return { _id: documentId }; }; // List databases typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'listDatabases', async () => { try { const client = await getMongoClient(); const adminDb = client.db().admin(); const result = await adminDb.listDatabases(); const databases: interfaces.IMongoDatabase[] = result.databases.map((db: any) => ({ name: db.name, sizeOnDisk: db.sizeOnDisk, empty: db.empty, })); return { databases }; } catch (err) { console.error('Error listing databases:', err); return { databases: [] }; } } ) ); // List collections typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'listCollections', async (reqData) => { try { const client = await getMongoClient(); const db = client.db(reqData.databaseName); const collectionsInfo = await db.listCollections().toArray(); const collections: interfaces.IMongoCollection[] = []; for (const coll of collectionsInfo) { const stats = await db.collection(coll.name).estimatedDocumentCount(); collections.push({ name: coll.name, count: stats, }); } return { collections }; } catch (err) { console.error('Error listing collections:', err); return { collections: [] }; } } ) ); // Create collection typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'createCollection', async (reqData) => { try { const client = await getMongoClient(); const db = client.db(reqData.databaseName); await db.createCollection(reqData.collectionName); return { success: true }; } catch (err) { console.error('Error creating collection:', err); return { success: false }; } } ) ); // Find documents typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'findDocuments', async (reqData) => { try { const client = await getMongoClient(); const db = client.db(reqData.databaseName); const collection = db.collection(reqData.collectionName); const filter = reqData.filter || {}; const projection = reqData.projection || {}; const sort = reqData.sort || {}; const skip = reqData.skip || 0; const limit = reqData.limit || 50; const [documents, total] = await Promise.all([ collection .find(filter) .project(projection) .sort(sort) .skip(skip) .limit(limit) .toArray(), collection.countDocuments(filter), ]); // Convert ObjectId to string for JSON serialization const serializedDocs = documents.map((doc: any) => { if (doc._id) { doc._id = doc._id.toString(); } return doc; }); return { documents: serializedDocs, total }; } catch (err) { console.error('Error finding documents:', err); return { documents: [], total: 0 }; } } ) ); // Get single document typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getDocument', async (reqData) => { try { const client = await getMongoClient(); const db = client.db(reqData.databaseName); const collection = db.collection(reqData.collectionName); const filter = createIdFilter(reqData.documentId); const document = await collection.findOne(filter); if (document && document._id) { document._id = document._id.toString(); } return { document }; } catch (err) { console.error('Error getting document:', err); return { document: null }; } } ) ); // Insert document typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'insertDocument', async (reqData) => { try { const client = await getMongoClient(); const db = client.db(reqData.databaseName); const collection = db.collection(reqData.collectionName); const result = await collection.insertOne(reqData.document); return { insertedId: result.insertedId.toString() }; } catch (err) { console.error('Error inserting document:', err); throw err; } } ) ); // Update document typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'updateDocument', async (reqData) => { try { const client = await getMongoClient(); const db = client.db(reqData.databaseName); const collection = db.collection(reqData.collectionName); const filter = createIdFilter(reqData.documentId); // Check if update has $ operators const hasOperators = Object.keys(reqData.update).some(k => k.startsWith('$')); const updateDoc = hasOperators ? reqData.update : { $set: reqData.update }; const result = await collection.updateOne(filter, updateDoc); return { success: result.modifiedCount > 0 || result.matchedCount > 0, modifiedCount: result.modifiedCount, }; } catch (err) { console.error('Error updating document:', err); return { success: false, modifiedCount: 0 }; } } ) ); // Delete document typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'deleteDocument', async (reqData) => { try { const client = await getMongoClient(); const db = client.db(reqData.databaseName); const collection = db.collection(reqData.collectionName); const filter = createIdFilter(reqData.documentId); const result = await collection.deleteOne(filter); return { success: result.deletedCount > 0, deletedCount: result.deletedCount, }; } catch (err) { console.error('Error deleting document:', err); return { success: false, deletedCount: 0 }; } } ) ); // Run aggregation pipeline typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'runAggregation', async (reqData) => { try { const client = await getMongoClient(); const db = client.db(reqData.databaseName); const collection = db.collection(reqData.collectionName); const results = await collection.aggregate(reqData.pipeline).toArray(); // Convert ObjectIds to strings const serializedResults = results.map((doc: any) => { if (doc._id) { doc._id = doc._id.toString(); } return doc; }); return { results: serializedResults }; } catch (err) { console.error('Error running aggregation:', err); return { results: [] }; } } ) ); // List indexes typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'listIndexes', async (reqData) => { try { const client = await getMongoClient(); const db = client.db(reqData.databaseName); const collection = db.collection(reqData.collectionName); const indexesInfo = await collection.indexes(); const indexes: interfaces.IMongoIndex[] = indexesInfo.map((idx: any) => ({ name: idx.name, keys: idx.key, unique: idx.unique || false, sparse: idx.sparse || false, })); return { indexes }; } catch (err) { console.error('Error listing indexes:', err); return { indexes: [] }; } } ) ); // Create index typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'createIndex', async (reqData) => { try { const client = await getMongoClient(); const db = client.db(reqData.databaseName); const collection = db.collection(reqData.collectionName); const indexName = await collection.createIndex(reqData.keys, reqData.options || {}); return { indexName }; } catch (err) { console.error('Error creating index:', err); throw err; } } ) ); // Drop index typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'dropIndex', async (reqData) => { try { const client = await getMongoClient(); const db = client.db(reqData.databaseName); const collection = db.collection(reqData.collectionName); await collection.dropIndex(reqData.indexName); return { success: true }; } catch (err) { console.error('Error dropping index:', err); return { success: false }; } } ) ); // Get collection stats typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getCollectionStats', async (reqData) => { try { const client = await getMongoClient(); const db = client.db(reqData.databaseName); const collection = db.collection(reqData.collectionName); const stats = await db.command({ collStats: reqData.collectionName }); const indexCount = (await collection.indexes()).length; return { stats: { count: stats.count || 0, size: stats.size || 0, avgObjSize: stats.avgObjSize || 0, storageSize: stats.storageSize || 0, indexCount, }, }; } catch (err) { console.error('Error getting collection stats:', err); return { stats: { count: 0, size: 0, avgObjSize: 0, storageSize: 0, indexCount: 0, }, }; } } ) ); // Get server status typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getServerStatus', async () => { try { const client = await getMongoClient(); const adminDb = client.db().admin(); const serverInfo = await adminDb.serverInfo(); const serverStatus = await adminDb.serverStatus(); return { version: serverInfo.version || 'unknown', uptime: serverStatus.uptime || 0, connections: { current: serverStatus.connections?.current || 0, available: serverStatus.connections?.available || 0, }, }; } catch (err) { console.error('Error getting server status:', err); return { version: 'unknown', uptime: 0, connections: { current: 0, available: 0 }, }; } } ) ); }