UNPKG

bigbasealpha

Version:

Enterprise-Grade NoSQL Database System with Modular Logger & Offline HSM Security - Complete database platform with professional text-based logging, encryption, caching, indexing, JWT authentication, auto-generated REST API, real-time dashboard, and maste

819 lines (731 loc) 29.1 kB
import express from 'express'; import cors from 'cors'; import { EventEmitter } from 'events'; /** * BigBaseAlpha Auto-Generated REST API * Automatically generates REST endpoints for all collections * * @copyright 2025 ByAlphas. All rights reserved. */ export class RESTAPIGenerator extends EventEmitter { constructor(database, authManager, options = {}) { super(); this.database = database; this.authManager = authManager; this.options = { port: options.port || 3001, prefix: options.prefix || '/api/v1', enableSwagger: options.enableSwagger !== false, enableCors: options.enableCors !== false, rateLimiting: options.rateLimiting !== false, pagination: { defaultLimit: options.pagination?.defaultLimit || 20, maxLimit: options.pagination?.maxLimit || 100 }, ...options }; this.app = express(); this.server = null; this.routes = new Map(); this.apiDocs = { openapi: '3.0.0', info: { title: 'BigBaseAlpha REST API', version: '1.5.0', description: 'Auto-generated REST API for BigBaseAlpha collections' }, servers: [ { url: `http://localhost:${this.options.port}${this.options.prefix}`, description: 'Development server' } ], paths: {}, components: { securitySchemes: { bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }, apiKey: { type: 'apiKey', in: 'header', name: 'X-API-Key' } } } }; this._setupMiddleware(); this._setupBaseRoutes(); } /** * Setup Express middleware */ _setupMiddleware() { // CORS if (this.options.enableCors) { this.app.use(cors({ origin: this.options.cors?.origin || '*', methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key'] })); } // Body parsing this.app.use(express.json({ limit: '10mb' })); this.app.use(express.urlencoded({ extended: true })); // Request logging this.app.use((req, res, next) => { console.log(`📡 ${req.method} ${req.path} - ${req.ip}`); next(); }); // Rate limiting (simple implementation) if (this.options.rateLimiting) { this.app.use(this._rateLimitMiddleware()); } } /** * Setup base routes */ _setupBaseRoutes() { const router = express.Router(); // Health check router.get('/health', (req, res) => { res.json({ status: 'healthy', timestamp: new Date().toISOString(), version: '1.5.0', uptime: process.uptime() }); }); // API documentation if (this.options.enableSwagger) { router.get('/docs', (req, res) => { res.json(this.apiDocs); }); router.get('/docs/ui', (req, res) => { res.send(this._generateSwaggerUI()); }); } // List all collections router.get('/collections', this.authManager.middleware(), this.authManager.requirePermission('read'), async (req, res) => { try { const collections = await this.database.listCollections(); const collectionStats = await Promise.all( collections.map(async (name) => { const collection = this.database.collection(name); const count = await collection.countDocuments(); return { name, count }; }) ); res.json({ success: true, collections: collectionStats, total: collections.length }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } } ); // Authentication routes router.post('/auth/login', async (req, res) => { try { const result = await this.authManager.login(req.body, { ipAddress: req.ip, userAgent: req.get('User-Agent') }); res.json(result); } catch (error) { res.status(401).json({ success: false, error: error.message }); } }); router.post('/auth/logout', this.authManager.middleware(), async (req, res) => { try { const token = req.headers.authorization?.substring(7); const result = await this.authManager.logout(token); res.json(result); } catch (error) { res.status(400).json({ success: false, error: error.message }); } } ); router.post('/auth/refresh', async (req, res) => { try { const { refreshToken } = req.body; const result = await this.authManager.refreshToken(refreshToken); res.json(result); } catch (error) { res.status(401).json({ success: false, error: error.message }); } }); router.get('/auth/me', this.authManager.middleware(), async (req, res) => { res.json({ success: true, user: req.user, authType: req.authType }); } ); this.app.use(this.options.prefix, router); } /** * Generate CRUD routes for a collection */ generateCollectionRoutes(collectionName) { const router = express.Router(); const collection = this.database.collection(collectionName); // GET /collections/:name - List documents with pagination router.get('/', this.authManager.middleware(), this.authManager.requirePermission('read'), async (req, res) => { try { const { page = 1, limit = this.options.pagination.defaultLimit, sort, filter, search } = req.query; const pageNum = Math.max(1, parseInt(page)); const limitNum = Math.min( Math.max(1, parseInt(limit)), this.options.pagination.maxLimit ); const skip = (pageNum - 1) * limitNum; // Build query let query = {}; if (filter) { try { query = JSON.parse(filter); } catch (e) { return res.status(400).json({ success: false, error: 'Invalid filter JSON' }); } } // Add search if (search) { query.$text = { $search: search }; } // Execute query let queryBuilder = collection.find(query); // Apply sorting if (sort) { const sortObj = {}; sort.split(',').forEach(field => { if (field.startsWith('-')) { sortObj[field.substring(1)] = -1; } else { sortObj[field] = 1; } }); queryBuilder = queryBuilder.sort(sortObj); } // Apply pagination const documents = await queryBuilder.skip(skip).limit(limitNum).toArray(); const totalCount = await collection.countDocuments(query); res.json({ success: true, data: documents, pagination: { page: pageNum, limit: limitNum, total: totalCount, pages: Math.ceil(totalCount / limitNum), hasNext: pageNum * limitNum < totalCount, hasPrev: pageNum > 1 } }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } } ); // GET /collections/:name/:id - Get single document router.get('/:id', this.authManager.middleware(), this.authManager.requirePermission('read'), async (req, res) => { try { const document = await collection.findOne({ _id: req.params.id }); if (!document) { return res.status(404).json({ success: false, error: 'Document not found' }); } res.json({ success: true, data: document }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } } ); // POST /collections/:name - Create new document router.post('/', this.authManager.middleware(), this.authManager.requirePermission('write'), async (req, res) => { try { const documentData = { ...req.body, _createdBy: req.user._id, _createdAt: new Date(), _updatedAt: new Date() }; const result = await collection.insertOne(documentData); res.status(201).json({ success: true, data: result, id: result._id }); } catch (error) { res.status(400).json({ success: false, error: error.message }); } } ); // PUT /collections/:name/:id - Update document router.put('/:id', this.authManager.middleware(), this.authManager.requirePermission('write'), async (req, res) => { try { const updateData = { ...req.body, _updatedBy: req.user._id, _updatedAt: new Date() }; // Remove immutable fields delete updateData._id; delete updateData._createdBy; delete updateData._createdAt; const result = await collection.findOneAndUpdate( { _id: req.params.id }, { $set: updateData }, { returnDocument: 'after' } ); if (!result) { return res.status(404).json({ success: false, error: 'Document not found' }); } res.json({ success: true, data: result }); } catch (error) { res.status(400).json({ success: false, error: error.message }); } } ); // PATCH /collections/:name/:id - Partial update router.patch('/:id', this.authManager.middleware(), this.authManager.requirePermission('write'), async (req, res) => { try { const updateData = { ...req.body, _updatedBy: req.user._id, _updatedAt: new Date() }; const result = await collection.findOneAndUpdate( { _id: req.params.id }, { $set: updateData }, { returnDocument: 'after' } ); if (!result) { return res.status(404).json({ success: false, error: 'Document not found' }); } res.json({ success: true, data: result }); } catch (error) { res.status(400).json({ success: false, error: error.message }); } } ); // DELETE /collections/:name/:id - Delete document router.delete('/:id', this.authManager.middleware(), this.authManager.requirePermission('delete'), async (req, res) => { try { const result = await collection.findOneAndDelete({ _id: req.params.id }); if (!result) { return res.status(404).json({ success: false, error: 'Document not found' }); } res.json({ success: true, message: 'Document deleted successfully', data: result }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } } ); // POST /collections/:name/bulk - Bulk operations router.post('/bulk', this.authManager.middleware(), this.authManager.requirePermission('write'), async (req, res) => { try { const { operation, documents, filter } = req.body; let result; const timestamp = new Date(); switch (operation) { case 'insert': const docsToInsert = documents.map(doc => ({ ...doc, _createdBy: req.user._id, _createdAt: timestamp, _updatedAt: timestamp })); result = await collection.insertMany(docsToInsert); break; case 'update': result = await collection.updateMany( filter || {}, { $set: { ...req.body.update, _updatedBy: req.user._id, _updatedAt: timestamp } } ); break; case 'delete': result = await collection.deleteMany(filter || {}); break; default: return res.status(400).json({ success: false, error: 'Invalid bulk operation' }); } res.json({ success: true, operation, result }); } catch (error) { res.status(400).json({ success: false, error: error.message }); } } ); // Mount collection routes this.app.use(`${this.options.prefix}/collections/${collectionName}`, router); this.routes.set(collectionName, router); // Update API documentation this._updateApiDocs(collectionName); console.log(`🌐 REST API routes generated for collection: ${collectionName}`); this.emit('routesGenerated', { collection: collectionName }); } /** * Auto-generate routes for all existing collections */ async generateAllRoutes() { try { const collections = await this.database.listCollections(); for (const collectionName of collections) { // Skip system collections if (!collectionName.startsWith('_')) { this.generateCollectionRoutes(collectionName); } } console.log(`🚀 Auto-generated REST API for ${collections.length} collections`); this.emit('allRoutesGenerated', { count: collections.length }); } catch (error) { console.error('Failed to generate routes:', error); throw error; } } /** * Start the API server */ async start() { try { // Generate routes for existing collections await this.generateAllRoutes(); // Start server this.server = this.app.listen(this.options.port, () => { console.log(`🌐 REST API server started on port ${this.options.port}`); console.log(`📖 API Documentation: http://localhost:${this.options.port}${this.options.prefix}/docs/ui`); console.log(`🔍 Health Check: http://localhost:${this.options.port}${this.options.prefix}/health`); }); this.emit('serverStarted', { port: this.options.port }); return this.server; } catch (error) { console.error('Failed to start API server:', error); throw error; } } /** * Stop the API server */ async stop() { if (this.server) { await new Promise((resolve) => { this.server.close(resolve); }); console.log('🛑 REST API server stopped'); this.emit('serverStopped'); } } /** * Rate limiting middleware */ _rateLimitMiddleware() { const requests = new Map(); return (req, res, next) => { const identifier = req.ip || 'unknown'; const now = Date.now(); const windowMs = 60 * 1000; // 1 minute const maxRequests = 100; // 100 requests per minute if (!requests.has(identifier)) { requests.set(identifier, []); } const userRequests = requests.get(identifier); // Clean old requests const validRequests = userRequests.filter(timestamp => now - timestamp < windowMs ); if (validRequests.length >= maxRequests) { return res.status(429).json({ error: 'Too many requests', retryAfter: Math.ceil(windowMs / 1000) }); } validRequests.push(now); requests.set(identifier, validRequests); next(); }; } /** * Update OpenAPI documentation */ _updateApiDocs(collectionName) { const basePath = `/collections/${collectionName}`; this.apiDocs.paths[basePath] = { get: { summary: `List ${collectionName} documents`, tags: [collectionName], security: [{ bearerAuth: [] }, { apiKey: [] }], parameters: [ { name: 'page', in: 'query', schema: { type: 'integer', default: 1 } }, { name: 'limit', in: 'query', schema: { type: 'integer', default: 20 } }, { name: 'filter', in: 'query', schema: { type: 'string' }, description: 'JSON filter object' }, { name: 'sort', in: 'query', schema: { type: 'string' }, description: 'Comma-separated sort fields' } ], responses: { 200: { description: 'List of documents', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'array' }, pagination: { type: 'object' } } } } } } } }, post: { summary: `Create ${collectionName} document`, tags: [collectionName], security: [{ bearerAuth: [] }, { apiKey: [] }], requestBody: { content: { 'application/json': { schema: { type: 'object' } } } }, responses: { 201: { description: 'Document created successfully' } } } }; this.apiDocs.paths[`${basePath}/{id}`] = { get: { summary: `Get ${collectionName} document by ID`, tags: [collectionName], security: [{ bearerAuth: [] }, { apiKey: [] }], parameters: [ { name: 'id', in: 'path', required: true, schema: { type: 'string' } } ], responses: { 200: { description: 'Document found' }, 404: { description: 'Document not found' } } }, put: { summary: `Update ${collectionName} document`, tags: [collectionName], security: [{ bearerAuth: [] }, { apiKey: [] }], parameters: [ { name: 'id', in: 'path', required: true, schema: { type: 'string' } } ], responses: { 200: { description: 'Document updated' } } }, delete: { summary: `Delete ${collectionName} document`, tags: [collectionName], security: [{ bearerAuth: [] }, { apiKey: [] }], parameters: [ { name: 'id', in: 'path', required: true, schema: { type: 'string' } } ], responses: { 200: { description: 'Document deleted' } } } }; } /** * Generate Swagger UI HTML */ _generateSwaggerUI() { return ` <!DOCTYPE html> <html> <head> <title>BigBaseAlpha API Documentation</title> <link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@4.15.5/swagger-ui.css" /> </head> <body> <div id="swagger-ui"></div> <script src="https://unpkg.com/swagger-ui-dist@4.15.5/swagger-ui-bundle.js"></script> <script> SwaggerUIBundle({ url: '${this.options.prefix}/docs', dom_id: '#swagger-ui', presets: [ SwaggerUIBundle.presets.apis, SwaggerUIBundle.presets.standalone ] }); </script> </body> </html> `; } /** * Get API statistics */ getAPIStats() { return { routes: { total: this.routes.size, collections: Array.from(this.routes.keys()) }, server: { running: !!this.server, port: this.options.port }, options: { prefix: this.options.prefix, enableSwagger: this.options.enableSwagger, enableCors: this.options.enableCors, rateLimiting: this.options.rateLimiting } }; } } export default RESTAPIGenerator;