Skip to main content
Glama
intecrel

Industrial MCP Server

by intecrel
route.ts11.7 kB
/** * Audit Query API Endpoint * Provides REST API for querying audit events with filtering and export capabilities */ import { NextRequest, NextResponse } from 'next/server' import { getGlobalDatabaseManager } from '../../../lib/database/index' import { AUTH_CONFIG } from '../../../lib/config' // Query parameters interface interface AuditQueryParams { // Time range startDate?: string endDate?: string // Filtering eventType?: string userId?: string userEmail?: string riskLevel?: 'low' | 'medium' | 'high' | 'critical' result?: 'success' | 'failure' | 'warning' databaseType?: 'neo4j' | 'mysql' operationType?: 'CREATE' | 'MERGE' | 'SET' | 'READ' // Pagination page?: number limit?: number // Export format?: 'json' | 'csv' // Search search?: string } // Authentication check function validateApiKey(request: NextRequest): boolean { const apiKey = request.headers.get('x-api-key') return apiKey === AUTH_CONFIG.API_KEY } // Parse query parameters function parseQueryParams(url: URL): AuditQueryParams { const params = new URLSearchParams(url.search) return { startDate: params.get('startDate') || undefined, endDate: params.get('endDate') || undefined, eventType: params.get('eventType') || undefined, userId: params.get('userId') || undefined, userEmail: params.get('userEmail') || undefined, riskLevel: params.get('riskLevel') as any || undefined, result: params.get('result') as any || undefined, databaseType: params.get('databaseType') as any || undefined, operationType: params.get('operationType') as any || undefined, page: params.get('page') ? parseInt(params.get('page')!) : 1, limit: Math.min(parseInt(params.get('limit') || '50'), 1000), // Max 1000 results format: params.get('format') as any || 'json', search: params.get('search') || undefined } } // Build SQL query with filters function buildAuditQuery(params: AuditQueryParams): { sql: string; values: any[] } { let sql = ` SELECT ae.id, ae.timestamp, ae.event_type, ae.user_id, ae.user_email, ae.client_id, ae.ip_address, ae.session_id, ae.resource, ae.action, ae.result, ae.risk_level, ae.details, ae.created_at, dae.database_type, dae.operation_type, dae.query_hash, dae.affected_nodes, dae.affected_relationships, dae.execution_time_ms, dae.complexity_score, dae.transaction_id FROM audit_events ae LEFT JOIN database_audit_events dae ON ae.id = dae.audit_event_id WHERE 1=1 ` const values: any[] = [] // Time range filters if (params.startDate) { sql += ` AND ae.timestamp >= ?` values.push(params.startDate) } if (params.endDate) { sql += ` AND ae.timestamp <= ?` values.push(params.endDate) } // Event type filter (supports wildcard patterns) if (params.eventType) { if (params.eventType.includes('%')) { sql += ` AND ae.event_type LIKE ?` values.push(params.eventType) } else { sql += ` AND ae.event_type = ?` values.push(params.eventType) } } // User filters if (params.userId) { sql += ` AND ae.user_id = ?` values.push(params.userId) } if (params.userEmail) { sql += ` AND ae.user_email = ?` values.push(params.userEmail) } // Risk and result filters if (params.riskLevel) { sql += ` AND ae.risk_level = ?` values.push(params.riskLevel) } if (params.result) { sql += ` AND ae.result = ?` values.push(params.result) } // Database-specific filters if (params.databaseType) { sql += ` AND dae.database_type = ?` values.push(params.databaseType) } if (params.operationType) { sql += ` AND dae.operation_type = ?` values.push(params.operationType) } // Search filter (searches across multiple text fields) if (params.search) { sql += ` AND ( ae.action LIKE ? OR ae.event_type LIKE ? OR ae.user_email LIKE ? OR JSON_EXTRACT(ae.details, '$') LIKE ? )` const searchTerm = `%${params.search}%` values.push(searchTerm, searchTerm, searchTerm, searchTerm) } // Order by timestamp (newest first) sql += ` ORDER BY ae.timestamp DESC` // Pagination (use direct values instead of placeholders for LIMIT/OFFSET) const offset = (params.page! - 1) * params.limit! sql += ` LIMIT ${params.limit} OFFSET ${offset}` return { sql, values } } // Convert query results to CSV function convertToCSV(data: any[]): string { if (data.length === 0) return '' const headers = Object.keys(data[0]) const csvHeaders = headers.join(',') const csvRows = data.map(row => { return headers.map(header => { const value = row[header] // Handle JSON fields if (typeof value === 'object' && value !== null) { return `"${JSON.stringify(value).replace(/"/g, '""')}"` } // Handle null/undefined if (value === null || value === undefined) { return '' } // Handle strings with commas or quotes const stringValue = String(value) if (stringValue.includes(',') || stringValue.includes('"') || stringValue.includes('\n')) { return `"${stringValue.replace(/"/g, '""')}"` } return stringValue }).join(',') }) return [csvHeaders, ...csvRows].join('\n') } // GET endpoint for querying audit events export async function GET(request: NextRequest) { try { // Validate API key if (!validateApiKey(request)) { return NextResponse.json( { error: 'Unauthorized - Invalid API key' }, { status: 401 } ) } // Parse query parameters const params = parseQueryParams(new URL(request.url)) // Get database connection const dbManager = await getGlobalDatabaseManager() const mysql = dbManager.getConnection() // Use default connection (environment-based MySQL) if (!mysql.isConnected) { await mysql.connect() } // Build and execute query const { sql, values } = buildAuditQuery(params) const result = await mysql.query(sql, values) const events = result.data || [] // Get total count for pagination (without LIMIT) const countSql = sql.replace(/SELECT.*?FROM/, 'SELECT COUNT(*) as total FROM') .replace(/ORDER BY.*$/, '') .replace(/LIMIT.*$/, '') // No need to slice values since LIMIT/OFFSET are no longer in the values array const countResult = await mysql.query(countSql, values) const total = countResult.data?.[0]?.total || 0 // Handle CSV export if (params.format === 'csv') { const csv = convertToCSV(events) return new NextResponse(csv, { status: 200, headers: { 'Content-Type': 'text/csv', 'Content-Disposition': `attachment; filename="audit-events-${new Date().toISOString().split('T')[0]}.csv"` } }) } // Return JSON response with pagination info const response = { events, pagination: { page: params.page, limit: params.limit, total, totalPages: Math.ceil(total / params.limit!), hasNext: params.page! * params.limit! < total, hasPrevious: params.page! > 1 }, filters: { startDate: params.startDate, endDate: params.endDate, eventType: params.eventType, userId: params.userId, userEmail: params.userEmail, riskLevel: params.riskLevel, result: params.result, databaseType: params.databaseType, operationType: params.operationType, search: params.search } } return NextResponse.json(response, { status: 200 }) } catch (error) { console.error('Audit query error:', error) return NextResponse.json( { error: 'Internal server error', details: process.env.NODE_ENV === 'development' ? String(error) : undefined }, { status: 500 } ) } } // POST endpoint for complex audit queries and aggregations export async function POST(request: NextRequest) { try { // Validate API key if (!validateApiKey(request)) { return NextResponse.json( { error: 'Unauthorized - Invalid API key' }, { status: 401 } ) } const body = await request.json() const { queryType, ...queryParams } = body const dbManager = await getGlobalDatabaseManager() const mysql = dbManager.getConnection() // Use default connection (environment-based MySQL) if (!mysql.isConnected) { await mysql.connect() } let result: any switch (queryType) { case 'risk_summary': // Risk level distribution result = await mysql.query(` SELECT risk_level, COUNT(*) as count, COUNT(*) * 100.0 / (SELECT COUNT(*) FROM audit_events) as percentage FROM audit_events WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 30 DAY) GROUP BY risk_level ORDER BY FIELD(risk_level, 'low', 'medium', 'high', 'critical') `) break case 'database_operations': // Database operation statistics result = await mysql.query(` SELECT dae.database_type, dae.operation_type, COUNT(*) as count, AVG(dae.execution_time_ms) as avg_execution_time, MAX(dae.execution_time_ms) as max_execution_time, AVG(dae.complexity_score) as avg_complexity FROM database_audit_events dae JOIN audit_events ae ON dae.audit_event_id = ae.id WHERE ae.timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY) GROUP BY dae.database_type, dae.operation_type ORDER BY count DESC `) break case 'user_activity': // User activity summary result = await mysql.query(` SELECT user_email, COUNT(*) as total_events, COUNT(CASE WHEN risk_level IN ('high', 'critical') THEN 1 END) as high_risk_events, MAX(timestamp) as last_activity FROM audit_events WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY) AND user_email IS NOT NULL GROUP BY user_email ORDER BY total_events DESC LIMIT 20 `) break case 'timeline': // Timeline of events const timeframe = queryParams.timeframe || '24h' const interval = timeframe === '24h' ? 'HOUR' : timeframe === '7d' ? 'DAY' : 'HOUR' result = await mysql.query(` SELECT DATE_FORMAT(timestamp, '${interval === 'HOUR' ? '%Y-%m-%d %H:00:00' : '%Y-%m-%d'}') as time_bucket, COUNT(*) as event_count, COUNT(CASE WHEN risk_level IN ('high', 'critical') THEN 1 END) as high_risk_count FROM audit_events WHERE timestamp >= DATE_SUB(NOW(), INTERVAL ${timeframe === '24h' ? '24 HOUR' : '7 DAY'}) GROUP BY time_bucket ORDER BY time_bucket `) break default: return NextResponse.json( { error: 'Invalid query type' }, { status: 400 } ) } return NextResponse.json({ queryType, data: result.data || [], generatedAt: new Date().toISOString() }) } catch (error) { console.error('Audit aggregation query error:', error) return NextResponse.json( { error: 'Internal server error', details: process.env.NODE_ENV === 'development' ? String(error) : undefined }, { status: 500 } ) } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/intecrel/industrial-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server