UNPKG

defect-inspection-tools-mcp-server

Version:

Defect inspection tools server handling defect detection, analysis, and reporting with AI/ML capabilities for quality control

350 lines 14.1 kB
import { BaseService, ServiceHealthStatus } from './base-service.js'; import { SanitizationMiddleware } from '../middleware/sanitization-middleware.js'; import { ErrorHandler } from '../middleware/error-handler.js'; import { logger } from '../utils/logger.js'; import { MongoClient } from 'mongodb'; export class DatabaseService extends BaseService { constructor(config) { super('DatabaseService', config); this.client = null; this.db = null; this.isConnected = false; const baseConfig = super.getConfig(); this.config = { ...baseConfig, uri: config.uri, databaseName: config.databaseName, maxPoolSize: config.maxPoolSize || 10, minPoolSize: config.minPoolSize || 2, maxIdleTimeMS: config.maxIdleTimeMS || 30000, serverSelectionTimeoutMS: config.serverSelectionTimeoutMS || 5000 }; } // Initialize database connection async initialize() { const context = ErrorHandler.createErrorContext('DatabaseService.initialize', {}); try { if (this.isConnected) { logger.warn('Database service already initialized'); return; } this.client = new MongoClient(this.config.uri, { maxPoolSize: this.config.maxPoolSize, minPoolSize: this.config.minPoolSize, maxIdleTimeMS: this.config.maxIdleTimeMS, serverSelectionTimeoutMS: this.config.serverSelectionTimeoutMS }); await this.client.connect(); this.db = this.client.db(this.config.databaseName); this.isConnected = true; logger.info('Database service initialized successfully', { databaseName: this.config.databaseName, maxPoolSize: this.config.maxPoolSize }); } catch (error) { logger.error('Failed to initialize database service', { error: error.message, uri: this.config.uri, databaseName: this.config.databaseName }); throw error; } } // Ensure database connection async ensureConnected() { if (!this.isConnected || !this.client || !this.db) { await this.initialize(); } } // Health check implementation async healthCheck() { try { await this.ensureConnected(); if (!this.db) { return ServiceHealthStatus.UNHEALTHY; } // Ping the database await this.db.admin().ping(); // Check server status const serverStatus = await this.db.admin().serverStatus(); if (serverStatus.ok === 1) { return ServiceHealthStatus.HEALTHY; } else { return ServiceHealthStatus.DEGRADED; } } catch (error) { logger.error('Database health check failed', { error: error.message, databaseName: this.config.databaseName }); return ServiceHealthStatus.UNHEALTHY; } } // Find documents with options async find(collectionName, query, options = {}) { const context = ErrorHandler.createErrorContext('DatabaseService.find', { collectionName, query, options }); return this.executeWithResilience(async () => { await this.ensureConnected(); const collection = this.db.collection(collectionName); // Sanitize query if requested const sanitizedQuery = options.sanitizeQuery !== false ? this.sanitizeQuery(query, options) : query; let cursor = collection.find(sanitizedQuery); // Apply options if (options.projection) { cursor = cursor.project(options.projection); } if (options.sort) { cursor = cursor.sort(options.sort); } if (options.skip) { cursor = cursor.skip(options.skip); } if (options.limit) { cursor = cursor.limit(options.limit); } const results = await cursor.toArray(); logger.debug('Database find operation completed', { collectionName, resultCount: results.length, query: SanitizationMiddleware.sanitizeForLogging(sanitizedQuery) }); return results; }, context); } // Find one document async findOne(collectionName, query, options = {}) { const context = ErrorHandler.createErrorContext('DatabaseService.findOne', { collectionName, query, options }); return this.executeWithResilience(async () => { await this.ensureConnected(); const collection = this.db.collection(collectionName); // Sanitize query if requested const sanitizedQuery = options.sanitizeQuery !== false ? this.sanitizeQuery(query, options) : query; const result = await collection.findOne(sanitizedQuery, options.projection ? { projection: options.projection } : {}); logger.debug('Database findOne operation completed', { collectionName, found: !!result, query: SanitizationMiddleware.sanitizeForLogging(sanitizedQuery) }); return result; }, context); } // Count documents async count(collectionName, query, options = {}) { const context = ErrorHandler.createErrorContext('DatabaseService.count', { collectionName, query, options }); return this.executeWithResilience(async () => { await this.ensureConnected(); const collection = this.db.collection(collectionName); // Sanitize query if requested const sanitizedQuery = options.sanitizeQuery !== false ? this.sanitizeQuery(query, options) : query; const count = await collection.countDocuments(sanitizedQuery); logger.debug('Database count operation completed', { collectionName, count, query: SanitizationMiddleware.sanitizeForLogging(sanitizedQuery) }); return count; }, context); } // Insert one document async insertOne(collectionName, document) { const context = ErrorHandler.createErrorContext('DatabaseService.insertOne', { collectionName, document }); return this.executeWithResilience(async () => { await this.ensureConnected(); const collection = this.db.collection(collectionName); // Sanitize document const sanitizedDoc = SanitizationMiddleware.sanitizeToolArguments(document); const result = await collection.insertOne(sanitizedDoc); logger.debug('Database insertOne operation completed', { collectionName, insertedId: result.insertedId, acknowledged: result.acknowledged }); return result.insertedId; }, context); } // Update one document async updateOne(collectionName, filter, update, options = {}) { const context = ErrorHandler.createErrorContext('DatabaseService.updateOne', { collectionName, filter, update, options }); return this.executeWithResilience(async () => { await this.ensureConnected(); const collection = this.db.collection(collectionName); // Sanitize filter and update const sanitizedFilter = this.sanitizeQuery(filter, options); const sanitizedUpdate = options.sanitizeUpdate !== false ? this.sanitizeUpdate(update, options) : update; const result = await collection.updateOne(sanitizedFilter, sanitizedUpdate, { upsert: options.upsert || false }); logger.debug('Database updateOne operation completed', { collectionName, matched: result.matchedCount, modified: result.modifiedCount, upserted: result.upsertedCount }); return result.modifiedCount > 0 || result.upsertedCount > 0; }, context); } // Delete one document async deleteOne(collectionName, filter, options = {}) { const context = ErrorHandler.createErrorContext('DatabaseService.deleteOne', { collectionName, filter, options }); return this.executeWithResilience(async () => { await this.ensureConnected(); const collection = this.db.collection(collectionName); // Sanitize filter const sanitizedFilter = this.sanitizeQuery(filter, options); const result = await collection.deleteOne(sanitizedFilter); logger.debug('Database deleteOne operation completed', { collectionName, deleted: result.deletedCount }); return result.deletedCount > 0; }, context); } // Aggregate documents async aggregate(collectionName, pipeline, options = {}) { const context = ErrorHandler.createErrorContext('DatabaseService.aggregate', { collectionName, pipeline, options }); return this.executeWithResilience(async () => { await this.ensureConnected(); const collection = this.db.collection(collectionName); // Sanitize pipeline if requested const sanitizedPipeline = options.sanitizePipeline !== false ? this.sanitizePipeline(pipeline, options) : pipeline; const results = await collection.aggregate(sanitizedPipeline).toArray(); logger.debug('Database aggregate operation completed', { collectionName, stageCount: sanitizedPipeline.length, resultCount: results.length }); return results; }, context); } // Get database statistics async getStats() { const context = ErrorHandler.createErrorContext('DatabaseService.getStats', {}); return this.executeWithResilience(async () => { await this.ensureConnected(); const stats = await this.db.stats(); logger.debug('Database stats retrieved', { collections: stats.collections, objects: stats.objects, dataSize: stats.dataSize }); return stats; }, context); } // List collections async listCollections() { const context = ErrorHandler.createErrorContext('DatabaseService.listCollections', {}); return this.executeWithResilience(async () => { await this.ensureConnected(); const collections = await this.db.listCollections().toArray(); const collectionNames = collections.map(col => col.name); logger.debug('Database collections listed', { collectionCount: collectionNames.length, collections: collectionNames }); return collectionNames; }, context); } // Private helper methods sanitizeQuery(query, options) { const sanitizationOptions = { allowedFields: options.allowedFields, maxStringLength: 1000, allowObjectIds: true }; return SanitizationMiddleware.sanitizeMongoQuery(query, sanitizationOptions); } sanitizeUpdate(update, options) { const sanitizationOptions = { allowedFields: options.allowedFields, maxStringLength: 1000, allowObjectIds: true }; return SanitizationMiddleware.sanitizeMongoQuery(update, sanitizationOptions); } sanitizePipeline(pipeline, options) { const maxLength = options.maxPipelineLength || 20; const allowedStages = options.allowedStages || [ '$match', '$project', '$sort', '$limit', '$skip', '$group', '$unwind', '$lookup', '$addFields', '$count', '$facet', '$sample' ]; if (pipeline.length > maxLength) { logger.warn('Aggregation pipeline too long, truncating', { originalLength: pipeline.length, maxLength }); pipeline = pipeline.slice(0, maxLength); } return pipeline.map(stage => { const stageKey = Object.keys(stage)[0]; if (!allowedStages.includes(stageKey)) { logger.warn('Aggregation stage not allowed, skipping', { stage: stageKey, allowedStages }); return { $match: {} }; // Replace with safe stage } const sanitizationOptions = { maxStringLength: 1000, allowObjectIds: true }; return SanitizationMiddleware.sanitizeMongoQuery(stage, sanitizationOptions); }); } // Cleanup and close connection async close() { try { if (this.client) { await this.client.close(); this.client = null; this.db = null; this.isConnected = false; logger.info('Database service closed successfully'); } } catch (error) { logger.error('Error closing database service', { error: error.message }); } } // Get connection status isConnectionHealthy() { return this.isConnected && this.client !== null && this.db !== null; } } //# sourceMappingURL=database-service.js.map