477 lines
14 KiB
TypeScript
477 lines
14 KiB
TypeScript
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<void> {
|
|
// 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<interfaces.IReq_ListDatabases>(
|
|
'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<interfaces.IReq_ListCollections>(
|
|
'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 database (by creating a placeholder collection)
|
|
typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<interfaces.IReq_CreateDatabase>(
|
|
'createDatabase',
|
|
async (reqData) => {
|
|
try {
|
|
const client = await getMongoClient();
|
|
const db = client.db(reqData.databaseName);
|
|
// Create a placeholder collection to materialize the database
|
|
await db.createCollection('_tsview_init');
|
|
return { success: true };
|
|
} catch (err) {
|
|
console.error('Error creating database:', err);
|
|
return { success: false };
|
|
}
|
|
}
|
|
)
|
|
);
|
|
|
|
// Drop database
|
|
typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<interfaces.IReq_DropDatabase>(
|
|
'dropDatabase',
|
|
async (reqData) => {
|
|
try {
|
|
const client = await getMongoClient();
|
|
const db = client.db(reqData.databaseName);
|
|
await db.dropDatabase();
|
|
return { success: true };
|
|
} catch (err) {
|
|
console.error('Error dropping database:', err);
|
|
return { success: false };
|
|
}
|
|
}
|
|
)
|
|
);
|
|
|
|
// Create collection
|
|
typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<interfaces.IReq_CreateCollection>(
|
|
'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 };
|
|
}
|
|
}
|
|
)
|
|
);
|
|
|
|
// Drop collection
|
|
typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<interfaces.IReq_DropCollection>(
|
|
'dropCollection',
|
|
async (reqData) => {
|
|
try {
|
|
const client = await getMongoClient();
|
|
const db = client.db(reqData.databaseName);
|
|
await db.dropCollection(reqData.collectionName);
|
|
return { success: true };
|
|
} catch (err) {
|
|
console.error('Error dropping collection:', err);
|
|
return { success: false };
|
|
}
|
|
}
|
|
)
|
|
);
|
|
|
|
// Find documents
|
|
typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<interfaces.IReq_FindDocuments>(
|
|
'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<interfaces.IReq_GetDocument>(
|
|
'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<interfaces.IReq_InsertDocument>(
|
|
'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<interfaces.IReq_UpdateDocument>(
|
|
'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<interfaces.IReq_DeleteDocument>(
|
|
'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<interfaces.IReq_RunAggregation>(
|
|
'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<interfaces.IReq_ListIndexes>(
|
|
'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<interfaces.IReq_CreateIndex>(
|
|
'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<interfaces.IReq_DropIndex>(
|
|
'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<interfaces.IReq_GetCollectionStats>(
|
|
'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<interfaces.IReq_GetServerStatus>(
|
|
'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 },
|
|
};
|
|
}
|
|
}
|
|
)
|
|
);
|
|
}
|