diff --git a/backend/package.json b/backend/package.json index aa21971..fe3b9a2 100644 --- a/backend/package.json +++ b/backend/package.json @@ -38,8 +38,6 @@ "express": "^4.18.2", "express-rate-limit": "^6.10.0", "express-validator": "^7.0.1", - "swagger-jsdoc": "^6.2.8", - "swagger-ui-express": "^5.0.0", "firebase-admin": "^13.7.0", "graphlib": "^2.1.8", "helmet": "^7.0.0", @@ -94,8 +92,6 @@ "@types/node": "^20.5.1", "@types/nodemailer": "^6.4.14", "@types/supertest": "^2.0.12", - "@types/swagger-jsdoc": "^6.0.4", - "@types/swagger-ui-express": "^4.1.6", "@types/uuid": "^9.0.4", "@types/web-push": "^3.6.4", "@typescript-eslint/eslint-plugin": "^6.4.0", diff --git a/backend/src/config/swagger.ts b/backend/src/config/swagger.ts deleted file mode 100644 index 0a2677f..0000000 --- a/backend/src/config/swagger.ts +++ /dev/null @@ -1,1005 +0,0 @@ -import swaggerJsdoc from 'swagger-jsdoc'; -import { version } from '../../package.json'; - -const options: swaggerJsdoc.Options = { - definition: { - openapi: '3.0.0', - info: { - title: 'AetherMint Education Backend API', - version, - description: - 'Decentralized education platform API on Stellar blockchain. Provides endpoints for authentication, courses, quizzes, payments, collaboration, and advanced features including federated learning, quantum encryption, and swarm intelligence.', - contact: { - name: 'AetherMint Team', - url: 'https://aethermint.io', - }, - license: { - name: 'MIT', - url: 'https://opensource.org/licenses/MIT', - }, - }, - servers: [ - { - url: 'http://localhost:3001', - description: 'Development server', - }, - ], - components: { - securitySchemes: { - bearerAuth: { - type: 'http', - scheme: 'bearer', - bearerFormat: 'JWT', - description: 'JWT token obtained from /api/auth/login or /api/auth/register', - }, - }, - schemas: { - Error: { - type: 'object', - properties: { - success: { type: 'boolean', example: false }, - message: { type: 'string', example: 'Error message' }, - error: { type: 'string', example: 'Error code' }, - }, - }, - Pagination: { - type: 'object', - properties: { - page: { type: 'integer', example: 1 }, - limit: { type: 'integer', example: 10 }, - total: { type: 'integer', example: 100 }, - pages: { type: 'integer', example: 10 }, - }, - }, - User: { - type: 'object', - properties: { - id: { type: 'string' }, - username: { type: 'string' }, - email: { type: 'string' }, - role: { type: 'string', enum: ['student', 'educator', 'admin'] }, - createdAt: { type: 'string', format: 'date-time' }, - updatedAt: { type: 'string', format: 'date-time' }, - }, - }, - AuthResponse: { - type: 'object', - properties: { - message: { type: 'string' }, - user: { $ref: '#/components/schemas/User' }, - token: { type: 'string' }, - }, - }, - Transaction: { - type: 'object', - properties: { - transactionId: { type: 'string' }, - userId: { type: 'string' }, - type: { type: 'string' }, - status: { type: 'string', enum: ['pending', 'processing', 'completed', 'failed'] }, - priority: { type: 'string', enum: ['low', 'medium', 'high'] }, - createdAt: { type: 'string', format: 'date-time' }, - }, - }, - Enrollment: { - type: 'object', - properties: { - id: { type: 'string' }, - userId: { type: 'string' }, - courseId: { type: 'string' }, - status: { type: 'string', enum: ['active', 'completed', 'cancelled'] }, - progress: { type: 'number' }, - createdAt: { type: 'string', format: 'date-time' }, - }, - }, - }, - }, - paths: { - // ===== Authentication ===== - '/api/auth/register': { - post: { - tags: ['Authentication'], - summary: 'Register a new user', - requestBody: { - required: true, - content: { - 'application/json': { - schema: { - type: 'object', - required: ['username', 'email', 'password'], - properties: { - username: { type: 'string', example: 'johndoe' }, - email: { type: 'string', format: 'email', example: 'john@example.com' }, - password: { type: 'string', format: 'password', example: 'securePass123' }, - role: { type: 'string', enum: ['student', 'educator', 'admin'], default: 'student' }, - }, - }, - }, - }, - }, - responses: { - 201: { description: 'User registered successfully', content: { 'application/json': { schema: { $ref: '#/components/schemas/AuthResponse' } } } }, - 400: { description: 'Missing required fields or invalid role', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, - 409: { description: 'User already exists' }, - }, - }, - }, - '/api/auth/login': { - post: { - tags: ['Authentication'], - summary: 'Authenticate user and get JWT token', - requestBody: { - required: true, - content: { - 'application/json': { - schema: { - type: 'object', - required: ['username', 'password'], - properties: { - username: { type: 'string', example: 'johndoe' }, - password: { type: 'string', format: 'password', example: 'securePass123' }, - }, - }, - }, - }, - }, - responses: { - 200: { description: 'Login successful', content: { 'application/json': { schema: { $ref: '#/components/schemas/AuthResponse' } } } }, - 400: { description: 'Missing credentials' }, - 401: { description: 'Invalid credentials' }, - }, - }, - }, - '/api/auth/profile': { - get: { - tags: ['Authentication'], - summary: 'Get current user profile', - security: [{ bearerAuth: [] }], - responses: { - 200: { description: 'User profile retrieved', content: { 'application/json': { schema: { type: 'object', properties: { user: { $ref: '#/components/schemas/User' } } } } } }, - 404: { description: 'User not found' }, - }, - }, - put: { - tags: ['Authentication'], - summary: 'Update user profile', - security: [{ bearerAuth: [] }], - requestBody: { - required: true, - content: { - 'application/json': { - schema: { - type: 'object', - properties: { - username: { type: 'string' }, - email: { type: 'string', format: 'email' }, - currentPassword: { type: 'string', format: 'password' }, - newPassword: { type: 'string', format: 'password' }, - }, - }, - }, - }, - }, - responses: { - 200: { description: 'Profile updated successfully' }, - 400: { description: 'Validation error' }, - 404: { description: 'User not found' }, - }, - }, - }, - '/api/auth/assign-role/{userId}': { - put: { - tags: ['Authentication'], - summary: 'Assign role to user (Admin only)', - security: [{ bearerAuth: [] }], - parameters: [{ in: 'path', name: 'userId', required: true, schema: { type: 'string' } }], - requestBody: { - required: true, - content: { - 'application/json': { - schema: { - type: 'object', - required: ['role'], - properties: { role: { type: 'string', enum: ['student', 'educator', 'admin'] } }, - }, - }, - }, - }, - responses: { - 200: { description: 'Role assigned successfully' }, - 400: { description: 'Invalid role' }, - 404: { description: 'User not found' }, - }, - }, - }, - '/api/auth/users': { - get: { - tags: ['Authentication'], - summary: 'Get all users (Admin only)', - security: [{ bearerAuth: [] }], - parameters: [ - { in: 'query', name: 'page', schema: { type: 'integer', default: 1 } }, - { in: 'query', name: 'limit', schema: { type: 'integer', default: 10 } }, - { in: 'query', name: 'role', schema: { type: 'string', enum: ['student', 'educator', 'admin'] } }, - ], - responses: { - 200: { description: 'Users retrieved', content: { 'application/json': { schema: { type: 'object', properties: { users: { type: 'array', items: { $ref: '#/components/schemas/User' } }, pagination: { $ref: '#/components/schemas/Pagination' } } } } } }, - }, - }, - }, - '/api/auth/users/{userId}': { - delete: { - tags: ['Authentication'], - summary: 'Delete user (Admin only)', - security: [{ bearerAuth: [] }], - parameters: [{ in: 'path', name: 'userId', required: true, schema: { type: 'string' } }], - responses: { - 200: { description: 'User deleted successfully' }, - 400: { description: 'Cannot delete self' }, - 404: { description: 'User not found' }, - }, - }, - }, - - // ===== Users ===== - '/api/users/profile/{address}': { - get: { - tags: ['Users'], - summary: 'Get user profile by Stellar address', - parameters: [{ in: 'path', name: 'address', required: true, schema: { type: 'string' }, description: 'Stellar wallet address' }], - responses: { 200: { description: 'User profile retrieved' }, 400: { description: 'Invalid address' } }, - }, - put: { - tags: ['Users'], - summary: 'Update user profile', - parameters: [{ in: 'path', name: 'address', required: true, schema: { type: 'string' } }], - requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { username: { type: 'string' }, email: { type: 'string' }, bio: { type: 'string' } } } } } }, - responses: { 200: { description: 'Profile updated' }, 400: { description: 'Validation error' } }, - }, - }, - '/api/users/settings/{userId}': { - get: { tags: ['Users'], summary: 'Get user settings', parameters: [{ in: 'path', name: 'userId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Settings retrieved' } } }, - put: { tags: ['Users'], summary: 'Update user settings', parameters: [{ in: 'path', name: 'userId', required: true, schema: { type: 'string' } }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object' } } } }, responses: { 200: { description: 'Settings updated' } } }, - }, - '/api/users/profile/{address}/achievements': { - get: { tags: ['Users'], summary: 'Get user achievements', parameters: [{ in: 'path', name: 'address', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Achievements retrieved' } } }, - }, - '/api/users/profile/{address}/stats': { - get: { tags: ['Users'], summary: 'Get user statistics', parameters: [{ in: 'path', name: 'address', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Stats retrieved' } } }, - }, - - // ===== Content (IPFS) ===== - '/api/content/upload': { - post: { - tags: ['Content'], - summary: 'Upload a single file to IPFS', - security: [{ bearerAuth: [] }], - requestBody: { required: true, content: { 'multipart/form-data': { schema: { type: 'object', properties: { file: { type: 'string', format: 'binary' }, metadata: { type: 'string' }, includeMetadata: { type: 'string' }, wrapWithDirectory: { type: 'string' } } } } } }, - responses: { 201: { description: 'File uploaded successfully' }, 400: { description: 'Upload failed' } }, - }, - }, - '/api/content/upload/batch': { - post: { - tags: ['Content'], - summary: 'Upload multiple files to IPFS', - security: [{ bearerAuth: [] }], - requestBody: { required: true, content: { 'multipart/form-data': { schema: { type: 'object', properties: { files: { type: 'array', items: { type: 'string', format: 'binary' } }, metadata: { type: 'string' } } } } } }, - responses: { 201: { description: 'Files uploaded successfully' } }, - }, - }, - '/api/content/{cid}': { - get: { tags: ['Content'], summary: 'Retrieve content from IPFS by CID', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'cid', required: true, schema: { type: 'string' } }, { in: 'query', name: 'format', schema: { type: 'string', enum: ['buffer', 'base64', 'stream'] } }], responses: { 200: { description: 'Content retrieved' }, 404: { description: 'Content not found' } } }, - }, - '/api/content/{cid}/metadata': { - get: { tags: ['Content'], summary: 'Retrieve metadata for IPFS content', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'cid', required: true, schema: { type: 'string' } }, { in: 'query', name: 'metadataCid', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Metadata retrieved' } } }, - }, - '/api/content/{cid}/pin': { - post: { tags: ['Content'], summary: 'Pin content to IPFS', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'cid', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Content pinned' } } }, - delete: { tags: ['Content'], summary: 'Unpin content from IPFS', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'cid', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Content unpinned' } } }, - }, - '/api/content/node/info': { - get: { tags: ['Content'], summary: 'Get IPFS node information', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Node info retrieved' } } }, - }, - '/api/content/cache/stats': { - get: { tags: ['Content'], summary: 'Get IPFS cache statistics', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Cache stats retrieved' } } }, - }, - '/api/content/cache': { - delete: { tags: ['Content'], summary: 'Clear IPFS cache', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Cache cleared' } } }, - }, - '/api/content/health': { - get: { tags: ['Content'], summary: 'IPFS service health check', responses: { 200: { description: 'Service healthy' }, 503: { description: 'Service unhealthy' } } }, - }, - - // ===== Courses ===== - '/api/courses/{contentId}/versions': { - post: { - tags: ['Courses'], - summary: 'Create a new version for course content', - parameters: [{ in: 'path', name: 'contentId', required: true, schema: { type: 'string' } }], - requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { title: { type: 'string' }, description: { type: 'string' }, content: { type: 'object' }, changes: { type: 'array', items: { type: 'string' } }, createdBy: { type: 'string' } } } } } }, - responses: { 201: { description: 'Version created successfully' }, 500: { description: 'Server error' } }, - }, - get: { - tags: ['Courses'], - summary: 'Get version history for course content', - parameters: [{ in: 'path', name: 'contentId', required: true, schema: { type: 'string' } }, { in: 'query', name: 'page', schema: { type: 'integer' } }, { in: 'query', name: 'limit', schema: { type: 'integer' } }], - responses: { 200: { description: 'Version history retrieved' } }, - }, - }, - '/api/courses/{contentId}/versions/current': { - get: { tags: ['Courses'], summary: 'Get current version of course content', parameters: [{ in: 'path', name: 'contentId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Current version retrieved' } } }, - }, - '/api/courses/{contentId}/versions/{versionNumber}': { - get: { tags: ['Courses'], summary: 'Get specific version by version number', parameters: [{ in: 'path', name: 'contentId', required: true, schema: { type: 'string' } }, { in: 'path', name: 'versionNumber', required: true, schema: { type: 'integer' } }], responses: { 200: { description: 'Version retrieved' } } }, - }, - '/api/courses/versions/compare/{version1Id}/{version2Id}': { - post: { tags: ['Courses'], summary: 'Compare two versions', parameters: [{ in: 'path', name: 'version1Id', required: true, schema: { type: 'string' } }, { in: 'path', name: 'version2Id', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Versions compared' } } }, - }, - '/api/courses/{contentId}/versions/restore': { - post: { tags: ['Courses'], summary: 'Restore content to a specific version', parameters: [{ in: 'path', name: 'contentId', required: true, schema: { type: 'string' } }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { versionId: { type: 'string' }, restoreReason: { type: 'string' }, restoredBy: { type: 'string' } } } } } }, responses: { 200: { description: 'Content restored' } } }, - }, - '/api/courses/{contentId}/versions/settings': { - put: { tags: ['Courses'], summary: 'Update version control settings', parameters: [{ in: 'path', name: 'contentId', required: true, schema: { type: 'string' } }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { autoVersioning: { type: 'boolean' }, maxVersions: { type: 'integer' } } } } } }, responses: { 200: { description: 'Settings updated' } } }, - }, - '/api/courses/{contentId}/versions/export': { - get: { tags: ['Courses'], summary: 'Export version history', parameters: [{ in: 'path', name: 'contentId', required: true, schema: { type: 'string' } }, { in: 'query', name: 'format', schema: { type: 'string', enum: ['json', 'csv'] } }], responses: { 200: { description: 'Version history exported' } } }, - }, - '/api/courses/{contentId}/versions/statistics': { - get: { tags: ['Courses'], summary: 'Get version statistics for content', parameters: [{ in: 'path', name: 'contentId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Version statistics retrieved' } } }, - }, - - // ===== Quizzes ===== - '/api/quizzes': { - post: { tags: ['Quizzes'], summary: 'Create a new quiz', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { title: { type: 'string' }, questions: { type: 'array' }, timeLimit: { type: 'integer' } } } } } }, responses: { 200: { description: 'Quiz created' } } }, - get: { tags: ['Quizzes'], summary: 'Get all quizzes', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Quizzes retrieved' } } }, - }, - '/api/quizzes/{id}': { - get: { tags: ['Quizzes'], summary: 'Get quiz by ID', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Quiz retrieved' }, 404: { description: 'Quiz not found' } } }, - put: { tags: ['Quizzes'], summary: 'Update quiz', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Quiz updated' } } }, - delete: { tags: ['Quizzes'], summary: 'Delete quiz', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Quiz deleted' } } }, - }, - '/api/quizzes/{id}/publish': { - post: { tags: ['Quizzes'], summary: 'Toggle quiz publish status', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Publish status toggled' } } }, - }, - '/api/quizzes/{id}/submit': { - post: { tags: ['Quizzes'], summary: 'Submit quiz answers', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { answers: { type: 'array' } } } } } }, responses: { 200: { description: 'Submission received' } } }, - }, - '/api/quizzes/{id}/results': { - get: { tags: ['Quizzes'], summary: 'Get quiz results', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Results retrieved' } } }, - }, - '/api/quizzes/{id}/statistics': { - get: { tags: ['Quizzes'], summary: 'Get quiz statistics', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Statistics retrieved' } } }, - }, - '/api/quizzes/health': { - get: { tags: ['Quizzes'], summary: 'Quiz service health check', responses: { 200: { description: 'Service healthy' } } }, - }, - - // ===== Events ===== - '/api/events/course-completion': { - post: { tags: ['Events'], summary: 'Log course completion event', responses: { 200: { description: 'Event logged' } } }, - }, - '/api/events/credential-issuance': { - post: { tags: ['Events'], summary: 'Log credential issuance event', responses: { 200: { description: 'Event logged' } } }, - }, - '/api/events/user-achievement': { - post: { tags: ['Events'], summary: 'Log user achievement event', responses: { 200: { description: 'Event logged' } } }, - }, - '/api/events/user/{userId}/events': { - get: { tags: ['Events'], summary: 'Get events by user', parameters: [{ in: 'path', name: 'userId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Events retrieved' } } }, - }, - '/api/events/recent': { - get: { tags: ['Events'], summary: 'Get recent events', responses: { 200: { description: 'Recent events retrieved' } } }, - }, - '/api/events/verify/{eventId}': { - get: { tags: ['Events'], summary: 'Verify event', parameters: [{ in: 'path', name: 'eventId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Event verified' } } }, - }, - - // ===== Sync ===== - '/api/sync/devices/register': { - post: { tags: ['Sync'], summary: 'Register device for sync', requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { deviceId: { type: 'string' }, platform: { type: 'string' } } } } } }, responses: { 200: { description: 'Device registered' } } }, - }, - '/api/sync/devices/{deviceId}': { - delete: { tags: ['Sync'], summary: 'Unregister device', parameters: [{ in: 'path', name: 'deviceId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Device unregistered' } } }, - }, - '/api/sync/sync': { - post: { tags: ['Sync'], summary: 'Sync entity data across devices', responses: { 200: { description: 'Data synced' } } }, - }, - '/api/sync/queue': { - post: { tags: ['Sync'], summary: 'Enqueue sync operation for offline processing', responses: { 200: { description: 'Operation queued' } } }, - }, - '/api/sync/queue/status': { - get: { tags: ['Sync'], summary: 'Get queue sync status', responses: { 200: { description: 'Queue status retrieved' } } }, - }, - - // ===== RBAC ===== - '/api/rbac/assign-role': { - post: { tags: ['RBAC'], summary: 'Assign role to user', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { userId: { type: 'string' }, role: { type: 'string' } } } } } }, responses: { 200: { description: 'Role assigned' } } }, - }, - '/api/rbac/permissions': { - get: { tags: ['RBAC'], summary: 'Get available permissions', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Permissions retrieved' } } }, - }, - - // ===== Transactions ===== - '/api/transactions': { - post: { tags: ['Transactions'], summary: 'Create and queue a new transaction', requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { sourceAccount: { type: 'string' }, destinationAccount: { type: 'string' }, amount: { type: 'string' }, asset: { type: 'object' }, priority: { type: 'string', enum: ['low', 'medium', 'high'] } } } } } }, responses: { 201: { description: 'Transaction queued', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { $ref: '#/components/schemas/Transaction' } } } } } } } }, - get: { tags: ['Transactions'], summary: 'Get transactions with optional filtering', responses: { 200: { description: 'Transactions retrieved' } } }, - }, - '/api/transactions/{transactionId}': { - get: { tags: ['Transactions'], summary: 'Get specific transaction details', parameters: [{ in: 'path', name: 'transactionId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Transaction details retrieved' }, 404: { description: 'Transaction not found' } } }, - }, - '/api/transactions/{transactionId}/status': { - get: { tags: ['Transactions'], summary: 'Get transaction status with queue position', parameters: [{ in: 'path', name: 'transactionId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Status retrieved' } } }, - }, - '/api/transactions/{transactionId}/retry': { - post: { tags: ['Transactions'], summary: 'Retry a failed transaction', parameters: [{ in: 'path', name: 'transactionId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Transaction queued for retry' } } }, - }, - '/api/transactions/submit': { - post: { tags: ['Transactions'], summary: 'Submit transaction to queue', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { type: { type: 'string' }, payload: { type: 'object' }, priority: { type: 'string', enum: ['low', 'medium', 'high'] } } } } } }, responses: { 201: { description: 'Transaction submitted' } } }, - }, - '/api/transactions/queue/stats': { - get: { tags: ['Transactions'], summary: 'Get transaction queue statistics', responses: { 200: { description: 'Queue stats retrieved' } } }, - }, - '/api/transactions/stellar/account/{accountId}': { - get: { tags: ['Transactions'], summary: 'Get Stellar account information', parameters: [{ in: 'path', name: 'accountId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Account info retrieved' }, 404: { description: 'Account not found' } } }, - }, - '/api/transactions/stellar/fee-stats': { - get: { tags: ['Transactions'], summary: 'Get Stellar network fee statistics', responses: { 200: { description: 'Fee stats retrieved' } } }, - }, - '/api/transactions/bulk': { - post: { tags: ['Transactions'], summary: 'Submit bulk transactions (max 100)', security: [{ bearerAuth: [] }], responses: { 201: { description: 'Bulk transactions submitted' } } }, - }, - '/api/transactions/analytics': { - get: { tags: ['Transactions'], summary: 'Get transaction analytics', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Analytics retrieved' } } }, - }, - - // ===== Notifications ===== - '/api/notifications/{userId}': { - get: { tags: ['Notifications'], summary: 'Get notification history', parameters: [{ in: 'path', name: 'userId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Notifications retrieved' } } }, - delete: { tags: ['Notifications'], summary: 'Delete notification', parameters: [{ in: 'path', name: 'notificationId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Notification deleted' } } }, - }, - '/api/notifications/{notificationId}/read': { - patch: { tags: ['Notifications'], summary: 'Mark notification as read', parameters: [{ in: 'path', name: 'notificationId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Marked as read' } } }, - }, - '/api/notifications/read-all': { - patch: { tags: ['Notifications'], summary: 'Mark all notifications as read', responses: { 200: { description: 'All marked as read' } } }, - }, - '/api/notifications/{userId}/preferences': { - get: { tags: ['Notifications'], summary: 'Get notification preferences', parameters: [{ in: 'path', name: 'userId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Preferences retrieved' } } }, - put: { tags: ['Notifications'], summary: 'Update notification preferences', parameters: [{ in: 'path', name: 'userId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Preferences updated' } } }, - }, - - // ===== Collaboration ===== - '/api/collaboration/rooms': { - post: { tags: ['Collaboration'], summary: 'Create collaboration room', responses: { 200: { description: 'Room created' } } }, - get: { tags: ['Collaboration'], summary: 'List collaboration rooms', responses: { 200: { description: 'Rooms listed' } } }, - }, - '/api/collaboration/rooms/{roomId}': { - get: { tags: ['Collaboration'], summary: 'Get room by ID', parameters: [{ in: 'path', name: 'roomId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Room details' } } }, - }, - '/api/collaboration/rooms/{roomId}/end': { - post: { tags: ['Collaboration'], summary: 'End collaboration room', parameters: [{ in: 'path', name: 'roomId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Room ended' } } }, - }, - - // ===== Holographic ===== - '/api/holographic/encode': { - post: { tags: ['Holographic'], summary: 'Encode content using holographic storage', responses: { 200: { description: 'Content encoded' } } }, - }, - '/api/holographic/decode/{hash}': { - get: { tags: ['Holographic'], summary: 'Decode holographic content', parameters: [{ in: 'path', name: 'hash', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Content decoded' } } }, - }, - '/api/holographic/metrics': { - get: { tags: ['Holographic'], summary: 'Get holographic storage metrics', responses: { 200: { description: 'Metrics retrieved' } } }, - }, - '/api/holographic/optimize': { - post: { tags: ['Holographic'], summary: 'Optimize holographic storage', responses: { 200: { description: 'Storage optimized' } } }, - }, - - // ===== Secure Communication ===== - '/api/secure-comm/generate-keypair': { - post: { tags: ['SecureComm'], summary: 'Generate quantum-resistant key pair', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Key pair generated' } } }, - }, - '/api/secure-comm/establish-secret': { - post: { tags: ['SecureComm'], summary: 'Establish shared secret between users', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Shared secret established' } } }, - }, - '/api/secure-comm/encrypt': { - post: { tags: ['SecureComm'], summary: 'Encrypt message', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Message encrypted' } } }, - }, - '/api/secure-comm/decrypt': { - post: { tags: ['SecureComm'], summary: 'Decrypt message', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Message decrypted' } } }, - }, - '/api/secure-comm/sign': { - post: { tags: ['SecureComm'], summary: 'Sign message', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Message signed' } } }, - }, - '/api/secure-comm/stats/{userId}': { - get: { tags: ['SecureComm'], summary: 'Get communication statistics', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'userId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Stats retrieved' } } }, - }, - - // ===== ACO ===== - '/api/aco/health': { - get: { tags: ['ACO'], summary: 'ACO system health check', responses: { 200: { description: 'Health status' } } }, - }, - '/api/aco/learning/optimize': { - post: { tags: ['ACO'], summary: 'Optimize learning path using ant colony optimization', requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { startCourse: { type: 'string' }, endCourse: { type: 'string' }, preferences: { type: 'object' } } } } } }, responses: { 200: { description: 'Path optimized' } } }, - }, - '/api/aco/resources/optimize': { - post: { tags: ['ACO'], summary: 'Optimize resource allocation', responses: { 200: { description: 'Resources optimized' } } }, - }, - '/api/aco/swarm/statistics': { - get: { tags: ['ACO'], summary: 'Get swarm intelligence statistics', responses: { 200: { description: 'Statistics retrieved' } } }, - }, - '/api/aco/replanning/path/{userId}': { - get: { tags: ['ACO'], summary: 'Get user learning path', parameters: [{ in: 'path', name: 'userId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Path retrieved' } } }, - }, - - // ===== Federated Learning ===== - '/api/federated-learning/sessions': { - post: { tags: ['FederatedLearning'], summary: 'Initialize federated learning session', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Session initialized' } } }, - }, - '/api/federated-learning/participants': { - post: { tags: ['FederatedLearning'], summary: 'Register participant', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Participant registered' } } }, - get: { tags: ['FederatedLearning'], summary: 'Get all participants', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Participants retrieved' } } }, - }, - '/api/federated-learning/rounds': { - post: { tags: ['FederatedLearning'], summary: 'Start new training round', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Round started' } } }, - }, - '/api/federated-learning/models/versions': { - get: { tags: ['FederatedLearning'], summary: 'Get model versions', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Model versions retrieved' } } }, - }, - '/api/federated-learning/analytics': { - get: { tags: ['FederatedLearning'], summary: 'Get federated learning analytics', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Analytics retrieved' } } }, - }, - '/api/federated-learning/health': { - get: { tags: ['FederatedLearning'], summary: 'Federated learning health check', responses: { 200: { description: 'Health status' } } }, - }, - '/api/federated-learning/initialize': { - post: { tags: ['FederatedLearning'], summary: 'Initialize FL system with model architecture', responses: { 200: { description: 'System initialized' } } }, - }, - '/api/federated-learning/aggregate': { - post: { tags: ['FederatedLearning'], summary: 'Perform privacy-preserving aggregation', responses: { 200: { description: 'Aggregation completed' } } }, - }, - '/api/federated-learning/privacy/budget': { - get: { tags: ['FederatedLearning'], summary: 'Get privacy budget status', responses: { 200: { description: 'Budget status retrieved' } } }, - }, - - // ===== Swarm Learning ===== - '/api/swarm-learning/initialize': { - post: { tags: ['SwarmLearning'], summary: 'Initialize swarm learning system', security: [{ bearerAuth: [] }], responses: { 200: { description: 'System initialized' } } }, - }, - '/api/swarm-learning/swarms': { - post: { tags: ['SwarmLearning'], summary: 'Create new swarm', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Swarm created' } } }, - }, - '/api/swarm-learning/agents': { - post: { tags: ['SwarmLearning'], summary: 'Register agent in swarm', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Agent registered' } } }, - }, - '/api/swarm-learning/analytics': { - get: { tags: ['SwarmLearning'], summary: 'Get swarm learning analytics', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Analytics retrieved' } } }, - }, - '/api/swarm-learning/alerts': { - get: { tags: ['SwarmLearning'], summary: 'Get swarm alerts', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Alerts retrieved' } } }, - }, - '/api/swarm-learning/health': { - get: { tags: ['SwarmLearning'], summary: 'Swarm learning health check', responses: { 200: { description: 'Health status' } } }, - }, - - // ===== Smart Wallet ===== - '/api/smart-wallet/create': { - post: { tags: ['SmartWallet'], summary: 'Create smart contract wallet', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Wallet created' } } }, - }, - '/api/smart-wallet/execute': { - post: { tags: ['SmartWallet'], summary: 'Execute transaction through smart wallet', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Transaction executed' } } }, - }, - '/api/smart-wallet/activity/{walletAddress}': { - get: { tags: ['SmartWallet'], summary: 'Get wallet activity', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'walletAddress', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Activity retrieved' } } }, - }, - '/api/smart-wallet/credentials/stats': { - get: { tags: ['SmartWallet'], summary: 'Get credential renewal stats', responses: { 200: { description: 'Stats retrieved' } } }, - }, - '/api/smart-wallet/social-recovery/setup': { - post: { tags: ['SmartWallet'], summary: 'Setup social recovery for wallet', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Recovery setup complete' } } }, - }, - '/api/smart-wallet/multi-sig/setup': { - post: { tags: ['SmartWallet'], summary: 'Setup multi-signature wallet', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Multi-sig setup complete' } } }, - }, - '/api/smart-wallet/session-key/create': { - post: { tags: ['SmartWallet'], summary: 'Create session key', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Session key created' } } }, - }, - - // ===== AGI Tutor ===== - '/api/agi-tutor/session': { - post: { tags: ['AGITutor'], summary: 'Generate personalized learning session', responses: { 200: { description: 'Session generated' } } }, - }, - '/api/agi-tutor/response': { - post: { tags: ['AGITutor'], summary: 'Process student response and provide adaptive feedback', responses: { 200: { description: 'Response processed' } } }, - }, - '/api/agi-tutor/assessment': { - post: { tags: ['AGITutor'], summary: 'Generate comprehensive assessment', responses: { 200: { description: 'Assessment generated' } } }, - }, - '/api/agi-tutor/guidance': { - post: { tags: ['AGITutor'], summary: 'Get real-time teaching guidance', responses: { 200: { description: 'Guidance provided' } } }, - }, - '/api/agi-tutor/visualization': { - get: { tags: ['AGITutor'], summary: 'Get knowledge visualization', responses: { 200: { description: 'Visualization retrieved' } } }, - }, - - // ===== Analytics ===== - '/api/analytics/overview': { - get: { tags: ['Analytics'], summary: 'Get analytics overview statistics', responses: { 200: { description: 'Overview stats retrieved' } } }, - }, - '/api/analytics/report': { - get: { tags: ['Analytics'], summary: 'Get detailed analytics report', responses: { 200: { description: 'Report retrieved' } } }, - }, - '/api/analytics/export': { - get: { tags: ['Analytics'], summary: 'Export analytics data', responses: { 200: { description: 'Data exported' } } }, - }, - - // ===== Autonomous Agents ===== - '/api/autonomous-agents/status': { - get: { tags: ['AutonomousAgents'], summary: 'Get autonomous agent system status', responses: { 200: { description: 'System status retrieved' } } }, - }, - '/api/autonomous-agents/support/ticket': { - post: { tags: ['AutonomousAgents'], summary: 'Submit support ticket for autonomous handling', requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['userId', 'title', 'description'], properties: { userId: { type: 'string' }, title: { type: 'string' }, description: { type: 'string' }, priority: { type: 'string', default: 'normal' }, category: { type: 'string', default: 'general' } } } } } }, responses: { 200: { description: 'Ticket handled' } } }, - }, - '/api/autonomous-agents/security/status': { - get: { tags: ['AutonomousAgents'], summary: 'Get current security posture', responses: { 200: { description: 'Security status retrieved' } } }, - }, - '/api/autonomous-agents/agents/{type}': { - get: { tags: ['AutonomousAgents'], summary: 'Get specific agent status', parameters: [{ in: 'path', name: 'type', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Agent status retrieved' } } }, - }, - - // ===== Gamification ===== - '/api/gamification/leaderboard': { - get: { tags: ['Gamification'], summary: 'Get leaderboard', parameters: [{ in: 'query', name: 'category', schema: { type: 'string' } }, { in: 'query', name: 'page', schema: { type: 'integer' } }, { in: 'query', name: 'limit', schema: { type: 'integer' } }], responses: { 200: { description: 'Leaderboard retrieved' } } }, - }, - '/api/gamification/user/{userId}/achievements': { - get: { tags: ['Gamification'], summary: 'Get user achievements', parameters: [{ in: 'path', name: 'userId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Achievements retrieved' } } }, - }, - '/api/gamification/event': { - post: { tags: ['Gamification'], summary: 'Process gamification event', requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['userId', 'event'], properties: { userId: { type: 'string' }, event: { type: 'string' }, data: { type: 'object' } } } } } }, responses: { 200: { description: 'Event processed' } } }, - }, - '/api/gamification/challenges': { - get: { tags: ['Gamification'], summary: 'Get active challenges', responses: { 200: { description: 'Challenges retrieved' } } }, - }, - '/api/gamification/challenges/{challengeId}/join': { - post: { tags: ['Gamification'], summary: 'Join a challenge', parameters: [{ in: 'path', name: 'challengeId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Joined challenge' } } }, - }, - - // ===== Admin ===== - '/api/admin/dashboard': { - get: { tags: ['Admin'], summary: 'Get admin dashboard statistics', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Dashboard statistics retrieved' } } }, - }, - '/api/admin/logs': { - get: { tags: ['Admin'], summary: 'Get system logs', security: [{ bearerAuth: [] }], parameters: [{ in: 'query', name: 'level', schema: { type: 'string', default: 'info' } }, { in: 'query', name: 'page', schema: { type: 'integer' } }, { in: 'query', name: 'limit', schema: { type: 'integer' } }], responses: { 200: { description: 'System logs retrieved' } } }, - }, - '/api/admin/settings': { - get: { tags: ['Admin'], summary: 'Get system settings', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Settings retrieved' } } }, - put: { tags: ['Admin'], summary: 'Update system settings', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { category: { type: 'string' }, settings: { type: 'object' } } } } } }, responses: { 200: { description: 'Settings updated' } } }, - }, - '/api/admin/backup': { - post: { tags: ['Admin'], summary: 'Initiate system backup', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Backup initiated' } } }, - }, - '/api/admin/announcements': { - post: { tags: ['Admin'], summary: 'Create system announcement', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['title', 'message'], properties: { title: { type: 'string' }, message: { type: 'string' }, targetRoles: { type: 'array', items: { type: 'string' } }, priority: { type: 'string', enum: ['low', 'normal', 'high', 'urgent'] } } } } } }, responses: { 201: { description: 'Announcement created' } } }, - }, - - // ===== Enrollments ===== - '/api/enrollments': { - get: { tags: ['Enrollments'], summary: 'Get user enrollments', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Enrollments retrieved', content: { 'application/json': { schema: { type: 'object', properties: { data: { type: 'array', items: { $ref: '#/components/schemas/Enrollment' } } } } } } } } }, - post: { tags: ['Enrollments'], summary: 'Create enrollment', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { courseId: { type: 'string' }, userId: { type: 'string' } } } } } }, responses: { 200: { description: 'Enrollment created' } } }, - }, - '/api/enrollments/{id}': { - get: { tags: ['Enrollments'], summary: 'Get enrollment by ID', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Enrollment details' } } }, - put: { tags: ['Enrollments'], summary: 'Update enrollment', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Enrollment updated' } } }, - delete: { tags: ['Enrollments'], summary: 'Cancel enrollment', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Enrollment cancelled' } } }, - }, - '/api/enrollments/{id}/progress': { - get: { tags: ['Enrollments'], summary: 'Get enrollment progress', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Progress retrieved' } } }, - put: { tags: ['Enrollments'], summary: 'Update enrollment progress', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Progress updated' } } }, - }, - '/api/enrollments/{id}/certificate': { - post: { tags: ['Enrollments'], summary: 'Issue certificate for completed enrollment', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Certificate issued' } } }, - }, - '/api/enrollments/bulk': { - post: { tags: ['Enrollments'], summary: 'Bulk enrollment operations', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Bulk operation complete' } } }, - }, - - // ===== Payments ===== - '/api/payments/intent': { - post: { tags: ['Payments'], summary: 'Create payment intent', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Payment intent created' } } }, - }, - '/api/payments/{id}': { - get: { tags: ['Payments'], summary: 'Get payment by ID', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Payment details' } } }, - }, - '/api/payments/history': { - get: { tags: ['Payments'], summary: 'Get user payment history', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Payment history retrieved' } } }, - }, - '/api/payments/stellar/create': { - post: { tags: ['Payments'], summary: 'Create Stellar payment', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Payment created' } } }, - }, - '/api/payments/stellar/balance/{address}': { - get: { tags: ['Payments'], summary: 'Get Stellar balance', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'address', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Balance retrieved' } } }, - }, - '/api/payments/exchange-rates': { - get: { tags: ['Payments'], summary: 'Get exchange rates', responses: { 200: { description: 'Exchange rates retrieved' } } }, - }, - '/api/payments/settings': { - get: { tags: ['Payments'], summary: 'Get payment settings', responses: { 200: { description: 'Payment settings retrieved' } } }, - put: { tags: ['Payments'], summary: 'Update payment settings', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Settings updated' } } }, - }, - - // ===== Plagiarism Detection ===== - '/api/plagiarism/analyze': { - post: { tags: ['Plagiarism'], summary: 'Analyze submission for plagiarism', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['submissionId', 'content'], properties: { submissionId: { type: 'string', format: 'uuid' }, content: { type: 'string' }, contentType: { type: 'string', enum: ['text', 'code', 'mixed'] } } } } } }, responses: { 200: { description: 'Analysis complete' } } }, - }, - '/api/plagiarism/reports/{reportId}': { - get: { tags: ['Plagiarism'], summary: 'Get plagiarism report', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'reportId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Report retrieved' } } }, - }, - '/api/plagiarism/settings': { - get: { tags: ['Plagiarism'], summary: 'Get plagiarism detection settings', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Settings retrieved' } } }, - put: { tags: ['Plagiarism'], summary: 'Update plagiarism detection settings', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Settings updated' } } }, - }, - '/api/plagiarism/health': { - get: { tags: ['Plagiarism'], summary: 'Plagiarism detection health check', responses: { 200: { description: 'Service healthy' } } }, - }, - - // ===== Assignments ===== - '/api/assignments/courses/{courseId}/assignments': { - post: { tags: ['Assignments'], summary: 'Create assignment for course', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'courseId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Assignment created' } } }, - get: { tags: ['Assignments'], summary: 'Get course assignments', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'courseId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Assignments retrieved' } } }, - }, - '/api/assignments/assignments/{assignmentId}': { - get: { tags: ['Assignments'], summary: 'Get assignment details', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'assignmentId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Assignment details' } } }, - put: { tags: ['Assignments'], summary: 'Update assignment', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'assignmentId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Assignment updated' } } }, - delete: { tags: ['Assignments'], summary: 'Delete assignment', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'assignmentId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Assignment deleted' } } }, - }, - '/api/assignments/assignments/{assignmentId}/submissions': { - post: { tags: ['Assignments'], summary: 'Submit assignment', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'assignmentId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Submission received' } } }, - get: { tags: ['Assignments'], summary: 'Get submissions for assignment', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'assignmentId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Submissions retrieved' } } }, - }, - '/api/assignments/submissions/{submissionId}/grade': { - post: { tags: ['Assignments'], summary: 'Grade submission', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'submissionId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Submission graded' } } }, - }, - - // ===== CDN Optimization ===== - '/api/cdn/optimize': { - post: { tags: ['CDN'], summary: 'Optimize content delivery', requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { contentId: { type: 'string' }, contentType: { type: 'string', enum: ['video', 'image', 'audio', 'document', 'other'] }, originalUrl: { type: 'string' } } } } } }, responses: { 200: { description: 'Content optimized' } } }, - }, - '/api/cdn/statistics': { - get: { tags: ['CDN'], summary: 'Get optimization statistics', responses: { 200: { description: 'Statistics retrieved' } } }, - }, - '/api/cdn/configuration': { - put: { tags: ['CDN'], summary: 'Update CDN service configuration', responses: { 200: { description: 'Configuration updated' } } }, - }, - '/api/cdn/health': { - get: { tags: ['CDN'], summary: 'CDN health check', responses: { 200: { description: 'Health status' } } }, - }, - - // ===== Bridge (Cross-Protocol) ===== - '/api/bridge/send': { - post: { tags: ['Bridge'], summary: 'Send cross-chain message', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { destinationChain: { type: 'integer' }, payload: { type: 'string' }, messageType: { type: 'string' }, gasLimit: { type: 'integer' } } } } } }, responses: { 201: { description: 'Message sent' } } }, - }, - '/api/bridge/message/{messageId}': { - get: { tags: ['Bridge'], summary: 'Get cross-chain message details', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'messageId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Message retrieved' }, 404: { description: 'Message not found' } } }, - }, - '/api/bridge/stats': { - get: { tags: ['Bridge'], summary: 'Get bridge statistics', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Statistics retrieved' } } }, - }, - '/api/bridge/gas-cost/{destinationChain}/{gasLimit}': { - get: { tags: ['Bridge'], summary: 'Calculate gas cost for cross-chain message', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'destinationChain', required: true, schema: { type: 'integer' } }, { in: 'path', name: 'gasLimit', required: true, schema: { type: 'integer' } }], responses: { 200: { description: 'Gas cost calculated' } } }, - }, - - // ===== Time-Lock Credentials ===== - '/api/time-lock/issue': { - post: { tags: ['TimeLock'], summary: 'Issue time-locked credential', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['recipient', 'credentialHash', 'metadata', 'releaseTime'], properties: { recipient: { type: 'string' }, credentialHash: { type: 'string' }, metadata: { type: 'object', description: 'Credential metadata' }, releaseTime: { type: 'string', format: 'date-time' } } } } } }, responses: { 201: { description: 'Credential issued' } } }, - }, - '/api/time-lock/release/{credentialId}': { - post: { tags: ['TimeLock'], summary: 'Release time-locked credential', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'credentialId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Credential released' } } }, - }, - '/api/time-lock/emergency-revoke/{credentialId}': { - post: { tags: ['TimeLock'], summary: 'Emergency revoke credential', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'credentialId', required: true, schema: { type: 'string' } }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['reason'], properties: { reason: { type: 'string' } } } } } }, responses: { 200: { description: 'Credential revoked' } } }, - }, - '/api/time-lock/batch-release': { - post: { tags: ['TimeLock'], summary: 'Batch release multiple credentials', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Batch release completed' } } }, - }, - '/api/time-lock/recipient/{recipient}': { - get: { tags: ['TimeLock'], summary: 'Get credentials by recipient', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'recipient', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Credentials retrieved' } } }, - }, - - // ===== Translation ===== - '/api/translate/text': { - post: { tags: ['Translation'], summary: 'Translate text content', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['text', 'sourceLanguage', 'targetLanguage'], properties: { text: { type: 'string' }, sourceLanguage: { type: 'string' }, targetLanguage: { type: 'string' }, contentType: { type: 'string', enum: ['course', 'subtitle', 'interaction', 'general'] } } } } } }, responses: { 200: { description: 'Translation complete' } } }, - }, - '/api/translate/batch': { - post: { tags: ['Translation'], summary: 'Batch translate multiple texts', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Batch translation complete' } } }, - }, - '/api/translate/subtitles': { - post: { tags: ['Translation'], summary: 'Translate and synchronize subtitles', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Subtitle translation complete' } } }, - }, - '/api/translate/quality/{contentType}': { - get: { tags: ['Translation'], summary: 'Get translation quality metrics', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'contentType', schema: { type: 'string' } }], responses: { 200: { description: 'Quality metrics retrieved' } } }, - }, - - // ===== VRF ===== - '/api/vrf/request': { - post: { tags: ['VRF'], summary: 'Request verifiable random number', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['seed', 'purpose'], properties: { seed: { type: 'string' }, purpose: { type: 'string' }, context: { type: 'string' } } } } } }, responses: { 201: { description: 'VRF request created' } } }, - }, - '/api/vrf/generate': { - post: { tags: ['VRF'], summary: 'Generate random number for specific purpose', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Random value generated' } } }, - }, - '/api/vrf/beacon/latest': { - get: { tags: ['VRF'], summary: 'Get latest randomness beacon', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Beacon retrieved' } } }, - }, - '/api/vrf/stats': { - get: { tags: ['VRF'], summary: 'Get VRF system statistics', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Statistics retrieved' } } }, - }, - '/api/vrf/commit': { - post: { tags: ['VRF'], summary: 'Commit to a value (commit-reveal scheme)', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Commitment recorded' } } }, - }, - - // ===== Offline ===== - '/api/offline': { - get: { tags: ['Offline'], summary: 'Get offline content for user', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Offline content retrieved' } } }, - }, - '/api/offline/request': { - post: { tags: ['Offline'], summary: 'Request content for offline download', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['contentId', 'deviceId'], properties: { contentId: { type: 'string' }, deviceId: { type: 'string' }, quality: { type: 'string', enum: ['low', 'medium', 'high'], default: 'medium' } } } } } }, responses: { 200: { description: 'Download queued' } } }, - }, - '/api/offline/storage/{deviceId}': { - get: { tags: ['Offline'], summary: 'Get storage usage for device', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'deviceId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Storage info retrieved' } } }, - }, - '/api/offline/{offlineId}': { - delete: { tags: ['Offline'], summary: 'Delete offline content', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'offlineId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Content deleted' } } }, - }, - - // ===== Optimization ===== - '/api/optimization/initialize': { - post: { tags: ['Optimization'], summary: 'Initialize optimization services', responses: { 200: { description: 'Services initialized' } } }, - }, - '/api/optimization/learning-paths/optimize': { - post: { tags: ['Optimization'], summary: 'Optimize learning path', responses: { 200: { description: 'Path optimized' } } }, - }, - '/api/optimization/analytics': { - get: { tags: ['Optimization'], summary: 'Get optimization analytics', responses: { 200: { description: 'Analytics retrieved' } } }, - }, - '/api/optimization/health': { - get: { tags: ['Optimization'], summary: 'Optimization service health check', responses: { 200: { description: 'Health status' } } }, - }, - - // ===== Prediction ===== - '/api/prediction/students/{studentId}/predict': { - post: { tags: ['Prediction'], summary: 'Predict student outcomes', requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { studentData: { type: 'object' } } } } } }, parameters: [{ in: 'path', name: 'studentId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Prediction generated' } } }, - }, - '/api/prediction/at-risk/identify': { - post: { tags: ['Prediction'], summary: 'Identify at-risk students', responses: { 200: { description: 'At-risk students identified' } } }, - }, - '/api/prediction/models/accuracy': { - get: { tags: ['Prediction'], summary: 'Get model accuracy metrics', responses: { 200: { description: 'Accuracy metrics retrieved' } } }, - }, - '/api/prediction/health': { - get: { tags: ['Prediction'], summary: 'Prediction service health check', responses: { 200: { description: 'Service healthy' } } }, - }, - - // ===== Bookmark ===== - '/api/bookmarks': { - get: { tags: ['Bookmarks'], summary: 'Get all bookmarks for user', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Bookmarks retrieved' } } }, - post: { tags: ['Bookmarks'], summary: 'Create or update bookmark', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['contentId', 'timestamp'], properties: { contentId: { type: 'string' }, timestamp: { type: 'number' }, note: { type: 'string' } } } } } }, responses: { 200: { description: 'Bookmark created/updated' } } }, - }, - '/api/bookmarks/{bookmarkId}': { - delete: { tags: ['Bookmarks'], summary: 'Delete bookmark', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'bookmarkId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Bookmark deleted' } } }, - }, - '/api/bookmarks/notes': { - get: { tags: ['Bookmarks'], summary: 'Get all notes for user', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Notes retrieved' } } }, - post: { tags: ['Bookmarks'], summary: 'Create note', security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['contentId', 'timestamp', 'text'], properties: { contentId: { type: 'string' }, timestamp: { type: 'number' }, text: { type: 'string' }, isPrivate: { type: 'boolean', default: true }, tags: { type: 'array', items: { type: 'string' } } } } } } }, responses: { 201: { description: 'Note created' } } }, - }, - '/api/bookmarks/notes/{noteId}': { - put: { tags: ['Bookmarks'], summary: 'Update note', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'noteId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Note updated' } } }, - delete: { tags: ['Bookmarks'], summary: 'Delete note', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'noteId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Note deleted' } } }, - }, - - // ===== Quantum ===== - '/api/quantum/providers': { - get: { tags: ['Quantum'], summary: 'Get available quantum providers', responses: { 200: { description: 'Providers listed' } } }, - }, - '/api/quantum/execute': { - post: { tags: ['Quantum'], summary: 'Execute quantum circuit', requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { circuit: { type: 'object' }, shots: { type: 'integer', default: 1024 }, provider: { type: 'string' } } } } } }, responses: { 200: { description: 'Circuit executed' } } }, - }, - '/api/quantum/health': { - get: { tags: ['Quantum'], summary: 'Quantum services health check', responses: { 200: { description: 'Health status' } } }, - }, - - // ===== Quantum Encryption ===== - '/api/quantum-encryption/keys/generate': { - post: { tags: ['QuantumEncryption'], summary: 'Generate quantum-resistant key pair', requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { algorithm: { type: 'string', enum: ['CRYSTALS_KYBER', 'CRYSTALS_DILITHIUM', 'FALCON'], default: 'CRYSTALS_KYBER' }, securityLevel: { type: 'integer', default: 4 } } } } } }, responses: { 200: { description: 'Key pair generated' } } }, - }, - '/api/quantum-encryption/encrypt': { - post: { tags: ['QuantumEncryption'], summary: 'Encrypt data using quantum-resistant encryption', responses: { 200: { description: 'Data encrypted' } } }, - }, - '/api/quantum-encryption/decrypt': { - post: { tags: ['QuantumEncryption'], summary: 'Decrypt quantum-encrypted data', responses: { 200: { description: 'Data decrypted' } } }, - }, - '/api/quantum-encryption/sign': { - post: { tags: ['QuantumEncryption'], summary: 'Sign data with quantum-resistant signature', responses: { 200: { description: 'Data signed' } } }, - }, - '/api/quantum-encryption/health': { - get: { tags: ['QuantumEncryption'], summary: 'Quantum encryption system health', responses: { 200: { description: 'Health status' } } }, - }, - - // ===== Fraud Detection ===== - '/api/fraud-detection/health': { - get: { tags: ['FraudDetection'], summary: 'Fraud detection system health check', responses: { 200: { description: 'Health status' } } }, - }, - '/api/fraud-detection/analyze-submission': { - post: { tags: ['FraudDetection'], summary: 'Analyze submission for plagiarism', requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['submissionId', 'userId', 'assignmentId', 'content'], properties: { submissionId: { type: 'string' }, userId: { type: 'string' }, assignmentId: { type: 'string' }, content: { type: 'string' } } } } } }, responses: { 200: { description: 'Analysis complete' } } }, - }, - '/api/fraud-detection/statistics': { - get: { tags: ['FraudDetection'], summary: 'Get fraud detection statistics', responses: { 200: { description: 'Statistics retrieved' } } }, - }, - '/api/fraud-detection/alerts': { - get: { tags: ['FraudDetection'], summary: 'Get fraud alerts', responses: { 200: { description: 'Alerts retrieved' } } }, - }, - - // ===== Search ===== - '/api/search': { - get: { tags: ['Search'], summary: 'Search courses and content', parameters: [{ in: 'query', name: 'q', schema: { type: 'string' } }, { in: 'query', name: 'query', schema: { type: 'string' } }], responses: { 200: { description: 'Search results' } } }, - }, - '/api/search/suggestions': { - get: { tags: ['Search'], summary: 'Get search suggestions', responses: { 200: { description: 'Suggestions retrieved' } } }, - }, - '/api/search/voice': { - post: { tags: ['Search'], summary: 'Voice search', responses: { 200: { description: 'Voice query processed' } } }, - }, - '/api/search/trending': { - get: { tags: ['Search'], summary: 'Get trending content', responses: { 200: { description: 'Trending content retrieved' } } }, - }, - - // ===== Recommendations ===== - '/api/recommendations/user/{userId}': { - get: { tags: ['Recommendations'], summary: 'Get personalized recommendations', parameters: [{ in: 'path', name: 'userId', required: true, schema: { type: 'string' } }, { in: 'query', name: 'count', schema: { type: 'integer', default: 10 } }, { in: 'query', name: 'algorithm', schema: { type: 'string', enum: ['collaborative', 'content_based', 'hybrid'], default: 'hybrid' } }], responses: { 200: { description: 'Recommendations retrieved' } } }, - }, - '/api/recommendations/popular': { - get: { tags: ['Recommendations'], summary: 'Get popular courses', responses: { 200: { description: 'Popular courses retrieved' } } }, - }, - '/api/recommendations/trending': { - get: { tags: ['Recommendations'], summary: 'Get trending courses', responses: { 200: { description: 'Trending courses retrieved' } } }, - }, - '/api/recommendations/similar/{courseId}': { - get: { tags: ['Recommendations'], summary: 'Get courses similar to given course', parameters: [{ in: 'path', name: 'courseId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Similar courses retrieved' } } }, - }, - '/api/recommendations/models/train': { - post: { tags: ['Recommendations'], summary: 'Train recommendation models', responses: { 200: { description: 'Training completed' } } }, - }, - - // ===== Tenants ===== - '/api/tenants': { - post: { tags: ['Tenants'], summary: 'Create new tenant', requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['name', 'subdomain'], properties: { name: { type: 'string' }, subdomain: { type: 'string' }, plan: { type: 'string', enum: ['starter', 'professional', 'enterprise'] } } } } } }, responses: { 201: { description: 'Tenant created' } } }, - }, - '/api/tenants/{tenantId}/users': { - post: { tags: ['Tenants'], summary: 'Create user in tenant', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'tenantId', required: true, schema: { type: 'string' } }], responses: { 201: { description: 'User created' } } }, - get: { tags: ['Tenants'], summary: 'Get tenant users', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'tenantId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Users retrieved' } } }, - }, - '/api/tenants/{tenantId}/settings': { - put: { tags: ['Tenants'], summary: 'Update tenant settings', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'tenantId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Settings updated' } } }, - }, - '/api/tenants/{tenantId}': { - delete: { tags: ['Tenants'], summary: 'Delete tenant', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'tenantId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Tenant deleted' } } }, - }, - - // ===== Tenant Analytics ===== - '/api/analytics/tenants/cross-tenant': { - get: { tags: ['TenantAnalytics'], summary: 'Get cross-tenant analytics', security: [{ bearerAuth: [] }], responses: { 200: { description: 'Analytics retrieved' } } }, - }, - '/api/analytics/tenants/{tenantId}': { - get: { tags: ['TenantAnalytics'], summary: 'Get tenant-specific analytics', security: [{ bearerAuth: [] }], parameters: [{ in: 'path', name: 'tenantId', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Tenant analytics retrieved' } } }, - }, - - // ===== Health ===== - '/api/health': { - get: { tags: ['System'], summary: 'Health check endpoint', responses: { 200: { description: 'Service is healthy', content: { 'application/json': { schema: { type: 'object', properties: { status: { type: 'string', example: 'healthy' }, timestamp: { type: 'string', format: 'date-time' }, uptime: { type: 'number' } } } } } } } }, - }, - }, - }, - apis: [ - './src/routes/*.js', - './src/routes/*.ts', - ], -}; - -export const swaggerSpec = swaggerJsdoc(options); diff --git a/backend/src/index.ts b/backend/src/index.ts index 5b65f4c..e87fe57 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -4,7 +4,6 @@ import dotenv from 'dotenv'; import cors from 'cors'; import helmet from 'helmet'; import { Redis } from 'ioredis'; -import swaggerUi from 'swagger-ui-express'; import logger from './utils/logger'; import requestLogger from './middleware/requestLogger'; import { connectRedis } from './utils/redis'; @@ -13,7 +12,6 @@ import { setSyncWebsocketEmitter } from './services/syncService'; import { initCollaborationService } from './services/initCollaboration'; // @ts-ignore import SecureRealtimeCommunication from './services/secureRealtimeCommunication'; -import { swaggerSpec } from './config/swagger'; // @ts-ignore import * as transactionQueue from './services/transactionQueue'; @@ -127,12 +125,6 @@ app.use(detectSuspiciousPatterns); // NEW/Updated: Sanitize all inputs app.use(requestSanitizer); -// Serve Swagger UI at /api-docs -app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec, { - explorer: true, - customSiteTitle: 'AetherMint API Docs', -})); - // API routes app.use('/api/quizzes', quizRoutes); app.use('/api/events', eventLoggerRoutes); diff --git a/backend/src/routes/aco.js b/backend/src/routes/aco.js index 4412d36..c647f13 100644 --- a/backend/src/routes/aco.js +++ b/backend/src/routes/aco.js @@ -1,63 +1,563 @@ /** - * @openapi - * tags: - * - name: ACO - * description: Ant Colony Optimization for adaptive learning paths + * ACO Optimization API Routes + * Provides REST endpoints for ant colony optimization functionality */ -const express = require("express"); +const express = require('express'); const router = express.Router(); -const { authenticate } = require("../middleware/auth"); -const acoController = require("../controllers/acoController"); +const LearningPathOptimizer = require('../services/aco/LearningPathOptimizer'); +const ResourceAllocationOptimizer = require('../services/aco/ResourceAllocationOptimizer'); +const DynamicPathReplanner = require('../services/aco/DynamicPathReplanner'); +const SwarmIntelligenceCoordinator = require('../services/aco/SwarmIntelligenceCoordinator'); +const OptimizationAnalytics = require('../services/aco/OptimizationAnalytics'); -router.use(authenticate); +// Initialize services +const learningOptimizer = new LearningPathOptimizer(); +const resourceOptimizer = new ResourceAllocationOptimizer(); +const pathReplanner = new DynamicPathReplanner(); +const swarmCoordinator = new SwarmIntelligenceCoordinator(); +const analytics = new OptimizationAnalytics(); + +// Middleware for request validation +const validateRequest = (req, res, next) => { + try { + // Basic validation + if (!req.body && req.method !== 'GET') { + return res.status(400).json({ error: 'Request body is required' }); + } + next(); + } catch (error) { + res.status(400).json({ error: error.message }); + } +}; /** - * @openapi - * /api/aco/optimize: - * post: - * tags: [ACO] - * summary: Optimize learning path using ant colony algorithm - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Learning path optimized + * Learning Path Optimization Routes */ -router.post("/optimize", acoController.optimizePath); + +// Setup learning environment +router.post('/learning/setup', validateRequest, (req, res) => { + try { + const { courses, dependencies } = req.body; + + if (!courses || !Array.isArray(courses)) { + return res.status(400).json({ error: 'Courses array is required' }); + } + + learningOptimizer.setupLearningEnvironment(courses, dependencies || {}); + + res.json({ + success: true, + message: 'Learning environment setup successfully', + coursesCount: courses.length + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Optimize learning path +router.post('/learning/optimize', validateRequest, (req, res) => { + try { + const { startCourse, endCourse, preferences } = req.body; + + if (!startCourse || !endCourse) { + return res.status(400).json({ error: 'Start and end courses are required' }); + } + + learningOptimizer.setUserPreferences(preferences || {}); + const result = learningOptimizer.optimizeLearningPath(startCourse, endCourse); + + // Record analytics + analytics.recordMetrics(`learning_${Date.now()}`, { + type: 'learning_path', + efficiency: result.efficiency, + totalDistance: result.totalDistance, + iterations: result.iterations, + pathLength: result.path.length + }); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Get alternative learning paths +router.post('/learning/alternatives', validateRequest, (req, res) => { + try { + const { startCourse, endCourse, numAlternatives } = req.body; + + if (!startCourse || !endCourse) { + return res.status(400).json({ error: 'Start and end courses are required' }); + } + + const alternatives = learningOptimizer.getAlternativePaths( + startCourse, + endCourse, + numAlternatives || 3 + ); + + res.json({ + success: true, + alternatives + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Get learning path analytics +router.post('/learning/analytics', validateRequest, (req, res) => { + try { + const { path } = req.body; + + if (!path || !Array.isArray(path)) { + return res.status(400).json({ error: 'Path array is required' }); + } + + const analytics = learningOptimizer.getPathAnalytics(path); + + res.json({ + success: true, + analytics + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); /** - * @openapi - * /api/aco/pheromone/update: - * post: - * tags: [ACO] - * summary: Update pheromone levels based on user progress - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Pheromones updated + * Resource Allocation Optimization Routes */ -router.post("/pheromone/update", acoController.updatePheromones); + +// Setup resource environment +router.post('/resources/setup', validateRequest, (req, res) => { + try { + const { resources, demands, constraints, objectives } = req.body; + + if (!resources || !Array.isArray(resources)) { + return res.status(400).json({ error: 'Resources array is required' }); + } + + resourceOptimizer.setupResources(resources); + + if (demands && Array.isArray(demands)) { + resourceOptimizer.setupDemands(demands); + } + + if (constraints && Array.isArray(constraints)) { + resourceOptimizer.setupConstraints(constraints); + } + + if (objectives && Array.isArray(objectives)) { + resourceOptimizer.setObjectives(objectives); + } + + res.json({ + success: true, + message: 'Resource environment setup successfully', + resourcesCount: resources.length + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Optimize resource allocation +router.post('/resources/optimize', validateRequest, (req, res) => { + try { + const result = resourceOptimizer.optimizeAllocation(); + + // Record analytics + analytics.recordMetrics(`resource_${Date.now()}`, { + type: 'resource_allocation', + efficiency: result.utilization, + utilization: result.utilization, + satisfaction: result.satisfaction, + cost: result.totalCost, + score: result.score + }); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Get resource allocation analytics +router.get('/resources/analytics/:allocationId', (req, res) => { + try { + const { allocationId } = req.params; + + // This would typically fetch allocation from database + // For now, return a placeholder + res.json({ + success: true, + allocationId, + message: 'Analytics endpoint - requires database integration' + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); /** - * @openapi - * /api/aco/path/{userId}: - * get: - * tags: [ACO] - * summary: Get optimized learning path for user - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Learning path retrieved + * Dynamic Path Replanning Routes */ -router.get("/path/:userId", acoController.getLearningPath); + +// Initialize user path +router.post('/replanning/initialize', validateRequest, (req, res) => { + try { + const { userId, startCourse, endCourse, preferences } = req.body; + + if (!userId || !startCourse || !endCourse) { + return res.status(400).json({ error: 'UserId, startCourse, and endCourse are required' }); + } + + const path = pathReplanner.initializePath(userId, startCourse, endCourse, preferences); + + res.json({ + success: true, + path + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Record change event +router.post('/replanning/events', validateRequest, (req, res) => { + try { + const event = req.body; + + if (!event.type || !event.data) { + return res.status(400).json({ error: 'Event type and data are required' }); + } + + const eventId = pathReplanner.recordChangeEvent(event); + + res.json({ + success: true, + eventId + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Get current user path +router.get('/replanning/path/:userId', (req, res) => { + try { + const { userId } = req.params; + const path = pathReplanner.getCurrentPath(userId); + + if (!path) { + return res.status(404).json({ error: 'Path not found for user' }); + } + + res.json({ + success: true, + path + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Get user path analytics +router.get('/replanning/analytics/:userId', (req, res) => { + try { + const { userId } = req.params; + const analytics = pathReplanner.getPathAnalytics(userId); + + if (!analytics) { + return res.status(404).json({ error: 'Analytics not found for user' }); + } + + res.json({ + success: true, + analytics + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Get system statistics +router.get('/replanning/statistics', (req, res) => { + try { + const stats = pathReplanner.getSystemStatistics(); + + res.json({ + success: true, + statistics: stats + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +/** + * Swarm Intelligence Routes + */ + +// Add agent to swarm +router.post('/swarm/agents', validateRequest, (req, res) => { + try { + const { agentId, agent, specialization } = req.body; + + if (!agentId || !agent) { + return res.status(400).json({ error: 'AgentId and agent are required' }); + } + + const agentInfo = swarmCoordinator.addAgent(agentId, agent, specialization); + + res.json({ + success: true, + agent: agentInfo + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Execute swarm iteration +router.post('/swarm/execute', validateRequest, (req, res) => { + try { + const { problemContext } = req.body; + + if (!problemContext) { + return res.status(400).json({ error: 'Problem context is required' }); + } + + // Execute iteration asynchronously + swarmCoordinator.executeIteration(problemContext).then(result => { + // Record analytics + analytics.recordMetrics(`swarm_${Date.now()}`, { + type: 'swarm_intelligence', + efficiency: result.globalBest?.fitness || 0, + iterations: result.iteration, + convergence: result.convergence ? 1 : 0 + }); + }).catch(error => { + console.error('Swarm execution error:', error); + }); + + res.json({ + success: true, + message: 'Swarm iteration started' + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Get swarm statistics +router.get('/swarm/statistics', (req, res) => { + try { + const stats = swarmCoordinator.getSwarmStatistics(); + + res.json({ + success: true, + statistics: stats + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +/** + * Analytics and Visualization Routes + */ + +// Get performance visualization data +router.get('/analytics/visualization/:optimizationId', (req, res) => { + try { + const { optimizationId } = req.params; + const { timeRange } = req.query; + + const visualization = analytics.getPerformanceVisualization( + optimizationId, + timeRange ? parseInt(timeRange) : null + ); + + res.json({ + success: true, + visualization + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Generate comparative analysis +router.post('/analytics/comparison', validateRequest, (req, res) => { + try { + const { optimizationIds } = req.body; + + if (!optimizationIds || !Array.isArray(optimizationIds)) { + return res.status(400).json({ error: 'Optimization IDs array is required' }); + } + + const comparison = analytics.generateComparativeAnalysis(optimizationIds); + + res.json({ + success: true, + comparison + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Get dashboard data +router.get('/analytics/dashboard', (req, res) => { + try { + const dashboard = analytics.getDashboardData(); + + res.json({ + success: true, + dashboard + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Export analytics data +router.get('/analytics/export', (req, res) => { + try { + const { format } = req.query; + const data = analytics.exportData(format || 'json'); + + const contentType = format === 'csv' ? 'text/csv' : 'application/json'; + const filename = `analytics_${Date.now()}.${format || 'json'}`; + + res.setHeader('Content-Type', contentType); + res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); + res.send(data); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +/** + * System Management Routes + */ + +// Get system health +router.get('/health', (req, res) => { + try { + const health = { + status: 'healthy', + timestamp: new Date().toISOString(), + services: { + learningOptimizer: 'active', + resourceOptimizer: 'active', + pathReplanner: 'active', + swarmCoordinator: 'active', + analytics: 'active' + }, + metrics: { + totalOptimizations: analytics.metrics.size, + activeOptimizations: analytics.realTimeMetrics.size, + swarmAgents: swarmCoordinator.agents.size + } + }; + + res.json(health); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Get system configuration +router.get('/config', (req, res) => { + try { + const config = { + learningOptimizer: { + numAnts: learningOptimizer.aco.numAnts, + numIterations: learningOptimizer.aco.numIterations, + alpha: learningOptimizer.aco.alpha, + beta: learningOptimizer.aco.beta, + rho: learningOptimizer.aco.rho + }, + resourceOptimizer: { + numAnts: resourceOptimizer.aco.numAnts, + numIterations: resourceOptimizer.aco.numIterations, + alpha: resourceOptimizer.aco.alpha, + beta: resourceOptimizer.aco.beta, + rho: resourceOptimizer.aco.rho + }, + pathReplanner: { + thresholds: pathReplanner.replanThresholds + }, + swarmCoordinator: { + populationSize: swarmCoordinator.config.populationSize, + communicationRadius: swarmCoordinator.config.communicationRadius, + knowledgeSharingRate: swarmCoordinator.config.knowledgeSharingRate, + collaborationMode: swarmCoordinator.config.collaborationMode + }, + analytics: { + alertThresholds: analytics.alertThresholds + } + }; + + res.json({ + success: true, + config + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Update system configuration +router.put('/config', validateRequest, (req, res) => { + try { + const { service, config } = req.body; + + if (!service || !config) { + return res.status(400).json({ error: 'Service and config are required' }); + } + + switch (service) { + case 'pathReplanner': + pathReplanner.setReplanThresholds(config.thresholds); + break; + case 'swarmCoordinator': + swarmCoordinator.updateConfig(config); + break; + case 'analytics': + analytics.setAlertThresholds(config.alertThresholds); + break; + default: + return res.status(400).json({ error: 'Unknown service' }); + } + + res.json({ + success: true, + message: `${service} configuration updated successfully` + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Error handling middleware +router.use((error, req, res, next) => { + console.error('ACO API Error:', error); + res.status(500).json({ + error: 'Internal server error', + message: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong' + }); +}); module.exports = router; diff --git a/backend/src/routes/admin.js b/backend/src/routes/admin.js index c34e826..e450b39 100644 --- a/backend/src/routes/admin.js +++ b/backend/src/routes/admin.js @@ -1,187 +1,470 @@ -/** - * @openapi - * tags: - * - name: Admin - * description: Administrative operations - */ - -const express = require("express"); +const express = require('express'); +const { authenticateToken, requireAdmin, requirePermission } = require('../middleware/auth'); +const { PERMISSIONS, UserRole } = require('../utils/roles'); const router = express.Router(); -const { authenticate, authorize } = require("../middleware/auth"); -const adminController = require("../controllers/adminController"); -router.use(authenticate, authorize("admin")); +// Apply authentication and admin middleware to all routes +router.use(authenticateToken); +router.use(requireAdmin); /** - * @openapi - * /api/admin/users/{userId}: - * get: - * tags: [Admin] - * summary: Get user details (admin) - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: User details retrieved + * Get admin dashboard statistics + * GET /api/admin/dashboard */ -router.get("/users/:userId", adminController.getUser); +router.get('/dashboard', requirePermission(PERMISSIONS.ADMIN_PANEL), (req, res) => { + try { + // Mock statistics - replace with actual database queries + const stats = { + users: { + total: 1250, + students: 1000, + educators: 200, + admins: 50, + newThisMonth: 75 + }, + courses: { + total: 150, + published: 120, + draft: 30, + newThisMonth: 12 + }, + quizzes: { + total: 450, + active: 380, + completed: 2500, + averageScore: 78.5 + }, + system: { + uptime: '99.9%', + storage: '45.2 GB used / 100 GB', + lastBackup: new Date().toISOString(), + activeConnections: 234 + } + }; -/** - * @openapi - * /api/admin/users/{userId}: - * put: - * tags: [Admin] - * summary: Update user (admin) - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: User updated - */ -router.put("/users/:userId", adminController.updateUser); + res.json({ + message: 'Dashboard statistics retrieved successfully', + stats, + timestamp: new Date().toISOString() + }); + } catch (error) { + console.error('Dashboard error:', error); + res.status(500).json({ + error: 'Internal server error', + message: 'Error retrieving dashboard statistics' + }); + } +}); /** - * @openapi - * /api/admin/users/{userId}: - * delete: - * tags: [Admin] - * summary: Delete user (admin) - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: User deleted + * Get system logs (Admin only) + * GET /api/admin/logs */ -router.delete("/users/:userId", adminController.deleteUser); +router.get('/logs', requirePermission(PERMISSIONS.SYSTEM_MANAGE), (req, res) => { + try { + const { + level = 'info', + page = 1, + limit = 50, + startDate, + endDate + } = req.query; -/** - * @openapi - * /api/admin/users: - * get: - * tags: [Admin] - * summary: List all users (admin) - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Users listed - */ -router.get("/users", adminController.listUsers); + // Mock logs - replace with actual log retrieval system + const logs = [ + { + id: 1, + level: 'info', + message: 'User login successful', + userId: '123', + timestamp: new Date().toISOString(), + ip: '192.168.1.1', + userAgent: 'Mozilla/5.0...' + }, + { + id: 2, + level: 'warning', + message: 'Failed login attempt', + userId: null, + timestamp: new Date(Date.now() - 3600000).toISOString(), + ip: '192.168.1.100', + userAgent: 'Mozilla/5.0...' + }, + { + id: 3, + level: 'error', + message: 'Database connection failed', + userId: null, + timestamp: new Date(Date.now() - 7200000).toISOString(), + ip: '127.0.0.1', + userAgent: 'Internal' + } + ]; + + // Filter by level if specified + const filteredLogs = level === 'all' ? logs : logs.filter(log => log.level === level); + + // Pagination + const offset = (page - 1) * limit; + const paginatedLogs = filteredLogs.slice(offset, offset + parseInt(limit)); + + res.json({ + logs: paginatedLogs, + pagination: { + page: parseInt(page), + limit: parseInt(limit), + total: filteredLogs.length, + pages: Math.ceil(filteredLogs.length / limit) + }, + filters: { + level, + startDate, + endDate + } + }); + } catch (error) { + console.error('Logs retrieval error:', error); + res.status(500).json({ + error: 'Internal server error', + message: 'Error retrieving system logs' + }); + } +}); /** - * @openapi - * /api/admin/system/health: - * get: - * tags: [Admin] - * summary: Get system health status (admin) - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Health status retrieved + * Get user activity report (Admin only) + * GET /api/admin/reports/user-activity */ -router.get("/system/health", adminController.getSystemHealth); +router.get('/reports/user-activity', requirePermission(PERMISSIONS.USER_READ), (req, res) => { + try { + const { period = '30d', role } = req.query; + + // Mock activity data - replace with actual analytics + const activityData = { + period, + totalUsers: 1250, + activeUsers: 890, + newUsers: 75, + userRetention: { + day1: 95, + day7: 82, + day30: 68, + day90: 45 + }, + roleDistribution: { + students: 1000, + educators: 200, + admins: 50 + }, + dailyActivity: [ + { date: '2024-01-01', logins: 245, signups: 12, courseCompletions: 8 }, + { date: '2024-01-02', logins: 289, signups: 15, courseCompletions: 12 }, + { date: '2024-01-03', logins: 312, signups: 8, courseCompletions: 15 } + ] + }; + + res.json({ + message: 'User activity report generated successfully', + data: activityData, + generatedAt: new Date().toISOString() + }); + } catch (error) { + console.error('Activity report error:', error); + res.status(500).json({ + error: 'Internal server error', + message: 'Error generating activity report' + }); + } +}); /** - * @openapi - * /api/admin/system/metrics: - * get: - * tags: [Admin] - * summary: Get system metrics (admin) - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Metrics retrieved + * Get course performance report (Admin only) + * GET /api/admin/reports/course-performance */ -router.get("/system/metrics", adminController.getSystemMetrics); +router.get('/reports/course-performance', requirePermission(PERMISSIONS.COURSE_READ), (req, res) => { + try { + const { period = '30d', courseId } = req.query; + + // Mock course performance data + const performanceData = { + period, + totalCourses: 150, + averageCompletion: 72.5, + totalEnrollments: 5432, + averageRating: 4.3, + topCourses: [ + { + id: '1', + title: 'Introduction to Blockchain', + enrollments: 450, + completions: 380, + averageRating: 4.7, + completionRate: 84.4 + }, + { + id: '2', + title: 'Advanced Smart Contracts', + enrollments: 320, + completions: 245, + averageRating: 4.5, + completionRate: 76.6 + } + ], + categoryPerformance: [ + { category: 'Blockchain', courses: 45, enrollments: 2100, avgCompletion: 78.2 }, + { category: 'Programming', courses: 38, enrollments: 1800, avgCompletion: 71.5 }, + { category: 'Design', courses: 28, enrollments: 980, avgCompletion: 65.3 } + ] + }; + + res.json({ + message: 'Course performance report generated successfully', + data: performanceData, + generatedAt: new Date().toISOString() + }); + } catch (error) { + console.error('Course performance report error:', error); + res.status(500).json({ + error: 'Internal server error', + message: 'Error generating course performance report' + }); + } +}); /** - * @openapi - * /api/admin/system/logs: - * get: - * tags: [Admin] - * summary: Get system logs (admin) - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Logs retrieved + * Manage system settings (Admin only) + * GET /api/admin/settings */ -router.get("/system/logs", adminController.getSystemLogs); +router.get('/settings', requirePermission(PERMISSIONS.SYSTEM_MANAGE), (req, res) => { + try { + // Mock system settings - replace with actual configuration management + const settings = { + general: { + siteName: 'AetherMint Education Platform', + siteDescription: 'Decentralized education on Stellar', + maintenanceMode: false, + registrationEnabled: true, + emailVerificationRequired: true + }, + security: { + passwordMinLength: 8, + sessionTimeout: 24, + maxLoginAttempts: 5, + lockoutDuration: 15 + }, + features: { + coursesEnabled: true, + quizzesEnabled: true, + certificatesEnabled: true, + socialFeaturesEnabled: true + }, + limits: { + maxCoursesPerUser: 10, + maxQuizzesPerCourse: 50, + maxFileSize: 10485760, // 10MB + maxUsersPerPlan: 1000 + } + }; + + res.json({ + message: 'System settings retrieved successfully', + settings + }); + } catch (error) { + console.error('Settings retrieval error:', error); + res.status(500).json({ + error: 'Internal server error', + message: 'Error retrieving system settings' + }); + } +}); /** - * @openapi - * /api/admin/backup: - * post: - * tags: [Admin] - * summary: Create system backup (admin) - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Backup created + * Update system settings (Admin only) + * PUT /api/admin/settings */ -router.post("/backup", adminController.createBackup); +router.put('/settings', requirePermission(PERMISSIONS.SYSTEM_MANAGE), (req, res) => { + try { + const { category, settings } = req.body; + + if (!category || !settings) { + return res.status(400).json({ + error: 'Invalid request', + message: 'Category and settings are required' + }); + } + + // Mock settings update - replace with actual configuration update + const validCategories = ['general', 'security', 'features', 'limits']; + + if (!validCategories.includes(category)) { + return res.status(400).json({ + error: 'Invalid category', + message: `Category must be one of: ${validCategories.join(', ')}` + }); + } + + // In a real implementation, you would validate and update the settings in your database + // or configuration file + + res.json({ + message: 'System settings updated successfully', + category, + settings, + updatedAt: new Date().toISOString() + }); + } catch (error) { + console.error('Settings update error:', error); + res.status(500).json({ + error: 'Internal server error', + message: 'Error updating system settings' + }); + } +}); /** - * @openapi - * /api/admin/restore: - * post: - * tags: [Admin] - * summary: Restore system backup (admin) - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Backup restored + * Backup system data (Admin only) + * POST /api/admin/backup */ -router.post("/restore", adminController.restoreBackup); +router.post('/backup', requirePermission(PERMISSIONS.SYSTEM_MANAGE), (req, res) => { + try { + const { type = 'full', includeFiles = true } = req.body; + + // Mock backup process - replace with actual backup implementation + const backupId = `backup_${Date.now()}`; + const backupSize = Math.floor(Math.random() * 1000000000); // Random size in bytes + + // In a real implementation, you would: + // 1. Create database backup + // 2. Backup file storage if includeFiles is true + // 3. Compress and store the backup + // 4. Return download link or backup information + + res.json({ + message: 'Backup initiated successfully', + backup: { + id: backupId, + type, + size: backupSize, + includeFiles, + status: 'in_progress', + estimatedCompletion: new Date(Date.now() + 300000).toISOString(), // 5 minutes from now + downloadUrl: `/api/admin/backups/${backupId}/download` + } + }); + } catch (error) { + console.error('Backup error:', error); + res.status(500).json({ + error: 'Internal server error', + message: 'Error initiating backup' + }); + } +}); /** - * @openapi - * /api/admin/audit-logs: - * get: - * tags: [Admin] - * summary: Get audit logs (admin) - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Audit logs retrieved + * Get list of backups (Admin only) + * GET /api/admin/backups */ -router.get("/audit-logs", adminController.getAuditLogs); +router.get('/backups', requirePermission(PERMISSIONS.SYSTEM_MANAGE), (req, res) => { + try { + // Mock backup list - replace with actual backup storage retrieval + const backups = [ + { + id: 'backup_1704067200000', + type: 'full', + size: 1048576000, + status: 'completed', + createdAt: '2024-01-01T00:00:00.000Z', + downloadUrl: '/api/admin/backups/backup_1704067200000/download' + }, + { + id: 'backup_1703980800000', + type: 'incremental', + size: 524288000, + status: 'completed', + createdAt: '2023-12-31T00:00:00.000Z', + downloadUrl: '/api/admin/backups/backup_1703980800000/download' + } + ]; + + res.json({ + message: 'Backups retrieved successfully', + backups, + total: backups.length + }); + } catch (error) { + console.error('Backups retrieval error:', error); + res.status(500).json({ + error: 'Internal server error', + message: 'Error retrieving backups' + }); + } +}); /** - * @openapi - * /api/admin/maintenance: - * post: - * tags: [Admin] - * summary: Trigger maintenance tasks (admin) - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Maintenance triggered + * Send system announcement (Admin only) + * POST /api/admin/announcements */ -router.post("/maintenance", adminController.triggerMaintenance); +router.post('/announcements', requirePermission(PERMISSIONS.SYSTEM_MANAGE), (req, res) => { + try { + const { title, message, targetRoles = [], priority = 'normal', expiresAt } = req.body; + + if (!title || !message) { + return res.status(400).json({ + error: 'Invalid request', + message: 'Title and message are required' + }); + } + + // Validate priority + const validPriorities = ['low', 'normal', 'high', 'urgent']; + if (!validPriorities.includes(priority)) { + return res.status(400).json({ + error: 'Invalid priority', + message: `Priority must be one of: ${validPriorities.join(', ')}` + }); + } + + // Validate target roles + if (targetRoles.length > 0) { + const invalidRoles = targetRoles.filter(role => !Object.values(UserRole).includes(role)); + if (invalidRoles.length > 0) { + return res.status(400).json({ + error: 'Invalid roles', + message: `Invalid target roles: ${invalidRoles.join(', ')}` + }); + } + } + + // Mock announcement creation + const announcement = { + id: `announcement_${Date.now()}`, + title, + message, + targetRoles, + priority, + expiresAt, + createdBy: req.user.id, + createdAt: new Date().toISOString(), + active: true + }; + + // In a real implementation, you would: + // 1. Store the announcement in the database + // 2. Send notifications to targeted users + // 3. Display the announcement on the platform + + res.status(201).json({ + message: 'Announcement created successfully', + announcement + }); + } catch (error) { + console.error('Announcement creation error:', error); + res.status(500).json({ + error: 'Internal server error', + message: 'Error creating announcement' + }); + } +}); module.exports = router; diff --git a/backend/src/routes/agiTutorRoutes.ts b/backend/src/routes/agiTutorRoutes.ts index 88aa549..17347bd 100644 --- a/backend/src/routes/agiTutorRoutes.ts +++ b/backend/src/routes/agiTutorRoutes.ts @@ -1,10 +1,3 @@ -/** - * @openapi - * tags: - * - name: AGITutor - * description: AGI tutor for personalized learning - */ - import { Router } from 'express'; import { AGITutorController } from '../controllers/agiTutorController'; @@ -12,113 +5,46 @@ const router: Router = Router(); const agiTutorController = new AGITutorController(); /** - * @openapi - * /api/agi-tutor/session: - * post: - * tags: [AGITutor] - * summary: Generate personalized learning session - * responses: - * '200': - * description: Session generated + * AGI Tutor Routes + * Implements universal learning capabilities with artificial general intelligence */ + +// Generate personalized learning session router.post('/session', async (req, res) => { await agiTutorController.generateLearningSession(req, res); }); -/** - * @openapi - * /api/agi-tutor/response: - * post: - * tags: [AGITutor] - * summary: Process student response and provide adaptive feedback - * responses: - * '200': - * description: Response processed - */ +// Process student response and provide adaptive feedback router.post('/response', async (req, res) => { await agiTutorController.processStudentResponse(req, res); }); -/** - * @openapi - * /api/agi-tutor/assessment: - * post: - * tags: [AGITutor] - * summary: Generate comprehensive assessment - * responses: - * '200': - * description: Assessment generated - */ +// Generate comprehensive assessment router.post('/assessment', async (req, res) => { await agiTutorController.generateAssessment(req, res); }); -/** - * @openapi - * /api/agi-tutor/guidance: - * post: - * tags: [AGITutor] - * summary: Get real-time teaching guidance - * responses: - * '200': - * description: Guidance provided - */ +// Get real-time teaching guidance for instructors router.post('/guidance', async (req, res) => { await agiTutorController.getTeachingGuidance(req, res); }); -/** - * @openapi - * /api/agi-tutor/visualization: - * get: - * tags: [AGITutor] - * summary: Get knowledge visualization - * responses: - * '200': - * description: Visualization retrieved - */ +// Get knowledge visualization and connections router.get('/visualization', async (req, res) => { await agiTutorController.getKnowledgeVisualization(req, res); }); -/** - * @openapi - * /api/agi-tutor/progress: - * post: - * tags: [AGITutor] - * summary: Track learning progress and predict outcomes - * responses: - * '200': - * description: Progress tracked - */ +// Track learning progress and predict outcomes router.post('/progress', async (req, res) => { await agiTutorController.trackLearningProgress(req, res); }); -/** - * @openapi - * /api/agi-tutor/recommendations: - * post: - * tags: [AGITutor] - * summary: Get personalized learning recommendations - * responses: - * '200': - * description: Recommendations provided - */ +// Get personalized learning recommendations router.post('/recommendations', async (req, res) => { await agiTutorController.getLearningRecommendations(req, res); }); -/** - * @openapi - * /api/agi-tutor/emotional-support: - * post: - * tags: [AGITutor] - * summary: Provide emotional support and motivation - * responses: - * '200': - * description: Emotional support provided - */ +// Handle emotional support and motivation router.post('/emotional-support', async (req, res) => { await agiTutorController.provideEmotionalSupport(req, res); }); diff --git a/backend/src/routes/analytics.js b/backend/src/routes/analytics.js index 0539f45..ba93d81 100644 --- a/backend/src/routes/analytics.js +++ b/backend/src/routes/analytics.js @@ -1,127 +1,10 @@ -/** - * @openapi - * tags: - * - name: Analytics - * description: Platform analytics and reporting - */ - -const express = require("express"); +const express = require('express'); const router = express.Router(); -const { authenticate, authorize } = require("../middleware/auth"); -const analyticsController = require("../controllers/analyticsController"); - -router.use(authenticate, authorize("admin")); - -/** - * @openapi - * /api/analytics/overview: - * get: - * tags: [Analytics] - * summary: Get analytics overview - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Overview retrieved - */ -router.get("/overview", analyticsController.getOverview); - -/** - * @openapi - * /api/analytics/users: - * get: - * tags: [Analytics] - * summary: Get user analytics - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: User analytics retrieved - */ -router.get("/users", analyticsController.getUserAnalytics); - -/** - * @openapi - * /api/analytics/courses: - * get: - * tags: [Analytics] - * summary: Get course analytics - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Course analytics retrieved - */ -router.get("/courses", analyticsController.getCourseAnalytics); - -/** - * @openapi - * /api/analytics/engagement: - * get: - * tags: [Analytics] - * summary: Get engagement metrics - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Engagement metrics retrieved - */ -router.get("/engagement", analyticsController.getEngagementMetrics); - -/** - * @openapi - * /api/analytics/revenue: - * get: - * tags: [Analytics] - * summary: Get revenue analytics - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Revenue analytics retrieved - */ -router.get("/revenue", analyticsController.getRevenueAnalytics); - -/** - * @openapi - * /api/analytics/performance: - * get: - * tags: [Analytics] - * summary: Get system performance metrics - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Performance metrics retrieved - */ -router.get("/performance", analyticsController.getPerformanceMetrics); - -/** - * @openapi - * /api/analytics/custom: - * post: - * tags: [Analytics] - * summary: Run custom analytics query - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Query results - */ -router.post("/custom", analyticsController.runCustomQuery); +const { getOverviewStats, getDetailedReport, exportData } = require('../controllers/analyticsController'); -/** - * @openapi - * /api/analytics/export: - * post: - * tags: [Analytics] - * summary: Export analytics report - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Report exported - */ -router.post("/export", analyticsController.exportReport); +// Analytics Data Routes +router.get('/overview', getOverviewStats); +router.get('/report', getDetailedReport); +router.get('/export', exportData); module.exports = router; diff --git a/backend/src/routes/assignmentRoutes.ts b/backend/src/routes/assignmentRoutes.ts index bf0b47c..d8ed9d8 100644 --- a/backend/src/routes/assignmentRoutes.ts +++ b/backend/src/routes/assignmentRoutes.ts @@ -1,112 +1,123 @@ /** - * @openapi - * tags: - * - name: Assignments - * description: Assignment management and submission + * Assignment Routes + * Defines all assignment-related API endpoints */ -import { Router } from "express"; -import { assignmentController } from "../controllers/assignmentController"; +import { Router } from 'express'; +import { AssignmentController } from '../controllers/assignmentController'; +import { authMiddleware } from '../middleware/auth'; +import { uploadMiddleware } from '../middleware/upload'; +import { rateLimitMiddleware } from '../middleware/rateLimit'; +import { validateRequest } from '../middleware/validation'; -const router = Router(); +export function createAssignmentRoutes(controller: AssignmentController): Router { + const router = Router(); -/** - * @openapi - * /api/assignments: - * post: - * tags: [Assignments] - * summary: Create assignment - * responses: - * '200': - * description: Assignment created - * get: - * tags: [Assignments] - * summary: List all assignments - * responses: - * '200': - * description: Assignments listed - */ -router.post("/", assignmentController.createAssignment); -router.get("/", assignmentController.getAllAssignments); + // Apply authentication to all routes + router.use(authMiddleware as any); -/** - * @openapi - * /api/assignments/{assignmentId}: - * get: - * tags: [Assignments] - * summary: Get assignment by ID - * parameters: - * - in: path - * name: assignmentId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Assignment retrieved - * put: - * tags: [Assignments] - * summary: Update assignment - * parameters: - * - in: path - * name: assignmentId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Assignment updated - * delete: - * tags: [Assignments] - * summary: Delete assignment - * parameters: - * - in: path - * name: assignmentId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Assignment deleted - */ -router.get("/:assignmentId", assignmentController.getAssignmentById); -router.put("/:assignmentId", assignmentController.updateAssignment); -router.delete("/:assignmentId", assignmentController.deleteAssignment); + // Assignment Management Routes + router.post( + '/courses/:courseId/assignments', + rateLimitMiddleware({ max: 10, windowMs: 15 * 60 * 1000 }) as any, + validateRequest as any, + controller.createAssignment.bind(controller) as any + ); -/** - * @openapi - * /api/assignments/{assignmentId}/submit: - * post: - * tags: [Assignments] - * summary: Submit assignment - * parameters: - * - in: path - * name: assignmentId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Assignment submitted - */ -router.post("/:assignmentId/submit", assignmentController.submitAssignment); + router.get( + '/courses/:courseId/assignments', + rateLimitMiddleware({ max: 100, windowMs: 15 * 60 * 1000 }) as any, + controller.getAssignments.bind(controller) as any + ); -/** - * @openapi - * /api/assignments/{assignmentId}/grade: - * post: - * tags: [Assignments] - * summary: Grade assignment submission - * parameters: - * - in: path - * name: assignmentId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Assignment graded - */ -router.post("/:assignmentId/grade", assignmentController.gradeAssignment); + router.get( + '/assignments/:assignmentId', + rateLimitMiddleware({ max: 200, windowMs: 15 * 60 * 1000 }) as any, + controller.getAssignment.bind(controller) as any + ); + + router.put( + '/assignments/:assignmentId', + rateLimitMiddleware({ max: 20, windowMs: 15 * 60 * 1000 }) as any, + validateRequest as any, + controller.updateAssignment.bind(controller) as any + ); + + router.delete( + '/assignments/:assignmentId', + rateLimitMiddleware({ max: 10, windowMs: 15 * 60 * 1000 }) as any, + controller.deleteAssignment.bind(controller) as any + ); + + // Submission Management Routes + router.post( + '/assignments/:assignmentId/submissions', + rateLimitMiddleware({ max: 20, windowMs: 15 * 60 * 1000 }) as any, + uploadMiddleware.array('files', 10) as any, + validateRequest as any, + controller.createSubmission.bind(controller) as any + ); + + router.get( + '/assignments/:assignmentId/submissions', + rateLimitMiddleware({ max: 100, windowMs: 15 * 60 * 1000 }) as any, + controller.getSubmissions.bind(controller) as any + ); + + router.get( + '/submissions/:submissionId', + rateLimitMiddleware({ max: 200, windowMs: 15 * 60 * 1000 }) as any, + controller.getSubmission.bind(controller) as any + ); + + router.put( + '/submissions/:submissionId', + rateLimitMiddleware({ max: 30, windowMs: 15 * 60 * 1000 }) as any, + uploadMiddleware.array('files', 5) as any, + validateRequest as any, + controller.updateSubmission.bind(controller) as any + ); + + router.post( + '/submissions/:submissionId/submit', + rateLimitMiddleware({ max: 10, windowMs: 15 * 60 * 1000 }) as any, + controller.submitAssignment.bind(controller) as any + ); + + // Grading Management Routes + router.post( + '/submissions/:submissionId/grade', + rateLimitMiddleware({ max: 50, windowMs: 15 * 60 * 1000 }) as any, + validateRequest as any, + controller.gradeSubmission.bind(controller) as any + ); + + router.get( + '/assignments/:assignmentId/grades', + rateLimitMiddleware({ max: 100, windowMs: 15 * 60 * 1000 }) as any, + controller.getGrades.bind(controller) as any + ); + + // Statistics and Analytics Routes + router.get( + '/assignments/:assignmentId/stats', + rateLimitMiddleware({ max: 50, windowMs: 15 * 60 * 1000 }) as any, + controller.getAssignmentStats.bind(controller) as any + ); + + router.get( + '/courses/:courseId/progress', + rateLimitMiddleware({ max: 100, windowMs: 15 * 60 * 1000 }) as any, + controller.getStudentProgress.bind(controller) as any + ); + + // Bulk Operations Routes + router.post( + '/assignments/:assignmentId/bulk-grade', + rateLimitMiddleware({ max: 5, windowMs: 15 * 60 * 1000 }) as any, + validateRequest as any, + controller.bulkGrade.bind(controller) as any + ); -export default router; + return router; +} diff --git a/backend/src/routes/auth.js b/backend/src/routes/auth.js index 677450a..b89a90f 100644 --- a/backend/src/routes/auth.js +++ b/backend/src/routes/auth.js @@ -7,13 +7,6 @@ const { authLimiter } = require('../middleware/rateLimiter'); const securityService = require('../services/securityService'); const router = express.Router(); -/** - * @openapi - * tags: - * - name: Authentication - * description: User authentication and profile management - */ - // Mock user database - replace with actual database implementation const users = new Map(); @@ -36,72 +29,8 @@ function generateToken(user) { } /** - * @openapi - * /api/auth/register: - * post: - * tags: [Authentication] - * summary: Register a new user - * description: Create a new user account with username, email, and password - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - username - * - email - * - password - * properties: - * username: - * type: string - * example: johndoe - * email: - * type: string - * format: email - * example: john@example.com - * password: - * type: string - * format: password - * example: securePass123 - * role: - * type: string - * enum: [student, educator, admin] - * default: student - * responses: - * 201: - * description: User registered successfully - * content: - * application/json: - * schema: - * type: object - * properties: - * message: - * type: string - * user: - * type: object - * properties: - * id: - * type: string - * username: - * type: string - * email: - * type: string - * role: - * type: string - * createdAt: - * type: string - * format: date-time - * token: - * type: string - * 400: - * description: Missing required fields or invalid role - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/Error' - * 409: - * description: User already exists + * Register new user + * POST /api/auth/register */ router.post('/register', authLimiter, async (req, res) => { try { @@ -177,56 +106,8 @@ router.post('/register', authLimiter, async (req, res) => { }); /** - * @openapi - * /api/auth/login: - * post: - * tags: [Authentication] - * summary: Authenticate user - * description: Login with username/email and password to get JWT token - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - username - * - password - * properties: - * username: - * type: string - * example: johndoe - * password: - * type: string - * format: password - * example: securePass123 - * responses: - * 200: - * description: Login successful - * content: - * application/json: - * schema: - * type: object - * properties: - * message: - * type: string - * user: - * type: object - * properties: - * id: - * type: string - * username: - * type: string - * email: - * type: string - * role: - * type: string - * token: - * type: string - * 400: - * description: Missing credentials - * 401: - * description: Invalid credentials + * User login + * POST /api/auth/login */ router.post('/login', authLimiter, async (req, res) => { try { @@ -285,70 +166,8 @@ router.post('/login', authLimiter, async (req, res) => { }); /** - * @openapi - * /api/auth/profile: - * get: - * tags: [Authentication] - * summary: Get current user profile - * security: - * - bearerAuth: [] - * responses: - * 200: - * description: User profile retrieved - * content: - * application/json: - * schema: - * type: object - * properties: - * user: - * type: object - * properties: - * id: - * type: string - * username: - * type: string - * email: - * type: string - * role: - * type: string - * createdAt: - * type: string - * format: date-time - * updatedAt: - * type: string - * format: date-time - * 404: - * description: User not found - * put: - * tags: [Authentication] - * summary: Update user profile - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * username: - * type: string - * email: - * type: string - * format: email - * currentPassword: - * type: string - * format: password - * newPassword: - * type: string - * format: password - * responses: - * 200: - * description: Profile updated successfully - * 400: - * description: Validation error - * 404: - * description: User not found + * Get current user profile + * GET /api/auth/profile */ router.get('/profile', authenticateToken, (req, res) => { const user = users.get(req.user.id); @@ -463,39 +282,8 @@ router.put('/profile', authenticateToken, async (req, res) => { }); /** - * @openapi - * /api/auth/assign-role/{userId}: - * put: - * tags: [Authentication] - * summary: Assign role to user (Admin only) - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * description: User ID - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - role - * properties: - * role: - * type: string - * enum: [student, educator, admin] - * responses: - * 200: - * description: Role assigned successfully - * 400: - * description: Invalid role - * 404: - * description: User not found + * Assign role to user (Admin only) + * PUT /api/auth/assign-role/:userId */ router.put('/assign-role/:userId', authenticateToken, @@ -549,43 +337,8 @@ router.put('/assign-role/:userId', ); /** - * @openapi - * /api/auth/users: - * get: - * tags: [Authentication] - * summary: Get all users (Admin only) - * security: - * - bearerAuth: [] - * parameters: - * - in: query - * name: page - * schema: - * type: integer - * default: 1 - * - in: query - * name: limit - * schema: - * type: integer - * default: 10 - * - in: query - * name: role - * schema: - * type: string - * enum: [student, educator, admin] - * responses: - * 200: - * description: Users retrieved - * content: - * application/json: - * schema: - * type: object - * properties: - * users: - * type: array - * items: - * type: object - * pagination: - * $ref: '#/components/schemas/Pagination' + * Get all users (Admin only) + * GET /api/auth/users */ router.get('/users', authenticateToken, @@ -636,26 +389,8 @@ router.get('/users', ); /** - * @openapi - * /api/auth/users/{userId}: - * delete: - * tags: [Authentication] - * summary: Delete user (Admin only) - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * 200: - * description: User deleted successfully - * 400: - * description: Cannot delete self - * 404: - * description: User not found + * Delete user (Admin only) + * DELETE /api/auth/users/:userId */ router.delete('/users/:userId', authenticateToken, diff --git a/backend/src/routes/autonomousAgents.js b/backend/src/routes/autonomousAgents.js index ba5dc28..82a0b86 100644 --- a/backend/src/routes/autonomousAgents.js +++ b/backend/src/routes/autonomousAgents.js @@ -1,127 +1,219 @@ +const express = require('express'); +const router = express.Router(); +const AutonomousAgentController = require('../services/autonomousAgents/AutonomousAgentController'); +const logger = require('../../utils/logger'); + +// Initialize controller +let agentController; + +try { + agentController = new AutonomousAgentController({ + enableCustomerService: true, + enablePerformanceOptimization: true, + enableSecurityMonitoring: true, + enableSelfHealing: true, + humanOversightEnabled: true + }); + logger.info('Autonomous agent controller initialized'); +} catch (error) { + logger.error('Failed to initialize autonomous agent controller:', error); +} + /** - * @openapi - * tags: - * - name: Autonomous Agents - * description: Multi-agent system for task automation + * @route GET /api/autonomous-agents/status + * @desc Get overall system status and metrics */ +router.get('/status', async (req, res) => { + try { + const report = agentController.getSystemReport(); + + res.json({ + success: true, + data: report + }); + } catch (error) { + logger.error('Error getting system status:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); -const express = require("express"); -const router = express.Router(); -const { authenticate } = require("../middleware/auth"); -const autonomousAgentsController = require("../controllers/autonomousAgentsController"); +/** + * @route POST /api/autonomous-agents/support/ticket + * @desc Submit support ticket for autonomous handling + */ +router.post('/support/ticket', async (req, res) => { + try { + const { userId, title, description, priority, category } = req.body; + + if (!userId || !title || !description) { + return res.status(400).json({ + success: false, + error: 'Missing required fields: userId, title, description' + }); + } -router.use(authenticate); + const ticket = { + id: `ticket_${Date.now()}`, + userId, + title, + description, + priority: priority || 'normal', + category: category || 'general', + createdAt: new Date(), + status: 'open' + }; + + const result = await agentController.handleSupportTicket(ticket); + + res.status(result.resolved ? 200 : 202).json({ + success: true, + data: { + ticketId: ticket.id, + resolved: result.resolved, + action: result.action, + requiresHumanAgent: result.requiresHumanAgent, + resolutionTime: result.resolutionTime + } + }); + } catch (error) { + logger.error('Error handling support ticket:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); /** - * @openapi - * /api/autonomous-agents/execute: - * post: - * tags: [Autonomous Agents] - * summary: Execute autonomous agent task - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Task executed + * @route POST /api/autonomous-agents/performance/optimize + * @desc Trigger performance optimization */ -router.post("/execute", autonomousAgentsController.execute); +router.post('/performance/optimize', async (req, res) => { + try { + const result = await agentController.optimizePerformance(); + + res.json({ + success: true, + data: result + }); + } catch (error) { + logger.error('Error optimizing performance:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); /** - * @openapi - * /api/autonomous-agents/status/{taskId}: - * get: - * tags: [Autonomous Agents] - * summary: Get autonomous task status - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: taskId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Task status retrieved + * @route GET /api/autonomous-agents/security/status + * @desc Get current security posture */ -router.get("/status/:taskId", autonomousAgentsController.getStatus); +router.get('/security/status', async (req, res) => { + try { + const status = await agentController.checkSecurityStatus(); + + res.json({ + success: true, + data: status + }); + } catch (error) { + logger.error('Error getting security status:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); /** - * @openapi - * /api/autonomous-agents/agents: - * get: - * tags: [Autonomous Agents] - * summary: List available agents - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Agents listed + * @route PUT /api/autonomous-agents/oversight + * @desc Enable/disable human oversight */ -router.get("/agents", autonomousAgentsController.getAgents); +router.put('/oversight', async (req, res) => { + try { + const { enabled } = req.body; + + if (typeof enabled !== 'boolean') { + return res.status(400).json({ + success: false, + error: 'enabled must be a boolean' + }); + } + + agentController.setHumanOversight(enabled); + + res.json({ + success: true, + message: `Human oversight ${enabled ? 'enabled' : 'disabled'}` + }); + } catch (error) { + logger.error('Error toggling human oversight:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); /** - * @openapi - * /api/autonomous-agents/agents/register: - * post: - * tags: [Autonomous Agents] - * summary: Register new agent - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Agent registered + * @route GET /api/autonomous-agents/metrics + * @desc Get detailed performance metrics */ -router.post("/agents/register", autonomousAgentsController.registerAgent); +router.get('/metrics', async (req, res) => { + try { + const report = agentController.getSystemReport(); + + res.json({ + success: true, + data: { + systemMetrics: report.systemMetrics, + autonomyRate: report.autonomyRate, + targetAchieved: parseFloat(report.autonomyRate) >= 80, + agents: report.agents + } + }); + } catch (error) { + logger.error('Error getting metrics:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); /** - * @openapi - * /api/autonomous-agents/agents/{agentId}: - * get: - * tags: [Autonomous Agents] - * summary: Get agent details - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: agentId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Agent details retrieved - * put: - * tags: [Autonomous Agents] - * summary: Update agent configuration - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: agentId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Agent updated - * delete: - * tags: [Autonomous Agents] - * summary: Delete agent - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: agentId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Agent deleted + * @route GET /api/autonomous-agents/agents/:type + * @desc Get specific agent status and metrics */ -router.get("/agents/:agentId", autonomousAgentsController.getAgentById); -router.put("/agents/:agentId", autonomousAgentsController.updateAgent); -router.delete("/agents/:agentId", autonomousAgentsController.deleteAgent); +router.get('/agents/:type', async (req, res) => { + try { + const { type } = req.params; + const report = agentController.getSystemReport(); + + const agentReport = report.agents[type]; + + if (!agentReport) { + return res.status(404).json({ + success: false, + error: `Agent type ${type} not found` + }); + } + + res.json({ + success: true, + data: agentReport + }); + } catch (error) { + logger.error('Error getting agent status:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); module.exports = router; diff --git a/backend/src/routes/bookmarks.js b/backend/src/routes/bookmarks.js index 5efec31..3a005c8 100644 --- a/backend/src/routes/bookmarks.js +++ b/backend/src/routes/bookmarks.js @@ -1,69 +1,187 @@ -/** - * @openapi - * tags: - * - name: Bookmarks - * description: Content bookmarking and favorites management - */ - -const express = require("express"); +const express = require('express'); const router = express.Router(); -const { authenticate } = require("../middleware/auth"); -const bookmarkController = require("../controllers/bookmarkController"); +const Bookmark = require('../models/Bookmark'); +const Note = require('../models/Note'); +const Content = require('../models/Content'); +const auth = require('../middleware/auth'); + +// Get all bookmarks for a user +router.get('/', auth, async (req, res) => { + try { + const { contentId } = req.query; + let query = { user: req.user.id }; + + if (contentId) { + query.content = contentId; + } + + const bookmarks = await Bookmark.find(query) + .populate('content', 'title type duration') + .sort({ createdAt: -1 }); + + res.json(bookmarks); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Create or update bookmark +router.post('/', auth, async (req, res) => { + try { + const { contentId, timestamp, note } = req.body; + + // Validate content exists + const content = await Content.findById(contentId); + if (!content) { + return res.status(404).json({ error: 'Content not found' }); + } + + // Validate timestamp + if (timestamp < 0 || (content.duration && timestamp > content.duration)) { + return res.status(400).json({ error: 'Invalid timestamp' }); + } + + const bookmark = await Bookmark.findOneAndUpdate( + { user: req.user.id, content: contentId }, + { + timestamp, + note: note || '', + createdAt: new Date() + }, + { + new: true, + upsert: true + } + ).populate('content', 'title type duration'); + + res.json(bookmark); + } catch (error) { + if (error.code === 11000) { + return res.status(400).json({ error: 'Bookmark already exists' }); + } + res.status(500).json({ error: error.message }); + } +}); + +// Delete bookmark +router.delete('/:bookmarkId', auth, async (req, res) => { + try { + const bookmark = await Bookmark.findOneAndDelete({ + _id: req.params.bookmarkId, + user: req.user.id + }); + + if (!bookmark) { + return res.status(404).json({ error: 'Bookmark not found' }); + } + + res.json({ message: 'Bookmark deleted successfully' }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); -router.use(authenticate); +// Get all notes for a user +router.get('/notes', auth, async (req, res) => { + try { + const { contentId, tags } = req.query; + let query = { user: req.user.id }; + + if (contentId) { + query.content = contentId; + } + + if (tags) { + const tagArray = tags.split(',').map(tag => tag.trim()); + query.tags = { $in: tagArray }; + } + + const notes = await Note.find(query) + .populate('content', 'title type duration') + .sort({ createdAt: -1 }); + + res.json(notes); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); -/** - * @openapi - * /api/bookmarks/{userId}: - * get: - * tags: [Bookmarks] - * summary: Get bookmarks for user - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Bookmarks retrieved - */ -router.get("/:userId", bookmarkController.getBookmarks); +// Create note +router.post('/notes', auth, async (req, res) => { + try { + const { contentId, timestamp, text, isPrivate, tags } = req.body; + + // Validate content exists + const content = await Content.findById(contentId); + if (!content) { + return res.status(404).json({ error: 'Content not found' }); + } + + // Validate timestamp + if (timestamp < 0 || (content.duration && timestamp > content.duration)) { + return res.status(400).json({ error: 'Invalid timestamp' }); + } + + const note = new Note({ + user: req.user.id, + content: contentId, + timestamp, + text, + isPrivate: isPrivate !== false, + tags: tags || [] + }); + + await note.save(); + await note.populate('content', 'title type duration'); + + res.status(201).json(note); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); -/** - * @openapi - * /api/bookmarks/add: - * post: - * tags: [Bookmarks] - * summary: Add bookmark - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Bookmark added - */ -router.post("/add", bookmarkController.addBookmark); +// Update note +router.put('/notes/:noteId', auth, async (req, res) => { + try { + const { text, isPrivate, tags } = req.body; + + const note = await Note.findOneAndUpdate( + { _id: req.params.noteId, user: req.user.id }, + { + text, + isPrivate, + tags: tags || [], + updatedAt: new Date() + }, + { new: true } + ).populate('content', 'title type duration'); + + if (!note) { + return res.status(404).json({ error: 'Note not found' }); + } + + res.json(note); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); -/** - * @openapi - * /api/bookmarks/{bookmarkId}: - * delete: - * tags: [Bookmarks] - * summary: Remove bookmark - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: bookmarkId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Bookmark removed - */ -router.delete("/:bookmarkId", bookmarkController.removeBookmark); +// Delete note +router.delete('/notes/:noteId', auth, async (req, res) => { + try { + const note = await Note.findOneAndDelete({ + _id: req.params.noteId, + user: req.user.id + }); + + if (!note) { + return res.status(404).json({ error: 'Note not found' }); + } + + res.json({ message: 'Note deleted successfully' }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); module.exports = router; diff --git a/backend/src/routes/cdnOptimizationRoutes.ts b/backend/src/routes/cdnOptimizationRoutes.ts index 7bb1fad..8a52870 100644 --- a/backend/src/routes/cdnOptimizationRoutes.ts +++ b/backend/src/routes/cdnOptimizationRoutes.ts @@ -1,73 +1,284 @@ /** - * @openapi - * tags: - * - name: CDN Optimization - * description: Content delivery network optimization + * CDN Optimization Routes + * API endpoints for content delivery optimization */ -import { Router } from "express"; -import * as cdnController from "../controllers/cdnOptimizationController"; +import { Router, RequestHandler } from "express"; +import { CDNOptimizationController } from "../controllers/cdnOptimizationController"; +import { rateLimit } from "express-rate-limit"; +import { body, param, query } from "express-validator"; +import { handleValidationErrors } from "../middleware/validation"; const router: Router = Router(); +const controller = new CDNOptimizationController(); + +// Rate limiting for optimization endpoints +const optimizationRateLimit = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 100, // Limit each IP to 100 requests per windowMs + message: { + success: false, + message: "Too many optimization requests, please try again later.", + }, + standardHeaders: true, + legacyHeaders: false, +}); + +// Rate limiting for analytics endpoints +const analyticsRateLimit = rateLimit({ + windowMs: 5 * 60 * 1000, // 5 minutes + max: 50, // Limit each IP to 50 requests per windowMs + message: { + success: false, + message: "Too many analytics requests, please try again later.", + }, + standardHeaders: true, + legacyHeaders: false, +}); + +/** + * POST /api/cdn/optimize + * Optimize content delivery for a specific piece of content + */ +router.post( + "/optimize", + optimizationRateLimit, + [ + body("contentId") + .notEmpty() + .withMessage("Content ID is required") + .isString() + .withMessage("Content ID must be a string"), + + body("contentType") + .notEmpty() + .withMessage("Content type is required") + .isString() + .withMessage("Content type must be a string") + .isIn(["video", "image", "audio", "document", "other"]) + .withMessage( + "Content type must be one of: video, image, audio, document, other", + ), + + body("originalUrl") + .notEmpty() + .withMessage("Original URL is required") + .isURL() + .withMessage("Original URL must be a valid URL"), + + body("clientInfo.ip") + .notEmpty() + .withMessage("Client IP is required") + .isIP() + .withMessage("Client IP must be a valid IP address"), + + body("clientInfo.userAgent") + .notEmpty() + .withMessage("Client user agent is required") + .isString() + .withMessage("Client user agent must be a string"), + + body("clientInfo.connectionType") + .notEmpty() + .withMessage("Client connection type is required") + .isIn(["wifi", "cellular", "ethernet", "unknown"]) + .withMessage( + "Connection type must be one of: wifi, cellular, ethernet, unknown", + ), + + body("requestedQuality") + .optional() + .isIn(["auto", "360p", "720p", "1080p", "4k"]) + .withMessage( + "Requested quality must be one of: auto, 360p, 720p, 1080p, 4k", + ), + + body("optimizationLevel") + .optional() + .isIn(["basic", "standard", "aggressive"]) + .withMessage( + "Optimization level must be one of: basic, standard, aggressive", + ), + + body("maxLatency") + .optional() + .isInt({ min: 0 }) + .withMessage("Max latency must be a positive integer"), + + body("maxCostPerGB") + .optional() + .isFloat({ min: 0 }) + .withMessage("Max cost per GB must be a positive number"), + + body("preferLowLatency") + .optional() + .isBoolean() + .withMessage("Prefer low latency must be a boolean"), + ], + handleValidationErrors, + controller.optimizeContent.bind(controller) as RequestHandler, +); + +/** + * GET /api/cdn/statistics + * Get optimization statistics + */ +router.get( + "/statistics", + analyticsRateLimit, + controller.getStatistics.bind(controller), +); + +/** + * GET /api/cdn/history + * Get optimization history + */ +router.get( + "/history", + analyticsRateLimit, + [ + query("limit") + .optional() + .isInt({ min: 1, max: 1000 }) + .withMessage("Limit must be an integer between 1 and 1000"), + ], + handleValidationErrors, + controller.getHistory.bind(controller) as RequestHandler, +); /** - * @openapi - * /api/cdn-optimization/optimize: - * post: - * tags: [CDN Optimization] - * summary: Optimize content delivery - * responses: - * '200': - * description: Content optimized + * GET /api/cdn/status + * Get service status */ -router.post("/optimize", cdnController.optimizeContent); +router.get("/status", controller.getStatus.bind(controller)); /** - * @openapi - * /api/cdn-optimization/cache/purge: - * post: - * tags: [CDN Optimization] - * summary: Purge CDN cache - * responses: - * '200': - * description: Cache purged + * PUT /api/cdn/configuration + * Update service configuration */ -router.post("/cache/purge", cdnController.purgeCache); +router.put( + "/configuration", + [ + body().isObject().withMessage("Configuration must be an object"), + + body("enableMultiCDN") + .optional() + .isBoolean() + .withMessage("enableMultiCDN must be a boolean"), + + body("enableAdaptiveBitrate") + .optional() + .isBoolean() + .withMessage("enableAdaptiveBitrate must be a boolean"), + + body("enableIntelligentCompression") + .optional() + .isBoolean() + .withMessage("enableIntelligentCompression must be a boolean"), + + body("enableNetworkDetection") + .optional() + .isBoolean() + .withMessage("enableNetworkDetection must be a boolean"), + + body("enableEdgeComputing") + .optional() + .isBoolean() + .withMessage("enableEdgeComputing must be a boolean"), + + body("enableAnalytics") + .optional() + .isBoolean() + .withMessage("enableAnalytics must be a boolean"), + + body("defaultQuality") + .optional() + .isIn(["auto", "360p", "720p", "1080p", "4k"]) + .withMessage( + "Default quality must be one of: auto, 360p, 720p, 1080p, 4k", + ), + + body("compressionProfile") + .optional() + .isString() + .withMessage("Compression profile must be a string"), + + body("maxConcurrentOptimizations") + .optional() + .isInt({ min: 1, max: 1000 }) + .withMessage( + "Max concurrent optimizations must be an integer between 1 and 1000", + ), + + body("optimizationTimeout") + .optional() + .isInt({ min: 5, max: 300 }) + .withMessage( + "Optimization timeout must be an integer between 5 and 300 seconds", + ), + ], + handleValidationErrors, + controller.updateConfiguration.bind(controller) as RequestHandler, +); + +/** + * DELETE /api/cdn/optimizations/:requestId + * Cancel an active optimization + */ +router.delete( + "/optimizations/:requestId", + [ + param("requestId") + .notEmpty() + .withMessage("Request ID is required") + .isString() + .withMessage("Request ID must be a string"), + ], + handleValidationErrors, + controller.cancelOptimization.bind(controller) as RequestHandler, +); + +/** + * GET /api/cdn/optimizations/active + * Get active optimizations + */ +router.get( + "/optimizations/active", + controller.getActiveOptimizations.bind(controller), +); + +/** + * GET /api/cdn/analytics + * Get analytics report + */ +router.get( + "/analytics", + analyticsRateLimit, + [ + query("start") + .optional() + .isISO8601() + .withMessage("Start date must be a valid ISO 8601 date"), + + query("end") + .optional() + .isISO8601() + .withMessage("End date must be a valid ISO 8601 date"), + ], + handleValidationErrors, + controller.getAnalyticsReport.bind(controller) as RequestHandler, +); /** - * @openapi - * /api/cdn-optimization/metrics/{region}: - * get: - * tags: [CDN Optimization] - * summary: Get CDN metrics for region - * parameters: - * - in: path - * name: region - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Metrics retrieved + * DELETE /api/cdn/history + * Clear optimization history */ -router.get("/metrics/:region", cdnController.getRegionalMetrics); +router.delete("/history", controller.clearHistory.bind(controller)); /** - * @openapi - * /api/cdn-optimization/edge/{edgeId}: - * get: - * tags: [CDN Optimization] - * summary: Get edge node status - * parameters: - * - in: path - * name: edgeId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Edge status retrieved + * GET /api/cdn/health + * Health check endpoint */ -router.get("/edge/:edgeId", cdnController.getEdgeNodeStatus); +router.get("/health", controller.healthCheck.bind(controller)); export default router; diff --git a/backend/src/routes/collaborationRoutes.ts b/backend/src/routes/collaborationRoutes.ts index 88cb664..766f1c7 100644 --- a/backend/src/routes/collaborationRoutes.ts +++ b/backend/src/routes/collaborationRoutes.ts @@ -1,68 +1,12 @@ -/** - * @openapi - * tags: - * - name: Collaboration - * description: Real-time collaboration room management - */ - import express from 'express'; import { CollaborationRoomController } from '../controllers/collaborationRoomController'; const router: import('express').Router = express.Router(); -/** - * @openapi - * /api/collaboration/rooms: - * post: - * tags: [Collaboration] - * summary: Create collaboration room - * responses: - * '200': - * description: Room created - * get: - * tags: [Collaboration] - * summary: List collaboration rooms - * responses: - * '200': - * description: Rooms listed - */ +// Room management routes router.post('/rooms', CollaborationRoomController.createRoom); router.get('/rooms', CollaborationRoomController.listRooms); - -/** - * @openapi - * /api/collaboration/rooms/{roomId}: - * get: - * tags: [Collaboration] - * summary: Get room by ID - * parameters: - * - in: path - * name: roomId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Room details - */ router.get('/rooms/:roomId', CollaborationRoomController.getRoomById); - -/** - * @openapi - * /api/collaboration/rooms/{roomId}/end: - * post: - * tags: [Collaboration] - * summary: End collaboration room - * parameters: - * - in: path - * name: roomId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Room ended - */ router.post('/rooms/:roomId/end', CollaborationRoomController.endRoom); export default router; diff --git a/backend/src/routes/content.js b/backend/src/routes/content.js index b6a5141..76d954e 100644 --- a/backend/src/routes/content.js +++ b/backend/src/routes/content.js @@ -1,10 +1,3 @@ -/** - * @openapi - * tags: - * - name: Content - * description: IPFS content management - */ - const express = require('express'); const multer = require('multer'); const router = express.Router(); @@ -38,34 +31,8 @@ const upload = multer({ }); /** - * @openapi - * /api/content/upload: - * post: - * tags: [Content] - * summary: Upload a single file to IPFS - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * multipart/form-data: - * schema: - * type: object - * properties: - * file: - * type: string - * format: binary - * metadata: - * type: string - * includeMetadata: - * type: string - * wrapWithDirectory: - * type: string - * responses: - * 201: - * description: File uploaded successfully - * 400: - * description: Upload failed + * Upload a single file to IPFS + * POST /api/content/upload */ router.post('/upload', requirePermission(PERMISSIONS.CONTENT_CREATE), diff --git a/backend/src/routes/courses.js b/backend/src/routes/courses.js index 9411b37..22a672a 100644 --- a/backend/src/routes/courses.js +++ b/backend/src/routes/courses.js @@ -1,10 +1,3 @@ -/** - * @openapi - * tags: - * - name: Courses - * description: Course content version management - */ - /** * Courses Route * Handles course content and version management endpoints @@ -35,41 +28,22 @@ const router = express.Router(); // const VersionControlService = require('../utils/versionControl'); /** - * @openapi - * /api/courses/{contentId}/versions: - * post: - * tags: [Courses] - * summary: Create a new version for course content - * parameters: - * - in: path - * name: contentId - * required: true - * schema: - * type: string - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * title: - * type: string - * description: - * type: string - * content: - * type: object - * changes: - * type: array - * items: - * type: string - * createdBy: - * type: string - * responses: - * 201: - * description: Version created successfully - * 500: - * description: Server error + * POST /api/courses/:contentId/versions + * Create a new version for course content + * + * @param {string} contentId - Content ID + * @body {ContentVersionCreateRequest} - Version creation data + * @returns {ContentVersion} - Created version + * + * @example + * POST /api/courses/content_123/versions + * { + * "title": "Updated Lesson Content", + * "description": "Updated description", + * "content": { "sections": [...] }, + * "changes": ["Updated introduction", "Added new examples"], + * "createdBy": "user_456" + * } */ router.post('/:contentId/versions', // validateContentIdParam, @@ -117,28 +91,15 @@ router.post('/:contentId/versions', ); /** - * @openapi - * /api/courses/{contentId}/versions: - * get: - * tags: [Courses] - * summary: Get version history for course content - * parameters: - * - in: path - * name: contentId - * required: true - * schema: - * type: string - * - in: query - * name: page - * schema: - * type: integer - * - in: query - * name: limit - * schema: - * type: integer - * responses: - * 200: - * description: Version history retrieved + * GET /api/courses/:contentId/versions + * Get version history for course content + * + * @param {string} contentId - Content ID + * @query {VersionFilter} - Filter options + * @returns {VersionHistoryResult} - Paginated version history + * + * @example + * GET /api/courses/content_123/versions?page=1&limit=10&sortBy=version&sortOrder=desc */ router.get('/:contentId/versions', // validateContentIdParam, @@ -205,20 +166,14 @@ router.get('/:contentId/versions', ); /** - * @openapi - * /api/courses/{contentId}/versions/current: - * get: - * tags: [Courses] - * summary: Get current version of course content - * parameters: - * - in: path - * name: contentId - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Current version retrieved + * GET /api/courses/:contentId/versions/current + * Get current version of course content + * + * @param {string} contentId - Content ID + * @returns {ContentVersion} - Current version + * + * @example + * GET /api/courses/content_123/versions/current */ router.get('/:contentId/versions/current', // validateContentIdParam, @@ -261,25 +216,15 @@ router.get('/:contentId/versions/current', ); /** - * @openapi - * /api/courses/{contentId}/versions/{versionNumber}: - * get: - * tags: [Courses] - * summary: Get specific version by version number - * parameters: - * - in: path - * name: contentId - * required: true - * schema: - * type: string - * - in: path - * name: versionNumber - * required: true - * schema: - * type: integer - * responses: - * 200: - * description: Version retrieved + * GET /api/courses/:contentId/versions/:versionNumber + * Get specific version by version number + * + * @param {string} contentId - Content ID + * @param {number} versionNumber - Version number + * @returns {ContentVersion} - Specific version + * + * @example + * GET /api/courses/content_123/versions/1 */ router.get('/:contentId/versions/:versionNumber', // validateContentIdParam, @@ -323,25 +268,15 @@ router.get('/:contentId/versions/:versionNumber', ); /** - * @openapi - * /api/courses/versions/compare/{version1Id}/{version2Id}: - * post: - * tags: [Courses] - * summary: Compare two versions - * parameters: - * - in: path - * name: version1Id - * required: true - * schema: - * type: string - * - in: path - * name: version2Id - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Versions compared + * POST /api/courses/versions/compare/:version1Id/:version2Id + * Compare two versions + * + * @param {string} version1Id - First version ID + * @param {string} version2Id - Second version ID + * @returns {VersionComparison} - Comparison result + * + * @example + * POST /api/courses/versions/compare/ver_1/ver_2 */ router.post('/versions/compare/:version1Id/:version2Id', // validateVersionComparison, @@ -407,33 +342,20 @@ router.post('/versions/compare/:version1Id/:version2Id', ); /** - * @openapi - * /api/courses/{contentId}/versions/restore: - * post: - * tags: [Courses] - * summary: Restore content to a specific version - * parameters: - * - in: path - * name: contentId - * required: true - * schema: - * type: string - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * versionId: - * type: string - * restoreReason: - * type: string - * restoredBy: - * type: string - * responses: - * 200: - * description: Content restored + * POST /api/courses/:contentId/versions/restore + * Restore content to a specific version + * + * @param {string} contentId - Content ID + * @body {VersionRestoreRequest} - Restore request data + * @returns {Content} - Updated content with restored version + * + * @example + * POST /api/courses/content_123/versions/restore + * { + * "versionId": "ver_1", + * "restoreReason": "Reverting to previous stable version", + * "restoredBy": "user_456" + * } */ router.post('/:contentId/versions/restore', // validateContentIdParam, @@ -482,31 +404,19 @@ router.post('/:contentId/versions/restore', ); /** - * @openapi - * /api/courses/{contentId}/versions/settings: - * put: - * tags: [Courses] - * summary: Update version control settings - * parameters: - * - in: path - * name: contentId - * required: true - * schema: - * type: string - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * autoVersioning: - * type: boolean - * maxVersions: - * type: integer - * responses: - * 200: - * description: Settings updated + * PUT /api/courses/:contentId/versions/settings + * Update version control settings for content + * + * @param {string} contentId - Content ID + * @body {object} - Version control settings + * @returns {object} - Updated settings + * + * @example + * PUT /api/courses/content_123/versions/settings + * { + * "autoVersioning": true, + * "maxVersions": 20 + * } */ router.put('/:contentId/versions/settings', // validateContentIdParam, @@ -547,25 +457,15 @@ router.put('/:contentId/versions/settings', ); /** - * @openapi - * /api/courses/{contentId}/versions/export: - * get: - * tags: [Courses] - * summary: Export version history - * parameters: - * - in: path - * name: contentId - * required: true - * schema: - * type: string - * - in: query - * name: format - * schema: - * type: string - * enum: [json, csv] - * responses: - * 200: - * description: Version history exported + * GET /api/courses/:contentId/versions/export + * Export version history + * + * @param {string} contentId - Content ID + * @query {string} format - Export format (json or csv) + * @returns {file} - Exported version history + * + * @example + * GET /api/courses/content_123/versions/export?format=json */ router.get('/:contentId/versions/export', // validateContentIdParam, @@ -620,20 +520,14 @@ router.get('/:contentId/versions/export', ); /** - * @openapi - * /api/courses/{contentId}/versions/statistics: - * get: - * tags: [Courses] - * summary: Get version statistics for content - * parameters: - * - in: path - * name: contentId - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Version statistics retrieved + * GET /api/courses/:contentId/versions/statistics + * Get version statistics for content + * + * @param {string} contentId - Content ID + * @returns {object} - Version statistics + * + * @example + * GET /api/courses/content_123/versions/statistics */ router.get('/:contentId/versions/statistics', // validateContentIdParam, diff --git a/backend/src/routes/crossProtocolBridge.ts b/backend/src/routes/crossProtocolBridge.ts index 0a4bc91..5d993a8 100644 --- a/backend/src/routes/crossProtocolBridge.ts +++ b/backend/src/routes/crossProtocolBridge.ts @@ -1,67 +1,315 @@ +import express, { Request, Response } from 'express'; +import { body, param, validationResult } from 'express-validator'; +import { crossProtocolBridgeService } from '../services/crossProtocolBridgeService'; +import { authenticateToken } from '../middleware/auth'; +import logger from '../utils/logger'; + +const router: import('express').Router = express.Router(); + +const validateRequest = (req: Request, res: Response, next: Function) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ + success: false, + errors: errors.array() + }); + } + next(); +}; + /** - * @openapi - * tags: - * - name: Cross-Protocol Bridge - * description: Cross-chain and cross-protocol interoperability bridge + * @route POST /api/bridge/send + * @desc Send a cross-chain message + * @access Private */ +router.post( + '/send', + authenticateToken, + [ + body('destinationChain').isInt({ min: 0 }).withMessage('Valid destination chain required'), + body('payload').notEmpty().withMessage('Payload is required'), + body('messageType').isIn(['CredentialVerification', 'DataSync', 'TokenTransfer', 'GovernanceVote', 'Custom']), + body('gasLimit').isInt({ min: 1 }).withMessage('Gas limit must be positive'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { destinationChain, payload, messageType, gasLimit } = req.body; + const sender = (req.user as any)?.address; + + if (!sender) { + return res.status(401).json({ + success: false, + message: 'Unauthorized' + }); + } -import { Router } from "express"; -import * as crossProtocolController from "../controllers/crossProtocolController"; + const messageId = await crossProtocolBridgeService.sendMessage( + sender, + destinationChain, + payload, + messageType, + parseInt(gasLimit) + ); -const router: Router = Router(); + res.status(201).json({ + success: true, + data: { messageId }, + message: 'Cross-chain message sent successfully', + }); + } catch (error: any) { + logger.error('Error sending message:', error); + res.status(400).json({ + success: false, + message: error.message || 'Failed to send message', + }); + } + } +); /** - * @openapi - * /api/cross-protocol-bridge/bridge: - * post: - * tags: [Cross-Protocol Bridge] - * summary: Bridge assets across protocols - * responses: - * '200': - * description: Bridge operation initiated + * @route GET /api/bridge/message/:messageId + * @desc Get cross-chain message details + * @access Private */ -router.post("/bridge", crossProtocolController.bridgeAssets); +router.get( + '/message/:messageId', + authenticateToken, + [ + param('messageId').notEmpty().withMessage('Message ID is required'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { messageId } = req.params; + + const message = await crossProtocolBridgeService.getMessage(messageId); + + if (!message) { + return res.status(404).json({ + success: false, + message: 'Message not found', + }); + } + + res.json({ + success: true, + data: message, + }); + } catch (error: any) { + logger.error('Error getting message:', error); + res.status(500).json({ + success: false, + message: 'Failed to get message', + }); + } + } +); /** - * @openapi - * /api/cross-protocol-bridge/status/{txId}: - * get: - * tags: [Cross-Protocol Bridge] - * summary: Get bridge transaction status - * parameters: - * - in: path - * name: txId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Transaction status retrieved + * @route GET /api/bridge/user/:user/messages + * @desc Get all messages by user + * @access Private */ -router.get("/status/:txId", crossProtocolController.getBridgeStatus); +router.get( + '/user/:user/messages', + authenticateToken, + [ + param('user').notEmpty().withMessage('User address is required'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { user } = req.params; + + const messages = await crossProtocolBridgeService.getMessagesBySender(user); + + res.json({ + success: true, + data: messages, + count: messages.length, + }); + } catch (error: any) { + logger.error('Error getting messages:', error); + res.status(500).json({ + success: false, + message: 'Failed to get messages', + }); + } + } +); /** - * @openapi - * /api/cross-protocol-bridge/protocols: - * get: - * tags: [Cross-Protocol Bridge] - * summary: List supported protocols - * responses: - * '200': - * description: Protocols listed + * @route POST /api/bridge/batch + * @desc Batch multiple messages for gas optimization + * @access Private */ -router.get("/protocols", crossProtocolController.getSupportedProtocols); +router.post( + '/batch', + authenticateToken, + [ + body('messageIds').isArray({ min: 1 }).withMessage('Message IDs must be an array'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { messageIds } = req.body; + + const batchId = await crossProtocolBridgeService.batchMessages(messageIds); + + res.status(201).json({ + success: true, + data: { batchId }, + message: `Batched ${messageIds.length} messages successfully`, + }); + } catch (error: any) { + logger.error('Error batching messages:', error); + res.status(400).json({ + success: false, + message: error.message || 'Failed to batch messages', + }); + } + } +); /** - * @openapi - * /api/cross-protocol-bridge/validate-address: - * post: - * tags: [Cross-Protocol Bridge] - * summary: Validate cross-protocol address - * responses: - * '200': - * description: Address validated + * @route POST /api/bridge/proof + * @desc Submit state proof for verification + * @access Private */ -router.post("/validate-address", crossProtocolController.validateAddress); +router.post( + '/proof', + authenticateToken, + [ + body('blockNumber').isInt({ min: 0 }).withMessage('Valid block number required'), + body('stateRoot').notEmpty().withMessage('State root is required'), + body('proofData').notEmpty().withMessage('Proof data is required'), + body('validatorSignatures').isArray({ min: 3 }).withMessage('Minimum 3 validator signatures required'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { blockNumber, stateRoot, proofData, validatorSignatures } = req.body; + + const proofId = await crossProtocolBridgeService.submitStateProof( + parseInt(blockNumber), + stateRoot, + proofData, + validatorSignatures + ); + + res.status(201).json({ + success: true, + data: { proofId }, + message: 'State proof submitted successfully', + }); + } catch (error: any) { + logger.error('Error submitting proof:', error); + res.status(400).json({ + success: false, + message: error.message || 'Failed to submit proof', + }); + } + } +); + +/** + * @route POST /api/bridge/proof/:proofId/verify + * @desc Verify state proof + * @access Private + */ +router.post( + '/proof/:proofId/verify', + authenticateToken, + [ + param('proofId').notEmpty().withMessage('Proof ID is required'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { proofId } = req.params; + + const isValid = await crossProtocolBridgeService.verifyStateProof(proofId); + + res.json({ + success: true, + data: { isValid }, + message: isValid ? 'Proof verified successfully' : 'Proof verification failed', + }); + } catch (error: any) { + logger.error('Error verifying proof:', error); + res.status(400).json({ + success: false, + message: error.message || 'Failed to verify proof', + }); + } + } +); + +/** + * @route GET /api/bridge/gas-cost/:destinationChain/:gasLimit + * @desc Calculate gas cost for cross-chain message + * @access Private + */ +router.get( + '/gas-cost/:destinationChain/:gasLimit', + authenticateToken, + [ + param('destinationChain').isInt({ min: 0 }).withMessage('Valid chain ID required'), + param('gasLimit').isInt({ min: 1 }).withMessage('Gas limit must be positive'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { destinationChain, gasLimit } = req.params; + + const gasCost = crossProtocolBridgeService.calculateGasCost( + parseInt(destinationChain), + parseInt(gasLimit) + ); + + res.json({ + success: true, + data: { + destinationChain: parseInt(destinationChain), + gasLimit: parseInt(gasLimit), + gasCost, + }, + }); + } catch (error: any) { + logger.error('Error calculating gas cost:', error); + res.status(500).json({ + success: false, + message: 'Failed to calculate gas cost', + }); + } + } +); + +/** + * @route GET /api/bridge/stats + * @desc Get bridge statistics + * @access Private + */ +router.get( + '/stats', + authenticateToken, + async (req: Request, res: Response) => { + try { + const stats = await crossProtocolBridgeService.getStats(); + + res.json({ + success: true, + data: stats, + }); + } catch (error: any) { + logger.error('Error getting stats:', error); + res.status(500).json({ + success: false, + message: 'Failed to get statistics', + }); + } + } +); export default router; diff --git a/backend/src/routes/enrollmentRoutes.ts b/backend/src/routes/enrollmentRoutes.ts index 16c5cc6..c32eabd 100644 --- a/backend/src/routes/enrollmentRoutes.ts +++ b/backend/src/routes/enrollmentRoutes.ts @@ -1,107 +1,75 @@ /** - * @openapi - * tags: - * - name: Enrollments - * description: Course enrollment management + * Enrollment Routes + * API endpoints for course enrollment management */ -import express, { Router } from "express"; -import { enrollmentController } from "../controllers/enrollmentController"; +import express, { Router, Request, Response } from "express"; +import { EnrollmentController } from "../controllers/EnrollmentController"; +import { authenticateToken, requireRole } from "../middleware/auth"; +import { validate } from "../middleware/validate"; +import { + createEnrollmentSchema, + updateEnrollmentSchema, + enrollmentIdParamSchema, + courseIdParamSchema, + updateProgressSchema, + bulkEnrollmentSchema, + validatePrerequisitesSchema, + userIdParamSchema, + completeEnrollmentSchema, + getUserEnrollmentsQuerySchema, + courseEnrollmentsQuerySchema, + exportEnrollmentsQuerySchema, + renewEnrollmentSchema, +} from "../middleware/schemas/enrollmentSchemas"; +import { rateLimit } from "express-rate-limit"; +import { UserRole } from "../models/User"; const router: Router = express.Router(); +const controller = new EnrollmentController(); -/** - * @openapi - * /api/enrollments/{userId}: - * get: - * tags: [Enrollments] - * summary: Get enrollment for user - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Enrollment details retrieved - */ -router.get("/:userId", enrollmentController.getEnrollment); +// Rate limiting for enrollment endpoints +const enrollmentLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, + max: 10, + message: "Too many enrollment attempts, please try again later.", + standardHeaders: true, + legacyHeaders: false, +}); -/** - * @openapi - * /api/enrollments: - * post: - * tags: [Enrollments] - * summary: Enroll user in course - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: User enrolled - */ -router.post("/", enrollmentController.enroll); +const paymentLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, + max: 20, + message: "Too many payment attempts, please try again later.", + standardHeaders: true, + legacyHeaders: false, +}); -/** - * @openapi - * /api/enrollments/{enrollmentId}: - * delete: - * tags: [Enrollments] - * summary: Unenroll user from course - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: enrollmentId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: User unenrolled - */ -router.delete("/:enrollmentId", enrollmentController.unenroll); +// Helper to wrap async handlers for Express +const wrap = (fn: (req: Request, res: Response) => Promise) => + (req: Request, res: Response) => fn(req, res); -/** - * @openapi - * /api/enrollments/{enrollmentId}/progress: - * put: - * tags: [Enrollments] - * summary: Update enrollment progress - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: enrollmentId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Progress updated - */ -router.put("/:enrollmentId/progress", enrollmentController.updateProgress); - -/** - * @openapi - * /api/enrollments/course/{courseId}: - * get: - * tags: [Enrollments] - * summary: Get all enrollments for a course - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: courseId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Enrollments retrieved - */ -router.get("/course/:courseId", enrollmentController.getCourseEnrollments); +router.get("/", authenticateToken as any, validate(getUserEnrollmentsQuerySchema) as any, wrap(controller.getUserEnrollments.bind(controller)) as any); +router.get("/:id", authenticateToken as any, validate(enrollmentIdParamSchema) as any, wrap(controller.getEnrollmentById.bind(controller)) as any); +router.post("/", authenticateToken as any, enrollmentLimiter, validate(createEnrollmentSchema) as any, wrap(controller.createEnrollment.bind(controller)) as any); +router.put("/:id", authenticateToken as any, validate(updateEnrollmentSchema) as any, wrap(controller.updateEnrollment.bind(controller)) as any); +router.delete("/:id", authenticateToken as any, validate(enrollmentIdParamSchema) as any, wrap(controller.cancelEnrollment.bind(controller)) as any); +router.post("/:id/complete", authenticateToken as any, validate(completeEnrollmentSchema) as any, wrap(controller.completeEnrollment.bind(controller)) as any); +router.get("/:id/progress", authenticateToken as any, validate(enrollmentIdParamSchema) as any, wrap(controller.getEnrollmentProgress.bind(controller)) as any); +router.put("/:id/progress", authenticateToken as any, validate(updateProgressSchema) as any, wrap(controller.updateEnrollmentProgress.bind(controller)) as any); +router.get("/course/:courseId", authenticateToken as any, requireRole([UserRole.EDUCATOR, UserRole.ADMIN]) as any, validate(courseEnrollmentsQuerySchema) as any, wrap(controller.getCourseEnrollments.bind(controller)) as any); +router.post("/:id/certificate", authenticateToken as any, requireRole([UserRole.EDUCATOR, UserRole.ADMIN]) as any, validate(enrollmentIdParamSchema) as any, wrap(controller.issueCertificate.bind(controller)) as any); +router.get("/waitlist/:courseId", authenticateToken as any, requireRole([UserRole.EDUCATOR, UserRole.ADMIN]) as any, validate(courseIdParamSchema) as any, wrap(controller.getCourseWaitlist.bind(controller)) as any); +router.post("/waitlist/:courseId", authenticateToken as any, enrollmentLimiter, validate(courseIdParamSchema) as any, wrap(controller.addToWaitlist.bind(controller)) as any); +router.delete("/waitlist/:courseId", authenticateToken as any, validate(courseIdParamSchema) as any, wrap(controller.removeFromWaitlist.bind(controller)) as any); +router.get("/analytics/user", authenticateToken as any, validate({}) as any, wrap(controller.getUserEnrollmentAnalytics.bind(controller)) as any); +router.get("/analytics/course/:courseId", authenticateToken as any, requireRole([UserRole.EDUCATOR, UserRole.ADMIN]) as any, validate(courseIdParamSchema) as any, wrap(controller.getCourseEnrollmentAnalytics.bind(controller)) as any); +router.get("/analytics/global", authenticateToken as any, requireRole([UserRole.ADMIN]) as any, validate({}) as any, wrap(controller.getGlobalEnrollmentAnalytics.bind(controller)) as any); +router.post("/bulk", authenticateToken as any, requireRole([UserRole.ADMIN]) as any, validate(bulkEnrollmentSchema) as any, wrap(controller.bulkEnrollmentOperations.bind(controller)) as any); +router.get("/capacity/:courseId", authenticateToken as any, validate(courseIdParamSchema) as any, wrap(controller.getCourseCapacity.bind(controller)) as any); +router.post("/validate-prerequisites", authenticateToken as any, validate(validatePrerequisitesSchema) as any, wrap(controller.validatePrerequisites.bind(controller)) as any); +router.get("/history/:userId", authenticateToken as any, validate(userIdParamSchema) as any, wrap(controller.getUserEnrollmentHistory.bind(controller)) as any); +router.post("/:id/renew", authenticateToken as any, paymentLimiter, validate(renewEnrollmentSchema) as any, wrap(controller.renewEnrollment.bind(controller)) as any); +router.get("/export/:courseId", authenticateToken as any, requireRole([UserRole.EDUCATOR, UserRole.ADMIN]) as any, validate(exportEnrollmentsQuerySchema) as any, wrap(controller.exportCourseEnrollments.bind(controller)) as any); export default router; diff --git a/backend/src/routes/eventLoggerRoutes.ts b/backend/src/routes/eventLoggerRoutes.ts index cd48ebe..23fc045 100644 --- a/backend/src/routes/eventLoggerRoutes.ts +++ b/backend/src/routes/eventLoggerRoutes.ts @@ -1,199 +1,31 @@ -/** - * @openapi - * tags: - * - name: Events - * description: Event logging and retrieval - */ - import { Router } from "express"; import { eventLoggerController } from "../controllers/eventLoggerController"; const router: Router = Router(); -/** - * @openapi - * /api/events/course-completion: - * post: - * tags: [Events] - * summary: Log course completion event - * responses: - * '200': - * description: Event logged - */ +// Event logging endpoints router.post("/course-completion", eventLoggerController.logCourseCompletion); - -/** - * @openapi - * /api/events/credential-issuance: - * post: - * tags: [Events] - * summary: Log credential issuance event - * responses: - * '200': - * description: Event logged - */ -router.post("/credential-issuance", eventLoggerController.logCredentialIssuance); - -/** - * @openapi - * /api/events/user-achievement: - * post: - * tags: [Events] - * summary: Log user achievement event - * responses: - * '200': - * description: Event logged - */ +router.post( + "/credential-issuance", + eventLoggerController.logCredentialIssuance, +); router.post("/user-achievement", eventLoggerController.logUserAchievement); - -/** - * @openapi - * /api/events/profile-update: - * post: - * tags: [Events] - * summary: Log profile update event - * responses: - * '200': - * description: Event logged - */ router.post("/profile-update", eventLoggerController.logProfileUpdate); - -/** - * @openapi - * /api/events/course-enrollment: - * post: - * tags: [Events] - * summary: Log course enrollment event - * responses: - * '200': - * description: Event logged - */ router.post("/course-enrollment", eventLoggerController.logCourseEnrollment); -/** - * @openapi - * /api/events/event/{eventId}: - * get: - * tags: [Events] - * summary: Get event by ID - * parameters: - * - in: path - * name: eventId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Event details retrieved - */ +// Event retrieval endpoints router.get("/event/:eventId", eventLoggerController.getEventById); - -/** - * @openapi - * /api/events/user/{userId}/events: - * get: - * tags: [Events] - * summary: Get events by user - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Events retrieved - */ router.get("/user/:userId/events", eventLoggerController.getUserEvents); - -/** - * @openapi - * /api/events/type/{eventType}: - * get: - * tags: [Events] - * summary: Get events by type - * parameters: - * - in: path - * name: eventType - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Events retrieved - */ router.get("/type/:eventType", eventLoggerController.getEventsByType); - -/** - * @openapi - * /api/events/recent: - * get: - * tags: [Events] - * summary: Get recent events - * responses: - * '200': - * description: Recent events retrieved - */ router.get("/recent", eventLoggerController.getRecentEvents); - -/** - * @openapi - * /api/events/count: - * get: - * tags: [Events] - * summary: Get event count - * responses: - * '200': - * description: Event count retrieved - */ router.get("/count", eventLoggerController.getEventCount); - -/** - * @openapi - * /api/events/search: - * get: - * tags: [Events] - * summary: Search events - * responses: - * '200': - * description: Search results - */ router.get("/search", eventLoggerController.searchEvents); -/** - * @openapi - * /api/events/verify/{eventId}: - * get: - * tags: [Events] - * summary: Verify event - * parameters: - * - in: path - * name: eventId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Event verified - */ +// Verification endpoints router.get("/verify/:eventId", eventLoggerController.verifyEvent); - -/** - * @openapi - * /api/events/audit-report/{userId}: - * get: - * tags: [Events] - * summary: Generate user audit report - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Audit report generated - */ -router.get("/audit-report/:userId", eventLoggerController.generateUserAuditReport); +router.get( + "/audit-report/:userId", + eventLoggerController.generateUserAuditReport, +); export default router; diff --git a/backend/src/routes/federatedLearning.js b/backend/src/routes/federatedLearning.js index 53c7af5..14635e8 100644 --- a/backend/src/routes/federatedLearning.js +++ b/backend/src/routes/federatedLearning.js @@ -1,111 +1,117 @@ -/** - * @openapi - * tags: - * - name: Federated Learning - * description: Federated learning model training and management - */ - -const express = require("express"); +const express = require('express'); const router = express.Router(); -const { authenticate, authorize } = require("../middleware/auth"); -const federatedLearningController = require("../controllers/federatedLearningController"); - -router.use(authenticate, authorize("admin")); - -/** - * @openapi - * /api/federated-learning/train: - * post: - * tags: [Federated Learning] - * summary: Start federated training session - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Training session started - */ -router.post("/train", federatedLearningController.startTraining); - -/** - * @openapi - * /api/federated-learning/aggregate: - * post: - * tags: [Federated Learning] - * summary: Aggregate model updates - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Model aggregated - */ -router.post("/aggregate", federatedLearningController.aggregateUpdates); - -/** - * @openapi - * /api/federated-learning/clients: - * get: - * tags: [Federated Learning] - * summary: List federated clients - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Clients listed - */ -router.get("/clients", federatedLearningController.listClients); - -/** - * @openapi - * /api/federated-learning/clients/register: - * post: - * tags: [Federated Learning] - * summary: Register federated client - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Client registered - */ -router.post("/clients/register", federatedLearningController.registerClient); - -/** - * @openapi - * /api/federated-learning/models/{modelId}: - * get: - * tags: [Federated Learning] - * summary: Get model details - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: modelId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Model details retrieved - */ -router.get("/models/:modelId", federatedLearningController.getModel); - -/** - * @openapi - * /api/federated-learning/metrics/{sessionId}: - * get: - * tags: [Federated Learning] - * summary: Get training metrics for session - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: sessionId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Metrics retrieved - */ -router.get("/metrics/:sessionId", federatedLearningController.getTrainingMetrics); +const FederatedLearningController = require('../controllers/federatedLearningController'); +const auth = require('../middleware/auth'); +const rateLimit = require('express-rate-limit'); + +// Initialize controller +const flController = new FederatedLearningController(); + +// Rate limiting for sensitive operations +const sensitiveOperationLimit = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 10, // limit each IP to 10 requests per windowMs + message: { + error: 'Too many requests, please try again later' + } +}); + +const modelUpdateLimit = rateLimit({ + windowMs: 1 * 60 * 1000, // 1 minute + max: 30, // limit each IP to 30 model updates per minute + message: { + error: 'Too many model updates, please try again later' + } +}); + +// Session Management Routes +router.post('/sessions', auth, async (req, res) => { + await flController.initializeSession(req, res); +}); + +router.get('/sessions/:sessionId/status', auth, async (req, res) => { + await flController.getSessionStatus(req, res); +}); + +// Participant Management Routes +router.post('/participants', auth, async (req, res) => { + await flController.registerParticipant(req, res); +}); + +router.get('/participants', auth, async (req, res) => { + await flController.getParticipants(req, res); +}); + +// Round Management Routes +router.post('/rounds', auth, sensitiveOperationLimit, async (req, res) => { + await flController.startRound(req, res); +}); + +router.post('/participants/:participantId/updates', auth, modelUpdateLimit, async (req, res) => { + await flController.submitModelUpdate(req, res); +}); + +router.get('/rounds/history', auth, async (req, res) => { + await flController.getRoundHistory(req, res); +}); + +// Model Management Routes +router.get('/models/versions', auth, async (req, res) => { + await flController.getModelVersions(req, res); +}); + +router.post('/models/rollback/:versionId', auth, sensitiveOperationLimit, async (req, res) => { + await flController.rollbackModel(req, res); +}); + +router.get('/models/compare', auth, async (req, res) => { + await flController.compareModels(req, res); +}); + +// Analytics Routes +router.get('/analytics', auth, async (req, res) => { + await flController.getAnalytics(req, res); +}); + +router.get('/analytics/export', auth, async (req, res) => { + await flController.exportAnalytics(req, res); +}); + +// Privacy Management Routes +router.get('/privacy/status', auth, async (req, res) => { + await flController.getPrivacyStatus(req, res); +}); + +router.post('/privacy/reset-budget', auth, sensitiveOperationLimit, async (req, res) => { + await flController.resetPrivacyBudget(req, res); +}); + +// Validation Routes +router.post('/validation/validate', auth, async (req, res) => { + await flController.validateModel(req, res); +}); + +router.get('/validation/stats', auth, async (req, res) => { + await flController.getValidationStats(req, res); +}); + +// System Health Routes +router.get('/health', async (req, res) => { + await flController.getSystemHealth(req, res); +}); + +router.post('/shutdown', auth, sensitiveOperationLimit, async (req, res) => { + await flController.shutdown(req, res); +}); + +// Error handling middleware +router.use((error, req, res, next) => { + console.error('Federated Learning Route Error:', error); + + res.status(error.status || 500).json({ + error: error.message || 'Internal server error', + details: process.env.NODE_ENV === 'development' ? error.stack : undefined + }); +}); module.exports = router; diff --git a/backend/src/routes/federatedLearningRoutes.js b/backend/src/routes/federatedLearningRoutes.js index 759ff0e..7b5a281 100644 --- a/backend/src/routes/federatedLearningRoutes.js +++ b/backend/src/routes/federatedLearningRoutes.js @@ -1,79 +1,681 @@ -/** - * @openapi - * tags: - * - name: Federated Learning Routes - * description: Federated learning routing and orchestration - */ - -const express = require("express"); +const express = require('express'); const router = express.Router(); -const { authenticate, authorize } = require("../middleware/auth"); -const federatedLearningRoutesController = require("../controllers/federatedLearningRoutesController"); - -router.use(authenticate, authorize("admin")); - -/** - * @openapi - * /api/federated-learning-routes: - * get: - * tags: [Federated Learning Routes] - * summary: List federated learning routes - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Routes listed - */ -router.get("/", federatedLearningRoutesController.listRoutes); - -/** - * @openapi - * /api/federated-learning-routes/{routeId}: - * get: - * tags: [Federated Learning Routes] - * summary: Get route details - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: routeId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Route details retrieved - * put: - * tags: [Federated Learning Routes] - * summary: Update route configuration - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: routeId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Route updated - * delete: - * tags: [Federated Learning Routes] - * summary: Delete route - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: routeId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Route deleted - */ -router.get("/:routeId", federatedLearningRoutesController.getRoute); -router.put("/:routeId", federatedLearningRoutesController.updateRoute); -router.delete("/:routeId", federatedLearningRoutesController.deleteRoute); +const FederatedLearningCoordinator = require('../services/federatedLearningCoordinator'); +const PrivacyPreservingAggregator = require('../services/privacyPreservingAggregator'); +const SecureMultiPartyComputation = require('../services/secureMultiPartyComputation'); +const DifferentialPrivacyService = require('../services/differentialPrivacyService'); +const ModelValidationService = require('../services/modelValidationService'); +const FederatedLearningAnalytics = require('../services/federatedLearningAnalytics'); + +// Initialize services +const flCoordinator = new FederatedLearningCoordinator({ + minParticipants: 3, + maxParticipants: 100, + aggregationStrategy: 'fedavg', + privacyBudget: 1.0, + differentialPrivacy: true, + secureAggregation: true +}); + +const privacyAggregator = new PrivacyPreservingAggregator({ + encryptionScheme: 'paillier', + differentialPrivacy: true, + epsilon: 1.0, + delta: 1e-5, + secureAggregation: true, + homomorphicEncryption: true +}); + +const mpcService = new SecureMultiPartyComputation({ + thresholdScheme: 'shamir', + threshold: 3, + maxParticipants: 10 +}); + +const dpService = new DifferentialPrivacyService({ + epsilon: 1.0, + delta: 1e-5, + sensitivity: 1.0, + mechanism: 'gaussian', + privacyBudget: 10.0 +}); + +const validationService = new ModelValidationService({ + validationMetrics: ['accuracy', 'precision', 'recall', 'f1'], + fairnessMetrics: ['demographic_parity', 'equalized_odds', 'equal_opportunity'], + accuracyThreshold: 0.7, + fairnessThreshold: 0.8, + stabilityThreshold: 0.9 +}); + +const analyticsService = new FederatedLearningAnalytics({ + updateInterval: 5000, + retentionPeriod: 24 * 60 * 60 * 1000, + maxDataPoints: 1000 +}); + +// Middleware for request validation +const validateRequest = (req, res, next) => { + try { + // Basic validation + if (!req.body && req.method !== 'GET') { + return res.status(400).json({ + success: false, + message: 'Request body is required' + }); + } + next(); + } catch (error) { + res.status(400).json({ + success: false, + message: 'Invalid request format', + error: error.message + }); + } +}; + +// Initialize federated learning system +router.post('/initialize', async (req, res) => { + try { + const { modelArchitecture } = req.body; + + if (!modelArchitecture) { + return res.status(400).json({ + success: false, + message: 'Model architecture is required' + }); + } + + await flCoordinator.initialize(modelArchitecture); + + // Start analytics monitoring + analyticsService.startMonitoring(); + + res.json({ + success: true, + message: 'Federated learning system initialized successfully', + data: { + coordinator: flCoordinator.getStatus(), + privacy: privacyAggregator.getPrivacyBudgetStatus(), + analytics: analyticsService.getMonitoringStatus() + } + }); + } catch (error) { + console.error('❌ Failed to initialize FL system:', error); + res.status(500).json({ + success: false, + message: 'Failed to initialize federated learning system', + error: error.message + }); + } +}); + +// Register participant institution +router.post('/participants/register', validateRequest, async (req, res) => { + try { + const { institutionId, publicKey, metadata } = req.body; + + if (!institutionId || !publicKey) { + return res.status(400).json({ + success: false, + message: 'Institution ID and public key are required' + }); + } + + const participant = flCoordinator.registerParticipant(institutionId, publicKey, metadata); + + res.json({ + success: true, + message: 'Participant registered successfully', + data: participant + }); + } catch (error) { + console.error('❌ Failed to register participant:', error); + res.status(500).json({ + success: false, + message: 'Failed to register participant', + error: error.message + }); + } +}); + +// Get all participants +router.get('/participants', (req, res) => { + try { + const participants = Array.from(flCoordinator.participants.values()); + + res.json({ + success: true, + data: participants, + count: participants.length + }); + } catch (error) { + console.error('❌ Failed to get participants:', error); + res.status(500).json({ + success: false, + message: 'Failed to retrieve participants', + error: error.message + }); + } +}); + +// Start new federated learning round +router.post('/rounds/start', validateRequest, async (req, res) => { + try { + const roundConfig = req.body; + + const round = await flCoordinator.startRound(roundConfig); + + res.json({ + success: true, + message: 'Federated learning round started successfully', + data: round + }); + } catch (error) { + console.error('❌ Failed to start round:', error); + res.status(500).json({ + success: false, + message: 'Failed to start federated learning round', + error: error.message + }); + } +}); + +// Submit model update from participant +router.post('/rounds/:roundId/updates', validateRequest, async (req, res) => { + try { + const { roundId } = req.params; + const { participantId, modelUpdate, signature } = req.body; + + if (!participantId || !modelUpdate || !signature) { + return res.status(400).json({ + success: false, + message: 'Participant ID, model update, and signature are required' + }); + } + + const result = await flCoordinator.receiveModelUpdate(participantId, modelUpdate, signature); + + res.json({ + success: true, + message: 'Model update received successfully', + data: { result } + }); + } catch (error) { + console.error('❌ Failed to receive model update:', error); + res.status(500).json({ + success: false, + message: 'Failed to receive model update', + error: error.message + }); + } +}); + +// Get current round status +router.get('/rounds/current', (req, res) => { + try { + const status = flCoordinator.getStatus(); + + res.json({ + success: true, + data: status + }); + } catch (error) { + console.error('❌ Failed to get round status:', error); + res.status(500).json({ + success: false, + message: 'Failed to retrieve round status', + error: error.message + }); + } +}); + +// Get global model +router.get('/model', (req, res) => { + try { + const model = flCoordinator.getGlobalModel(); + + res.json({ + success: true, + data: model + }); + } catch (error) { + console.error('❌ Failed to get global model:', error); + res.status(500).json({ + success: false, + message: 'Failed to retrieve global model', + error: error.message + }); + } +}); + +// Privacy-preserving aggregation +router.post('/aggregate', validateRequest, async (req, res) => { + try { + const { updates, aggregationMethod } = req.body; + + if (!updates || !Array.isArray(updates)) { + return res.status(400).json({ + success: false, + message: 'Updates array is required' + }); + } + + const result = await privacyAggregator.aggregateModelUpdates(updates, aggregationMethod); + + // Validate privacy guarantees + const privacyValid = privacyAggregator.validatePrivacyGuarantees(result); + + res.json({ + success: true, + message: 'Privacy-preserving aggregation completed', + data: { + aggregation: result, + privacyValid + } + }); + } catch (error) { + console.error('❌ Failed to aggregate models:', error); + res.status(500).json({ + success: false, + message: 'Failed to perform privacy-preserving aggregation', + error: error.message + }); + } +}); + +// Secure multi-party computation +router.post('/mpc/initialize', validateRequest, async (req, res) => { + try { + const { participantIds, computationType } = req.body; + + if (!participantIds || !Array.isArray(participantIds)) { + return res.status(400).json({ + success: false, + message: 'Participant IDs array is required' + }); + } + + const computation = await mpcService.initializeComputation(participantIds, computationType); + + res.json({ + success: true, + message: 'Secure computation initialized', + data: computation + }); + } catch (error) { + console.error('❌ Failed to initialize MPC:', error); + res.status(500).json({ + success: false, + message: 'Failed to initialize secure multi-party computation', + error: error.message + }); + } +}); + +// Distribute shares in MPC +router.post('/mpc/:computationId/distribute-shares', validateRequest, async (req, res) => { + try { + const { computationId } = req.params; + const { values } = req.body; + + if (!values || !Array.isArray(values)) { + return res.status(400).json({ + success: false, + message: 'Values array is required' + }); + } + + const shares = await mpcService.distributeShares(computationId, values); + + res.json({ + success: true, + message: 'Shares distributed successfully', + data: shares + }); + } catch (error) { + console.error('❌ Failed to distribute shares:', error); + res.status(500).json({ + success: false, + message: 'Failed to distribute shares', + error: error.message + }); + } +}); + +// Collect shares in MPC +router.post('/mpc/:computationId/collect-shares', validateRequest, async (req, res) => { + try { + const { computationId } = req.params; + const { participantId, shares } = req.body; + + if (!participantId || !shares) { + return res.status(400).json({ + success: false, + message: 'Participant ID and shares are required' + }); + } + + const result = await mpcService.collectShares(computationId, participantId, shares); + + res.json({ + success: true, + message: result ? 'Computation completed' : 'Shares collected, waiting for more participants', + data: { result, completed: result !== null } + }); + } catch (error) { + console.error('❌ Failed to collect shares:', error); + res.status(500).json({ + success: false, + message: 'Failed to collect shares', + error: error.message + }); + } +}); + +// Apply differential privacy +router.post('/privacy/apply', validateRequest, async (req, res) => { + try { + const { value, queryType, epsilon } = req.body; + + if (value === undefined) { + return res.status(400).json({ + success: false, + message: 'Value is required' + }); + } + + const result = dpService.applyDifferentialPrivacy(value, queryType, epsilon); + + res.json({ + success: true, + message: 'Differential privacy applied successfully', + data: result + }); + } catch (error) { + console.error('❌ Failed to apply differential privacy:', error); + res.status(500).json({ + success: false, + message: 'Failed to apply differential privacy', + error: error.message + }); + } +}); + +// Get privacy budget status +router.get('/privacy/budget', (req, res) => { + try { + const budget = dpService.getPrivacyBudgetStatus(); + + res.json({ + success: true, + data: budget + }); + } catch (error) { + console.error('❌ Failed to get privacy budget:', error); + res.status(500).json({ + success: false, + message: 'Failed to retrieve privacy budget status', + error: error.message + }); + } +}); + +// Update privacy parameters +router.put('/privacy/parameters', validateRequest, async (req, res) => { + try { + const params = req.body; + + dpService.updatePrivacyParameters(params); + + res.json({ + success: true, + message: 'Privacy parameters updated successfully', + data: dpService.getPrivacyBudgetStatus() + }); + } catch (error) { + console.error('❌ Failed to update privacy parameters:', error); + res.status(500).json({ + success: false, + message: 'Failed to update privacy parameters', + error: error.message + }); + } +}); + +// Validate model +router.post('/validation/validate', validateRequest, async (req, res) => { + try { + const { model, testData, sensitiveAttributes } = req.body; + + if (!model || !testData) { + return res.status(400).json({ + success: false, + message: 'Model and test data are required' + }); + } + + const results = await validationService.validateModel(model, testData, sensitiveAttributes); + + res.json({ + success: true, + message: 'Model validation completed', + data: results + }); + } catch (error) { + console.error('❌ Failed to validate model:', error); + res.status(500).json({ + success: false, + message: 'Failed to validate model', + error: error.message + }); + } +}); + +// Generate fairness report +router.post('/validation/fairness-report', validateRequest, async (req, res) => { + try { + const { validationResults } = req.body; + + if (!validationResults) { + return res.status(400).json({ + success: false, + message: 'Validation results are required' + }); + } + + const report = validationService.generateFairnessReport(validationResults); + + res.json({ + success: true, + message: 'Fairness report generated successfully', + data: report + }); + } catch (error) { + console.error('❌ Failed to generate fairness report:', error); + res.status(500).json({ + success: false, + message: 'Failed to generate fairness report', + error: error.message + }); + } +}); + +// Get validation history +router.get('/validation/history', (req, res) => { + try { + const limit = parseInt(req.query.limit) || 10; + const history = validationService.getValidationHistory(limit); + + res.json({ + success: true, + data: history, + count: history.length + }); + } catch (error) { + console.error('❌ Failed to get validation history:', error); + res.status(500).json({ + success: false, + message: 'Failed to retrieve validation history', + error: error.message + }); + } +}); + +// Analytics dashboard data +router.get('/analytics/dashboard', (req, res) => { + try { + const timeRange = req.query.timeRange || '1h'; + const dashboardData = analyticsService.getDashboardData(timeRange); + + res.json({ + success: true, + data: dashboardData + }); + } catch (error) { + console.error('❌ Failed to get dashboard data:', error); + res.status(500).json({ + success: false, + message: 'Failed to retrieve dashboard data', + error: error.message + }); + } +}); + +// Get analytics report +router.get('/analytics/report', (req, res) => { + try { + const reportType = req.query.type || 'comprehensive'; + const report = analyticsService.getAnalyticsReport(reportType); + + res.json({ + success: true, + data: report + }); + } catch (error) { + console.error('❌ Failed to generate analytics report:', error); + res.status(500).json({ + success: false, + message: 'Failed to generate analytics report', + error: error.message + }); + } +}); + +// Export analytics data +router.get('/analytics/export', (req, res) => { + try { + const format = req.query.format || 'json'; + const data = analyticsService.exportAnalyticsData(format); + + const contentType = format === 'csv' ? 'text/csv' : 'application/json'; + const filename = `federated-learning-analytics.${format}`; + + res.setHeader('Content-Type', contentType); + res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); + res.send(data); + } catch (error) { + console.error('❌ Failed to export analytics data:', error); + res.status(500).json({ + success: false, + message: 'Failed to export analytics data', + error: error.message + }); + } +}); + +// System health check +router.get('/health', (req, res) => { + try { + const health = { + timestamp: new Date(), + status: 'healthy', + services: { + coordinator: flCoordinator.getStatus(), + privacy: privacyAggregator.getPrivacyBudgetStatus(), + mpc: { activeComputations: mpcService.activeComputations.size }, + differentialPrivacy: dpService.getPrivacyBudgetStatus(), + validation: { historyCount: validationService.validationHistory.length }, + analytics: analyticsService.getMonitoringStatus() + } + }; + + res.json({ + success: true, + data: health + }); + } catch (error) { + console.error('❌ Health check failed:', error); + res.status(500).json({ + success: false, + message: 'Health check failed', + error: error.message + }); + } +}); + +// System configuration +router.get('/config', (req, res) => { + try { + const config = { + federatedLearning: { + minParticipants: flCoordinator.minParticipants, + maxParticipants: flCoordinator.maxParticipants, + aggregationStrategy: flCoordinator.aggregationStrategy, + privacyBudget: flCoordinator.privacyBudget, + differentialPrivacy: flCoordinator.differentialPrivacy, + secureAggregation: flCoordinator.secureAggregation + }, + privacy: { + encryptionScheme: privacyAggregator.encryptionScheme, + epsilon: privacyAggregator.epsilon, + delta: privacyAggregator.delta, + secureAggregation: privacyAggregator.secureAggregation, + homomorphicEncryption: privacyAggregator.homomorphicEncryption + }, + mpc: { + thresholdScheme: mpcService.thresholdScheme, + threshold: mpcService.threshold, + maxParticipants: mpcService.maxParticipants + }, + differentialPrivacy: { + epsilon: dpService.epsilon, + delta: dpService.delta, + sensitivity: dpService.sensitivity, + mechanism: dpService.mechanism, + privacyBudget: dpService.privacyBudget + }, + validation: { + accuracyThreshold: validationService.thresholds.accuracy, + fairnessThreshold: validationService.thresholds.fairness, + stabilityThreshold: validationService.thresholds.stability + }, + analytics: analyticsService.dashboardConfig + }; + + res.json({ + success: true, + data: config + }); + } catch (error) { + console.error('❌ Failed to get configuration:', error); + res.status(500).json({ + success: false, + message: 'Failed to retrieve system configuration', + error: error.message + }); + } +}); + +// Error handling middleware +router.use((error, req, res, next) => { + console.error('❌ Federated Learning API Error:', error); + + res.status(error.status || 500).json({ + success: false, + message: error.message || 'Internal server error', + ...(process.env.NODE_ENV === 'development' && { stack: error.stack }) + }); +}); module.exports = router; diff --git a/backend/src/routes/fraudDetectionRoutes.js b/backend/src/routes/fraudDetectionRoutes.js index eaa7fde..e62bd50 100644 --- a/backend/src/routes/fraudDetectionRoutes.js +++ b/backend/src/routes/fraudDetectionRoutes.js @@ -1,10 +1,3 @@ -/** - * @openapi - * tags: - * - name: Fraud Detection - * description: Fraud detection, plagiarism checking, and anomaly monitoring - */ - /** * Fraud Detection API Routes * RESTful endpoints for fraud detection and prevention system @@ -32,14 +25,9 @@ const validateRequest = (req, res, next) => { }; /** - * @openapi - * /api/v1/fraud-detection/health: - * get: - * tags: [Fraud Detection] - * summary: Check fraud detection system health - * responses: - * '200': - * description: Health status + * @route GET /api/v1/fraud-detection/health + * @desc Check fraud detection system health + * @access Private */ router.get('/health', async (req, res) => { try { @@ -132,27 +120,9 @@ router.post('/analyze-submission', validateRequest, async (req, res) => { }); /** - * @openapi - * /api/v1/fraud-detection/verify-credential: - * post: - * tags: [Fraud Detection] - * summary: Verify credential for fraud detection - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * credentialId: - * type: string - * userId: - * type: string - * credentialType: - * type: string - * responses: - * '200': - * description: Credential verified + * @route POST /api/v1/fraud-detection/verify-credential + * @desc Verify credential for fraud detection + * @access Private */ router.post('/verify-credential', validateRequest, async (req, res) => { try { @@ -204,14 +174,9 @@ router.post('/verify-credential', validateRequest, async (req, res) => { }); /** - * @openapi - * /api/v1/fraud-detection/detect-anomaly: - * post: - * tags: [Fraud Detection] - * summary: Detect anomalies in user behavior - * responses: - * '200': - * description: Anomaly detection result + * @route POST /api/v1/fraud-detection/detect-anomaly + * @desc Detect anomalies in user behavior + * @access Private */ router.post('/detect-anomaly', validateRequest, async (req, res) => { try { @@ -236,6 +201,8 @@ router.post('/detect-anomaly', validateRequest, async (req, res) => { metadata }; + // This would trigger real-time anomaly detection + // For now, return a simulated response const anomalyResult = { activityId: require('crypto').randomUUID(), isAnomalous: false, @@ -259,14 +226,9 @@ router.post('/detect-anomaly', validateRequest, async (req, res) => { }); /** - * @openapi - * /api/v1/fraud-detection/initiate-investigation: - * post: - * tags: [Fraud Detection] - * summary: Initiate automated investigation - * responses: - * '200': - * description: Investigation initiated + * @route POST /api/v1/fraud-detection/initiate-investigation + * @desc Initiate automated investigation + * @access Private */ router.post('/initiate-investigation', validateRequest, async (req, res) => { try { @@ -312,14 +274,9 @@ router.post('/initiate-investigation', validateRequest, async (req, res) => { }); /** - * @openapi - * /api/v1/fraud-detection/investigations: - * get: - * tags: [Fraud Detection] - * summary: Get list of investigations - * responses: - * '200': - * description: Investigations retrieved + * @route GET /api/v1/fraud-detection/investigations + * @desc Get list of investigations + * @access Private */ router.get('/investigations', async (req, res) => { try { @@ -358,20 +315,9 @@ router.get('/investigations', async (req, res) => { }); /** - * @openapi - * /api/v1/fraud-detection/investigations/{investigationId}: - * get: - * tags: [Fraud Detection] - * summary: Get specific investigation details - * parameters: - * - in: path - * name: investigationId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Investigation details retrieved + * @route GET /api/v1/fraud-detection/investigations/:investigationId + * @desc Get specific investigation details + * @access Private */ router.get('/investigations/:investigationId', async (req, res) => { try { @@ -401,20 +347,9 @@ router.get('/investigations/:investigationId', async (req, res) => { }); /** - * @openapi - * /api/v1/fraud-detection/investigations/{investigationId}/evidence: - * post: - * tags: [Fraud Detection] - * summary: Add evidence to investigation - * parameters: - * - in: path - * name: investigationId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Evidence added + * @route POST /api/v1/fraud-detection/investigations/:investigationId/evidence + * @desc Add evidence to investigation + * @access Private */ router.post('/investigations/:investigationId/evidence', validateRequest, async (req, res) => { try { @@ -460,14 +395,9 @@ router.post('/investigations/:investigationId/evidence', validateRequest, async }); /** - * @openapi - * /api/v1/fraud-detection/statistics: - * get: - * tags: [Fraud Detection] - * summary: Get fraud detection statistics - * responses: - * '200': - * description: Statistics retrieved + * @route GET /api/v1/fraud-detection/statistics + * @desc Get fraud detection statistics + * @access Private */ router.get('/statistics', async (req, res) => { try { diff --git a/backend/src/routes/gamification.js b/backend/src/routes/gamification.js index 060acbd..b3376b3 100644 --- a/backend/src/routes/gamification.js +++ b/backend/src/routes/gamification.js @@ -1,153 +1,280 @@ +const express = require('express'); +const router = express.Router(); +const GamificationEngine = require('../services/gamification/GamificationEngine'); +const Achievement = require('../models/Achievement'); +const Challenge = require('../models/Challenge'); +const logger = require('../../utils/logger'); + +// Initialize engine +const gamificationEngine = new GamificationEngine(); + /** - * @openapi - * tags: - * - name: Gamification - * description: Gamification features including achievements, badges, leaderboards + * @route GET /api/gamification/leaderboard + * @desc Get leaderboard with optional filtering */ +router.get('/leaderboard', async (req, res) => { + try { + const { category = 'global', categoryId, page = 1, limit = 50 } = req.query; + + const leaderboard = await gamificationEngine.getLeaderboard( + category, + categoryId, + parseInt(page), + parseInt(limit) + ); -const express = require("express"); -const router = express.Router(); -const { authenticate } = require("../middleware/auth"); -const gamificationController = require("../controllers/gamificationController"); - -router.use(authenticate); + res.json({ + success: true, + data: leaderboard + }); + } catch (error) { + logger.error('Error getting leaderboard:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); /** - * @openapi - * /api/gamification/{userId}/points: - * get: - * tags: [Gamification] - * summary: Get points for user - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: User points retrieved + * @route GET /api/gamification/user/:userId/achievements + * @desc Get user's achievements */ -router.get("/:userId/points", gamificationController.getPoints); +router.get('/user/:userId/achievements', async (req, res) => { + try { + const { userId } = req.params; + const { earned, category } = req.query; + + const query = { userId }; + if (earned !== undefined) query.isEarned = earned === 'true'; + if (category) query.category = category; + + const achievements = await Achievement.find(query).sort({ earnedDate: -1 }); + + res.json({ + success: true, + data: achievements + }); + } catch (error) { + logger.error('Error getting achievements:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); /** - * @openapi - * /api/gamification/{userId}/badges: - * get: - * tags: [Gamification] - * summary: Get badges for user - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: User badges retrieved + * @route POST /api/gamification/event + * @desc Process gamification event (lesson complete, quiz complete, etc.) */ -router.get("/:userId/badges", gamificationController.getBadges); +router.post('/event', async (req, res) => { + try { + const { userId, event, data } = req.body; + + if (!userId || !event) { + return res.status(400).json({ + success: false, + error: 'Missing required fields: userId, event' + }); + } + + const result = await gamificationEngine.processEvent(userId, event, data); + + res.json({ + success: true, + data: result + }); + } catch (error) { + logger.error('Error processing event:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); /** - * @openapi - * /api/gamification/{userId}/leaderboard: - * get: - * tags: [Gamification] - * summary: Get leaderboard position and rankings - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Leaderboard data retrieved + * @route POST /api/gamification/points/award + * @desc Award points to user */ -router.get("/:userId/leaderboard", gamificationController.getLeaderboard); +router.post('/points/award', async (req, res) => { + try { + const { userId, amount, category, description, metadata } = req.body; + + if (!userId || amount === undefined || !category || !description) { + return res.status(400).json({ + success: false, + error: 'Missing required fields: userId, amount, category, description' + }); + } + + const transaction = await gamificationEngine.awardPoints( + userId, + amount, + category, + description, + metadata + ); + + res.json({ + success: true, + data: transaction + }); + } catch (error) { + logger.error('Error awarding points:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); /** - * @openapi - * /api/gamification/achievements: - * get: - * tags: [Gamification] - * summary: Get all achievements - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Achievements retrieved - * post: - * tags: [Gamification] - * summary: Create new achievement - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Achievement created + * @route GET /api/gamification/challenges + * @desc Get active challenges */ -router.get("/achievements", gamificationController.getAchievements); -router.post("/achievements", gamificationController.createAchievement); +router.get('/challenges', async (req, res) => { + try { + const { status = 'active', type, category } = req.query; + + const query = { status }; + if (type) query.type = type; + if (category) query.category = category; + + const challenges = await Challenge.find(query) + .sort({ startDate: -1 }) + .limit(20); + + res.json({ + success: true, + data: challenges + }); + } catch (error) { + logger.error('Error getting challenges:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); /** - * @openapi - * /api/gamification/achievements/{achievementId}: - * put: - * tags: [Gamification] - * summary: Update achievement - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: achievementId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Achievement updated - * delete: - * tags: [Gamification] - * summary: Delete achievement - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: achievementId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Achievement deleted + * @route POST /api/gamification/challenges/:challengeId/join + * @desc Join a challenge */ -router.put("/achievements/:achievementId", gamificationController.updateAchievement); -router.delete("/achievements/:achievementId", gamificationController.deleteAchievement); +router.post('/challenges/:challengeId/join', async (req, res) => { + try { + const { challengeId } = req.params; + const { userId } = req.body; + + const challenge = await Challenge.findById(challengeId); + + if (!challenge) { + return res.status(404).json({ + success: false, + error: 'Challenge not found' + }); + } + + if (challenge.status !== 'active') { + return res.status(400).json({ + success: false, + error: 'Challenge is not active' + }); + } + + // Add participant + challenge.participants.push({ + userId, + joinedAt: new Date(), + progress: 0, + completed: false + }); + + await challenge.save(); + + res.json({ + success: true, + message: 'Joined challenge successfully' + }); + } catch (error) { + logger.error('Error joining challenge:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); /** - * @openapi - * /api/gamification/{userId}/redeem-badge: - * post: - * tags: [Gamification] - * summary: Redeem badge for user - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Badge redeemed + * @route PUT /api/gamification/challenges/:challengeId/progress + * @desc Update challenge progress */ -router.post("/:userId/redeem-badge", gamificationController.redeemBadge); +router.put('/challenges/:challengeId/progress', async (req, res) => { + try { + const { challengeId } = req.params; + const { userId, objectiveId, progress } = req.body; + + const challenge = await Challenge.findById(challengeId); + + if (!challenge) { + return res.status(404).json({ + success: false, + error: 'Challenge not found' + }); + } + + const participant = challenge.participants.find(p => p.userId === userId); + + if (!participant) { + return res.status(404).json({ + success: false, + error: 'Not a participant of this challenge' + }); + } + + // Update objective progress + const objective = challenge.objectives.find(o => o.id === objectiveId); + if (objective) { + objective.current = progress; + if (progress >= objective.target) { + objective.completed = true; + } + } + + await challenge.save(); + + // Check if challenge completed + const allCompleted = challenge.objectives.every(o => o.completed); + if (allCompleted && !participant.completed) { + participant.completed = true; + + // Award rewards + if (challenge.rewards.points > 0) { + await gamificationEngine.awardPoints( + userId, + challenge.rewards.points, + 'challenge', + `Completed challenge: ${challenge.title}` + ); + } + } + + res.json({ + success: true, + data: { + participant, + challengeCompleted: allCompleted + } + }); + } catch (error) { + logger.error('Error updating challenge progress:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); module.exports = router; diff --git a/backend/src/routes/holographicRoutes.ts b/backend/src/routes/holographicRoutes.ts index fe59884..0d902f1 100644 --- a/backend/src/routes/holographicRoutes.ts +++ b/backend/src/routes/holographicRoutes.ts @@ -1,79 +1,12 @@ -/** - * @openapi - * tags: - * - name: Holographic - * description: Holographic storage and retrieval for content - */ - import express, { Router } from "express"; import * as holographicController from "../controllers/holographicController"; const router: Router = express.Router(); -/** - * @openapi - * /api/holographic/encode: - * post: - * tags: [Holographic] - * summary: Encode content using holographic storage - * responses: - * '200': - * description: Content encoded - */ router.post("/encode", holographicController.encodeContent); - -/** - * @openapi - * /api/holographic/decode/{hash}: - * get: - * tags: [Holographic] - * summary: Decode holographic content - * parameters: - * - in: path - * name: hash - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Content decoded - */ router.get("/decode/:hash", holographicController.decodeContent); - -/** - * @openapi - * /api/holographic/access/parallel: - * post: - * tags: [Holographic] - * summary: Parallel access to holographic content - * responses: - * '200': - * description: Parallel access granted - */ router.post("/access/parallel", holographicController.parallelAccess); - -/** - * @openapi - * /api/holographic/metrics: - * get: - * tags: [Holographic] - * summary: Get holographic storage metrics - * responses: - * '200': - * description: Metrics retrieved - */ router.get("/metrics", holographicController.getMetrics); - -/** - * @openapi - * /api/holographic/optimize: - * post: - * tags: [Holographic] - * summary: Optimize holographic storage - * responses: - * '200': - * description: Storage optimized - */ router.post("/optimize", holographicController.optimizeStorage); export default router; diff --git a/backend/src/routes/notificationRoutes.ts b/backend/src/routes/notificationRoutes.ts index 8931290..6ca20a0 100644 --- a/backend/src/routes/notificationRoutes.ts +++ b/backend/src/routes/notificationRoutes.ts @@ -1,105 +1,22 @@ -/** - * @openapi - * tags: - * - name: Notifications - * description: User notification management - */ - import express, { Router } from "express"; import { notificationController } from "../controllers/notificationController"; const router: Router = express.Router(); -/** - * @openapi - * /api/notifications/{userId}: - * get: - * tags: [Notifications] - * summary: Get notification history for user - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Notifications retrieved - * delete: - * tags: [Notifications] - * summary: Delete notification - * parameters: - * - in: path - * name: notificationId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Notification deleted - */ +// Get notification history router.get("/:userId", notificationController.getNotifications); -router.delete("/:notificationId", notificationController.deleteNotification); -/** - * @openapi - * /api/notifications/{notificationId}/read: - * patch: - * tags: [Notifications] - * summary: Mark notification as read - * parameters: - * - in: path - * name: notificationId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Marked as read - */ +// Mark as read router.patch("/:notificationId/read", notificationController.markAsRead); -/** - * @openapi - * /api/notifications/read-all: - * patch: - * tags: [Notifications] - * summary: Mark all notifications as read - * responses: - * '200': - * description: All marked as read - */ +// Mark all as read router.patch("/read-all", notificationController.markAllAsRead); -/** - * @openapi - * /api/notifications/{userId}/preferences: - * get: - * tags: [Notifications] - * summary: Get notification preferences - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Preferences retrieved - * put: - * tags: [Notifications] - * summary: Update notification preferences - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Preferences updated - */ +// Preferences router.get("/:userId/preferences", notificationController.getPreferences); router.put("/:userId/preferences", notificationController.updatePreferences); +// Delete +router.delete("/:notificationId", notificationController.deleteNotification); + export default router; diff --git a/backend/src/routes/offline.js b/backend/src/routes/offline.js index da12b40..86f2eb3 100644 --- a/backend/src/routes/offline.js +++ b/backend/src/routes/offline.js @@ -1,89 +1,209 @@ -/** - * @openapi - * tags: - * - name: Offline - * description: Offline access and content download management - */ - -const express = require("express"); +const express = require('express'); const router = express.Router(); -const { authenticate } = require("../middleware/auth"); -const offlineController = require("../controllers/offlineController"); +const OfflineContent = require('../models/OfflineContent'); +const Content = require('../models/Content'); +const auth = require('../middleware/auth'); +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +// Get all offline content for a user +router.get('/', auth, async (req, res) => { + try { + const { deviceId } = req.query; + + if (!deviceId) { + return res.status(400).json({ error: 'Device ID is required' }); + } + + const offlineContent = await OfflineContent.find({ + user: req.user.id, + deviceId + }) + .populate('content', 'title type duration files metadata') + .sort({ lastAccessed: -1 }); + + res.json(offlineContent); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); -router.use(authenticate); +// Request content for offline download +router.post('/request', auth, async (req, res) => { + try { + const { contentId, deviceId, quality = 'medium' } = req.body; + + if (!contentId || !deviceId) { + return res.status(400).json({ error: 'Content ID and Device ID are required' }); + } + + const content = await Content.findById(contentId); + if (!content) { + return res.status(404).json({ error: 'Content not found' }); + } + + // Check if content allows offline download + if (content.isPremium) { + // Add premium validation logic here + // For now, allow premium content for offline download + } + + // Check if already downloaded + const existing = await OfflineContent.findOne({ + user: req.user.id, + content: contentId, + deviceId + }); + + if (existing) { + return res.json(existing); + } + + // Create offline content record + const offlineContent = new OfflineContent({ + user: req.user.id, + content: contentId, + deviceId, + cachedFiles: [], + isDownloaded: false, + downloadProgress: 0 + }); + + await offlineContent.save(); + await offlineContent.populate('content', 'title type duration files metadata'); + + res.status(201).json(offlineContent); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); -/** - * @openapi - * /api/offline/content: - * post: - * tags: [Offline] - * summary: Download content for offline access - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Content downloaded - */ -router.post("/content", offlineController.downloadContent); +// Update download progress +router.put('/:offlineId/progress', auth, async (req, res) => { + try { + const { progress, fileData } = req.body; + + const offlineContent = await OfflineContent.findOne({ + _id: req.params.offlineId, + user: req.user.id + }); + + if (!offlineContent) { + return res.status(404).json({ error: 'Offline content not found' }); + } + + offlineContent.downloadProgress = Math.min(100, Math.max(0, progress)); + + if (fileData) { + const existingFile = offlineContent.cachedFiles.find( + f => f.type === fileData.type + ); + + if (existingFile) { + existingFile.localPath = fileData.localPath; + existingFile.size = fileData.size; + existingFile.checksum = fileData.checksum; + } else { + offlineContent.cachedFiles.push(fileData); + } + } + + if (offlineContent.downloadProgress === 100) { + offlineContent.isDownloaded = true; + } + + await offlineContent.save(); + res.json(offlineContent); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); -/** - * @openapi - * /api/offline/content/{contentId}: - * delete: - * tags: [Offline] - * summary: Remove downloaded content - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: contentId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Content removed - */ -router.delete("/content/:contentId", offlineController.removeContent); +// Mark content as accessed +router.put('/:offlineId/access', auth, async (req, res) => { + try { + const offlineContent = await OfflineContent.findOneAndUpdate( + { + _id: req.params.offlineId, + user: req.user.id + }, + { + lastAccessed: new Date(), + $inc: { 'content.viewCount': 1 } + }, + { new: true } + ).populate('content', 'title type duration'); + + if (!offlineContent) { + return res.status(404).json({ error: 'Offline content not found' }); + } + + res.json(offlineContent); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); -/** - * @openapi - * /api/offline/{userId}: - * get: - * tags: [Offline] - * summary: Get offline content list - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Content list retrieved - */ -router.get("/:userId", offlineController.getOfflineContent); +// Delete offline content +router.delete('/:offlineId', auth, async (req, res) => { + try { + const offlineContent = await OfflineContent.findOneAndDelete({ + _id: req.params.offlineId, + user: req.user.id + }); + + if (!offlineContent) { + return res.status(404).json({ error: 'Offline content not found' }); + } + + // Optionally delete actual files from filesystem + offlineContent.cachedFiles.forEach(file => { + if (file.localPath && fs.existsSync(file.localPath)) { + try { + fs.unlinkSync(file.localPath); + } catch (err) { + console.error('Failed to delete file:', err); + } + } + }); + + res.json({ message: 'Offline content deleted successfully' }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); -/** - * @openapi - * /api/offline/{userId}/sync: - * post: - * tags: [Offline] - * summary: Sync offline changes - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Offline changes synced - */ -router.post("/:userId/sync", offlineController.syncOfflineChanges); +// Get storage usage for a device +router.get('/storage/:deviceId', auth, async (req, res) => { + try { + const { deviceId } = req.params; + + const offlineContent = await OfflineContent.find({ + user: req.user.id, + deviceId, + isDownloaded: true + }); + + let totalSize = 0; + let fileCount = 0; + + offlineContent.forEach(item => { + item.cachedFiles.forEach(file => { + totalSize += file.size || 0; + fileCount++; + }); + }); + + res.json({ + totalSize, + fileCount, + contentCount: offlineContent.length, + deviceId + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); module.exports = router; diff --git a/backend/src/routes/optimization.js b/backend/src/routes/optimization.js index 5b50568..17f75e6 100644 --- a/backend/src/routes/optimization.js +++ b/backend/src/routes/optimization.js @@ -1,94 +1,79 @@ -/** - * @openapi - * tags: - * - name: Optimization - * description: System optimization and resource management - */ - -const express = require("express"); +const express = require('express'); const router = express.Router(); -const { authenticate, authorize } = require("../middleware/auth"); -const optimizationController = require("../controllers/optimizationController"); - -router.use(authenticate, authorize("admin")); - -/** - * @openapi - * /api/optimization/cache: - * get: - * tags: [Optimization] - * summary: Get cache optimization status - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Cache status retrieved - * post: - * tags: [Optimization] - * summary: Optimize cache - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Cache optimized - */ -router.get("/cache", optimizationController.getCacheStatus); -router.post("/cache", optimizationController.optimizeCache); - -/** - * @openapi - * /api/optimization/compression: - * post: - * tags: [Optimization] - * summary: Compress data for optimization - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Data compressed - */ -router.post("/compression", optimizationController.optimizeCompression); - -/** - * @openapi - * /api/optimization/performance: - * get: - * tags: [Optimization] - * summary: Get performance optimization report - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Performance report retrieved - */ -router.get("/performance", optimizationController.getPerformanceReport); - -/** - * @openapi - * /api/optimization/scheduler: - * post: - * tags: [Optimization] - * summary: Schedule optimization task - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Task scheduled - */ -router.post("/scheduler", optimizationController.scheduleOptimization); - -/** - * @openapi - * /api/optimization/recommendations: - * get: - * tags: [Optimization] - * summary: Get optimization recommendations - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Recommendations retrieved - */ -router.get("/recommendations", optimizationController.getOptimizationRecommendations); +const OptimizationController = require('../controllers/optimizationController'); + +// Initialize optimization controller +const optimizationController = new OptimizationController(); + +// Initialize optimization services +router.post('/initialize', async (req, res) => { + await optimizationController.initialize(req, res); +}); + +// Learning Path Optimization +router.post('/learning-paths/optimize', async (req, res) => { + await optimizationController.optimizeLearningPath(req, res); +}); + +// Resource Allocation Optimization +router.post('/resources/optimize', async (req, res) => { + await optimizationController.optimizeResourceAllocation(req, res); +}); + +// Dynamic Replanning +router.post('/replanning/register', async (req, res) => { + await optimizationController.registerPath(req, res); +}); + +router.post('/replanning/update-environment', async (req, res) => { + await optimizationController.updateEnvironmentState(req, res); +}); + +// Swarm Coordination +router.post('/swarm/initialize', async (req, res) => { + await optimizationController.initializeSwarm(req, res); +}); + +// Analytics and Visualization +router.get('/analytics', async (req, res) => { + await optimizationController.getAnalytics(req, res); +}); + +router.get('/visualizations/:vizId', async (req, res) => { + await optimizationController.getVisualization(req, res); +}); + +router.get('/visualizations', async (req, res) => { + await optimizationController.getAllVisualizations(req, res); +}); + +router.get('/realtime', async (req, res) => { + await optimizationController.getRealTimeData(req, res); +}); + +// Session Management +router.get('/sessions/:sessionId', async (req, res) => { + await optimizationController.getSessionStatus(req, res); +}); + +// Data Export +router.get('/export', async (req, res) => { + await optimizationController.exportData(req, res); +}); + +// Health Check +router.get('/health', async (req, res) => { + await optimizationController.healthCheck(req, res); +}); + +// Error handling middleware +router.use((error, req, res, next) => { + console.error('Optimization API error:', error); + res.status(500).json({ + error: 'Internal server error', + message: error.message, + timestamp: new Date() + }); +}); module.exports = router; diff --git a/backend/src/routes/paymentRoutes.ts b/backend/src/routes/paymentRoutes.ts index 88f8e6e..1c88e74 100644 --- a/backend/src/routes/paymentRoutes.ts +++ b/backend/src/routes/paymentRoutes.ts @@ -1,91 +1,54 @@ /** - * @openapi - * tags: - * - name: Payments - * description: Payment processing and transaction management + * Payment Routes + * API endpoints for payment processing and management */ -import express, { Router } from "express"; -import { paymentController } from "../controllers/paymentController"; +import express, { Router, Request, Response } from "express"; +import { PaymentController } from "../controllers/PaymentController"; +import { authenticateToken, requireRole } from "../middleware/auth"; +import { validatePayment } from "../middleware/validation"; +import { rateLimit } from "express-rate-limit"; +import { UserRole } from "../models/User"; const router: Router = express.Router(); - -/** - * @openapi - * /api/payments/create-payment-intent: - * post: - * tags: [Payments] - * summary: Create payment intent - * responses: - * '200': - * description: Payment intent created - */ -router.post("/create-payment-intent", paymentController.createPaymentIntent); - -/** - * @openapi - * /api/payments/webhook: - * post: - * tags: [Payments] - * summary: Handle payment webhook - * responses: - * '200': - * description: Webhook processed - */ -router.post("/webhook", paymentController.handleWebhook); - -/** - * @openapi - * /api/payments/{paymentId}: - * get: - * tags: [Payments] - * summary: Get payment details - * parameters: - * - in: path - * name: paymentId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Payment details retrieved - */ -router.get("/:paymentId", paymentController.getPayment); - -/** - * @openapi - * /api/payments/{paymentId}/refund: - * post: - * tags: [Payments] - * summary: Refund payment - * parameters: - * - in: path - * name: paymentId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Payment refunded - */ -router.post("/:paymentId/refund", paymentController.refundPayment); - -/** - * @openapi - * /api/payments/history/{userId}: - * get: - * tags: [Payments] - * summary: Get payment history for user - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Payment history retrieved - */ -router.get("/history/:userId", paymentController.getUserPaymentHistory); +const controller = new PaymentController(); +const wrap = (fn: (req: Request, res: Response) => Promise) => + (req: Request, res: Response) => fn(req, res); + +const paymentLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, + max: 20, + message: "Too many payment attempts, please try again later.", + standardHeaders: true, + legacyHeaders: false, +}); + +const refundLimiter = rateLimit({ + windowMs: 60 * 60 * 1000, + max: 5, + message: "Too many refund requests, please try again later.", + standardHeaders: true, + legacyHeaders: false, +}); + +router.post("/intent", authenticateToken as any, paymentLimiter, validatePayment as any, wrap(controller.createPaymentIntent.bind(controller)) as any); +router.post("/stellar/create", authenticateToken as any, paymentLimiter, wrap(controller.createStellarPayment.bind(controller)) as any); +router.post("/stellar/submit", authenticateToken as any, paymentLimiter, wrap(controller.submitStellarPayment.bind(controller)) as any); +router.get("/:id", authenticateToken as any, wrap(controller.getPaymentById.bind(controller)) as any); +router.get("/enrollment/:enrollmentId", authenticateToken as any, wrap(controller.getEnrollmentPayments.bind(controller)) as any); +router.get("/history", authenticateToken as any, wrap(controller.getUserPaymentHistory.bind(controller)) as any); +router.post("/:id/refund", authenticateToken as any, requireRole([UserRole.ADMIN]) as any, refundLimiter, wrap(controller.processRefund.bind(controller)) as any); +router.get("/receipt/:paymentId", authenticateToken as any, wrap(controller.generateReceipt.bind(controller)) as any); +router.get("/settings", wrap(controller.getPaymentSettings.bind(controller)) as any); +router.put("/settings", authenticateToken as any, requireRole([UserRole.ADMIN]) as any, wrap(controller.updatePaymentSettings.bind(controller)) as any); +router.get("/methods", wrap(controller.getSupportedPaymentMethods.bind(controller)) as any); +router.post("/validate", authenticateToken as any, wrap(controller.validatePaymentParameters.bind(controller)) as any); +router.get("/analytics", authenticateToken as any, requireRole([UserRole.ADMIN]) as any, wrap(controller.getPaymentAnalytics.bind(controller)) as any); +router.get("/exchange-rates", wrap(controller.getExchangeRates.bind(controller)) as any); +router.post("/convert", authenticateToken as any, wrap(controller.convertCurrency.bind(controller)) as any); +router.get("/stellar/balance/:address", authenticateToken as any, wrap(controller.getStellarBalance.bind(controller)) as any); +router.get("/stellar/transactions/:address", authenticateToken as any, wrap(controller.getStellarTransactionHistory.bind(controller)) as any); +router.post("/webhook/stellar", wrap(controller.handleStellarWebhook.bind(controller)) as any); +router.post("/webhook/payment-gateway", wrap(controller.handlePaymentGatewayWebhook.bind(controller)) as any); export default router; diff --git a/backend/src/routes/plagiarismDetectionRoutes.ts b/backend/src/routes/plagiarismDetectionRoutes.ts index 86a3285..86334ef 100644 --- a/backend/src/routes/plagiarismDetectionRoutes.ts +++ b/backend/src/routes/plagiarismDetectionRoutes.ts @@ -1,61 +1,261 @@ /** - * @openapi - * tags: - * - name: Plagiarism Detection - * description: Content plagiarism check and analysis + * Plagiarism Detection Routes + * Defines API endpoints for plagiarism detection operations */ -import { Router } from "express"; -import * as plagiarismController from "../controllers/plagiarismDetectionController"; +import { Router, RequestHandler } from "express"; +import { PlagiarismDetectionController } from "../controllers/plagiarismDetectionController"; +import { + authenticateToken, + requireEducatorOrAdmin, + requireAdmin, +} from "../middleware/auth"; +import { handleValidationErrors } from "../middleware/validation"; +import { body, param, query } from "express-validator"; const router: Router = Router(); +const plagiarismController = new PlagiarismDetectionController(); + +// Validation schemas +const analyzeSubmissionValidation = [ + body("submissionId") + .notEmpty() + .withMessage("Submission ID is required") + .isUUID() + .withMessage("Submission ID must be a valid UUID"), + body("content") + .notEmpty() + .withMessage("Content is required") + .isLength({ min: 10, max: 100000 }) + .withMessage("Content must be between 10 and 100,000 characters"), + body("contentType") + .isIn(["text", "code", "mixed"]) + .withMessage("Content type must be text, code, or mixed"), + body("language") + .optional() + .isISO31661Alpha2() + .withMessage("Language must be a valid ISO 3166-1 alpha-2 code"), + body("codeLanguage") + .optional() + .isIn([ + "javascript", + "python", + "java", + "cpp", + "csharp", + "php", + "ruby", + "go", + "rust", + "typescript", + ]) + .withMessage("Code language must be a supported programming language"), + body("sensitivity") + .optional() + .isIn(["low", "medium", "high"]) + .withMessage("Sensitivity must be low, medium, or high"), + body("includeWebScanning") + .optional() + .isBoolean() + .withMessage("includeWebScanning must be a boolean"), + body("includeAcademicDatabase") + .optional() + .isBoolean() + .withMessage("includeAcademicDatabase must be a boolean"), + body("includeInternalComparison") + .optional() + .isBoolean() + .withMessage("includeInternalComparison must be a boolean"), +]; + +const batchAnalyzeValidation = [ + body("submissions") + .isArray({ min: 1, max: 50 }) + .withMessage("Submissions must be an array with 1-50 items"), + body("submissions.*.submissionId") + .notEmpty() + .withMessage("Submission ID is required for each submission"), + body("submissions.*.content") + .notEmpty() + .withMessage("Content is required for each submission"), + body("submissions.*.contentType") + .isIn(["text", "code", "mixed"]) + .withMessage("Content type must be text, code, or mixed"), + body("settings.sensitivityLevel") + .optional() + .isIn(["low", "medium", "high"]) + .withMessage("Sensitivity level must be low, medium, or high"), + body("settings.minimumSimilarityThreshold") + .optional() + .isFloat({ min: 0, max: 100 }) + .withMessage("Minimum similarity threshold must be between 0 and 100"), +]; + +const updateSettingsValidation = [ + body("sensitivityLevel") + .optional() + .isIn(["low", "medium", "high"]) + .withMessage("Sensitivity level must be low, medium, or high"), + body("minimumSimilarityThreshold") + .optional() + .isFloat({ min: 0, max: 100 }) + .withMessage("Minimum similarity threshold must be between 0 and 100"), + body("autoFlagThreshold") + .optional() + .isFloat({ min: 0, max: 100 }) + .withMessage("Auto flag threshold must be between 0 and 100"), + body("reviewRequiredThreshold") + .optional() + .isFloat({ min: 0, max: 100 }) + .withMessage("Review required threshold must be between 0 and 100"), + body("excludedDomains") + .optional() + .isArray() + .withMessage("Excluded domains must be an array"), + body("trustedSources") + .optional() + .isArray() + .withMessage("Trusted sources must be an array"), +]; + +const appealValidation = [ + body("reportId") + .notEmpty() + .withMessage("Report ID is required") + .isUUID() + .withMessage("Report ID must be a valid UUID"), + body("reason") + .notEmpty() + .withMessage("Appeal reason is required") + .isLength({ min: 10, max: 500 }) + .withMessage("Reason must be between 10 and 500 characters"), + body("explanation") + .notEmpty() + .withMessage("Explanation is required") + .isLength({ min: 20, max: 2000 }) + .withMessage("Explanation must be between 20 and 2000 characters"), + body("evidence") + .optional() + .isArray() + .withMessage("Evidence must be an array"), +]; + +// Routes + +/** + * @route POST /api/plagiarism/analyze + * @desc Analyze a single submission for plagiarism + * @access Private + */ +router.post( + "/analyze", + authenticateToken, + analyzeSubmissionValidation, + handleValidationErrors, + plagiarismController.analyzeSubmission.bind(plagiarismController) as RequestHandler, +); + +/** + * @route POST /api/plagiarism/batch-analyze + * @desc Analyze multiple submissions for plagiarism + * @access Private + */ +router.post( + "/batch-analyze", + authenticateToken, + batchAnalyzeValidation, + handleValidationErrors, + plagiarismController.batchAnalyze.bind(plagiarismController) as RequestHandler, +); + +/** + * @route GET /api/plagiarism/reports/:reportId + * @desc Get plagiarism report by ID + * @access Private + */ +router.get( + "/reports/:reportId", + authenticateToken, + param("reportId").isUUID().withMessage("Report ID must be a valid UUID"), + handleValidationErrors, + plagiarismController.getReport.bind(plagiarismController) as RequestHandler, +); /** - * @openapi - * /api/plagiarism-detection/check: - * post: - * tags: [Plagiarism Detection] - * summary: Check content for plagiarism - * responses: - * '200': - * description: Plagiarism check complete - */ -router.post("/check", plagiarismController.checkPlagiarism); - -/** - * @openapi - * /api/plagiarism-detection/report/{submissionId}: - * get: - * tags: [Plagiarism Detection] - * summary: Get plagiarism report for submission - * parameters: - * - in: path - * name: submissionId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Report retrieved - */ -router.get("/report/:submissionId", plagiarismController.getPlagiarismReport); - -/** - * @openapi - * /api/plagiarism-detection/history/{userId}: - * get: - * tags: [Plagiarism Detection] - * summary: Get plagiarism check history for user - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: History retrieved - */ -router.get("/history/:userId", plagiarismController.getUserHistory); + * @route GET /api/plagiarism/settings + * @desc Get plagiarism detection settings + * @access Private (Admin/Educator) + */ +router.get( + "/settings", + authenticateToken, + requireEducatorOrAdmin, + plagiarismController.getSettings.bind(plagiarismController) as RequestHandler, +); + +/** + * @route PUT /api/plagiarism/settings + * @desc Update plagiarism detection settings + * @access Private (Admin) + */ +router.put( + "/settings", + authenticateToken, + requireAdmin, + updateSettingsValidation, + handleValidationErrors, + plagiarismController.updateSettings.bind(plagiarismController) as RequestHandler, +); + +/** + * @route GET /api/plagiarism/analytics + * @desc Get plagiarism detection analytics + * @access Private (Admin/Educator) + */ +router.get( + "/analytics", + authenticateToken, + requireEducatorOrAdmin, + query("startDate") + .optional() + .isISO8601() + .withMessage("Start date must be a valid date"), + query("endDate") + .optional() + .isISO8601() + .withMessage("End date must be a valid date"), + query("institutionId") + .optional() + .isUUID() + .withMessage("Institution ID must be a valid UUID"), + handleValidationErrors, + plagiarismController.getAnalytics.bind(plagiarismController) as RequestHandler, +); + +/** + * @route POST /api/plagiarism/appeal + * @desc Submit an appeal for plagiarism detection + * @access Private + */ +router.post( + "/appeal", + authenticateToken, + appealValidation, + handleValidationErrors, + plagiarismController.submitAppeal.bind(plagiarismController) as RequestHandler, +); + +/** + * @route GET /api/plagiarism/health + * @desc Health check for plagiarism detection service + * @access Public + */ +router.get("/health", (req, res) => { + res.json({ + status: "healthy", + timestamp: new Date().toISOString(), + service: "plagiarism-detection", + }); +}); export default router; diff --git a/backend/src/routes/prediction.js b/backend/src/routes/prediction.js index d0f9fce..3ddd5a5 100644 --- a/backend/src/routes/prediction.js +++ b/backend/src/routes/prediction.js @@ -1,85 +1,108 @@ -/** - * @openapi - * tags: - * - name: Prediction - * description: AI-powered predictions and forecasting - */ - -const express = require("express"); +const express = require('express'); const router = express.Router(); -const { authenticate } = require("../middleware/auth"); -const predictionController = require("../controllers/predictionController"); - -router.use(authenticate); - -/** - * @openapi - * /api/prediction/student-performance: - * post: - * tags: [Prediction] - * summary: Predict student performance - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Performance prediction generated - */ -router.post("/student-performance", predictionController.predictStudentPerformance); - -/** - * @openapi - * /api/prediction/dropout-risk: - * post: - * tags: [Prediction] - * summary: Predict dropout risk - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Dropout risk prediction generated - */ -router.post("/dropout-risk", predictionController.predictDropoutRisk); - -/** - * @openapi - * /api/prediction/course-completion: - * post: - * tags: [Prediction] - * summary: Predict course completion probability - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Completion prediction generated - */ -router.post("/course-completion", predictionController.predictCourseCompletion); - -/** - * @openapi - * /api/prediction/engagement: - * post: - * tags: [Prediction] - * summary: Predict student engagement levels - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Engagement prediction generated - */ -router.post("/engagement", predictionController.predictEngagement); - -/** - * @openapi - * /api/prediction/learning-style: - * post: - * tags: [Prediction] - * summary: Predict learning style - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Learning style prediction generated - */ -router.post("/learning-style", predictionController.predictLearningStyle); +const PredictionController = require('../controllers/predictionController'); + +// Initialize controller +const predictionController = new PredictionController(); + +// Middleware for validation +const validateStudentData = (req, res, next) => { + const { studentData } = req.body; + + if (!studentData || typeof studentData !== 'object') { + return res.status(400).json({ + success: false, + message: 'Valid student data object is required' + }); + } + + next(); +}; + +const validateBatchData = (req, res, next) => { + const { students } = req.body; + + if (!students || !Array.isArray(students) || students.length === 0) { + return res.status(400).json({ + success: false, + message: 'Valid students array is required' + }); + } + + if (students.length > 100) { + return res.status(400).json({ + success: false, + message: 'Maximum 100 students allowed per batch request' + }); + } + + next(); +}; + +// Prediction routes +router.post('/students/:studentId/predict', + validateStudentData, + predictionController.predictStudentOutcomes.bind(predictionController) +); + +router.post('/batch/predict', + validateBatchData, + predictionController.predictBatchOutcomes.bind(predictionController) +); + +// At-risk student identification +router.post('/at-risk/identify', + validateBatchData, + predictionController.identifyAtRiskStudents.bind(predictionController) +); + +// Intervention recommendations +router.post('/students/:studentId/interventions', + predictionController.generateInterventions.bind(predictionController) +); + +router.put('/students/:studentId/interventions/:interventionId/status', + predictionController.updateInterventionStatus.bind(predictionController) +); + +router.get('/students/:studentId/interventions/effectiveness', + predictionController.getInterventionEffectiveness.bind(predictionController) +); + +// Learning path optimization +router.post('/students/:studentId/learning-path/optimize', + predictionController.optimizeLearningPath.bind(predictionController) +); + +// Model management +router.get('/models/accuracy', + predictionController.getModelAccuracy.bind(predictionController) +); + +router.post('/models/train', + predictionController.trainModels.bind(predictionController) +); + +// Comprehensive analytics +router.post('/students/:studentId/analytics', + validateStudentData, + predictionController.getStudentAnalytics.bind(predictionController) +); + +// Health check +router.get('/health', + predictionController.healthCheck.bind(predictionController) +); + +// Error handling middleware +router.use((error, req, res, next) => { + console.error('Prediction route error:', error); + + res.status(500).json({ + success: false, + message: 'Internal server error in prediction service', + error: process.env.NODE_ENV === 'development' ? error.message : undefined + }); +}); module.exports = router; diff --git a/backend/src/routes/quantum.js b/backend/src/routes/quantum.js index 97a7319..388d51f 100644 --- a/backend/src/routes/quantum.js +++ b/backend/src/routes/quantum.js @@ -1,10 +1,3 @@ -/** - * @openapi - * tags: - * - name: Quantum - * description: Quantum computing services including algorithms, optimization, ML, circuits, and error correction - */ - """ Quantum Computing API Routes RESTful API endpoints for quantum computing services @@ -39,16 +32,7 @@ const handleValidationErrors = (req, res, next) => { // ==================== Quantum Algorithm Integration ==================== -/** - * @openapi - * /api/quantum/providers: - * get: - * tags: [Quantum] - * summary: Get available quantum providers - * responses: - * '200': - * description: Providers retrieved - */ +// Get available quantum providers router.get('/providers', async (req, res) => { try { const providers = quantum_service.get_available_providers(); @@ -74,16 +58,7 @@ router.get('/providers', async (req, res) => { } }); -/** - * @openapi - * /api/quantum/providers/connect: - * post: - * tags: [Quantum] - * summary: Connect to quantum provider - * responses: - * '200': - * description: Connected - */ +// Connect to quantum provider router.post('/providers/connect', [ body('provider').notEmpty().isIn(['ibmq', 'google', 'azure', 'amazon']), body('config').notEmpty().isObject(), @@ -235,16 +210,7 @@ router.post('/optimization/solve', [ } }); -/** - * @openapi - * /api/quantum/optimization/compare: - * post: - * tags: [Quantum] - * summary: Compare quantum optimizers - * responses: - * '200': - * description: Comparison results - */ +// Compare optimizers router.post('/optimization/compare', [ body('problem').notEmpty().isObject(), body('optimizer_names').isArray().notEmpty() @@ -279,16 +245,7 @@ router.post('/optimization/compare', [ // ==================== Quantum Machine Learning ==================== -/** - * @openapi - * /api/quantum/ml/algorithms: - * get: - * tags: [Quantum] - * summary: Get available quantum ML algorithms - * responses: - * '200': - * description: Algorithms retrieved - */ +// Get available ML algorithms router.get('/ml/algorithms', async (req, res) => { try { const algorithms = quantum_ml_service.get_available_algorithms(); @@ -314,16 +271,7 @@ router.get('/ml/algorithms', async (req, res) => { } }); -/** - * @openapi - * /api/quantum/ml/models: - * post: - * tags: [Quantum] - * summary: Create quantum ML model - * responses: - * '200': - * description: Model created - */ +// Create ML model router.post('/ml/models', [ body('model_id').notEmpty(), body('model_type').notEmpty().isIn(['classification', 'regression', 'clustering', 'kernel']), @@ -352,22 +300,7 @@ router.post('/ml/models', [ } }); -/** - * @openapi - * /api/quantum/ml/models/{modelId}/train: - * post: - * tags: [Quantum] - * summary: Train quantum ML model - * parameters: - * - in: path - * name: modelId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Model trained - */ +// Train ML model router.post('/ml/models/:modelId/train', [ param('modelId').notEmpty(), body('algorithm_name').notEmpty(), @@ -378,6 +311,7 @@ router.post('/ml/models/:modelId/train', [ const { modelId } = req.params; const { algorithm_name, features, labels } = req.body; + // Convert to numpy-like arrays const X = new Float32Array(features.flat()); const y = new Float32Array(labels); @@ -396,22 +330,7 @@ router.post('/ml/models/:modelId/train', [ } }); -/** - * @openapi - * /api/quantum/ml/models/{modelId}/predict: - * post: - * tags: [Quantum] - * summary: Make predictions using quantum ML model - * parameters: - * - in: path - * name: modelId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Predictions made - */ +// Make predictions router.post('/ml/models/:modelId/predict', [ param('modelId').notEmpty(), body('features').isArray() @@ -438,16 +357,7 @@ router.post('/ml/models/:modelId/predict', [ // ==================== Quantum Circuit Design ==================== -/** - * @openapi - * /api/quantum/circuits/designers: - * get: - * tags: [Quantum] - * summary: Get available circuit designers - * responses: - * '200': - * description: Designers retrieved - */ +// Get available circuit designers router.get('/circuits/designers', async (req, res) => { try { const designers = quantum_circuit_service.get_available_designers(); @@ -473,16 +383,7 @@ router.get('/circuits/designers', async (req, res) => { } }); -/** - * @openapi - * /api/quantum/circuits/design: - * post: - * tags: [Quantum] - * summary: Design quantum circuit - * responses: - * '200': - * description: Circuit designed - */ +// Design quantum circuit router.post('/circuits/design', [ body('circuit_id').notEmpty(), body('circuit_type').notEmpty().isIn(['feature_map', 'ansatz', 'measurement', 'custom']), @@ -522,22 +423,7 @@ router.post('/circuits/design', [ } }); -/** - * @openapi - * /api/quantum/circuits/{circuitId}/optimize: - * post: - * tags: [Quantum] - * summary: Optimize quantum circuit - * parameters: - * - in: path - * name: circuitId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Circuit optimized - */ +// Optimize circuit router.post('/circuits/:circuitId/optimize', [ param('circuitId').notEmpty(), body('optimization_level').optional().isInt({ min: 0, max: 3 }) @@ -561,16 +447,7 @@ router.post('/circuits/:circuitId/optimize', [ } }); -/** - * @openapi - * /api/quantum/circuits: - * get: - * tags: [Quantum] - * summary: List quantum circuits - * responses: - * '200': - * description: Circuits listed - */ +// List circuits router.get('/circuits', async (req, res) => { try { const circuits = quantum_circuit_service.list_circuits(); @@ -598,16 +475,7 @@ router.get('/circuits', async (req, res) => { // ==================== Quantum Resource Management ==================== -/** - * @openapi - * /api/quantum/resources: - * get: - * tags: [Quantum] - * summary: List quantum resources - * responses: - * '200': - * description: Resources listed - */ +// List resources router.get('/resources', async (req, res) => { try { const { resource_type, status } = req.query; @@ -629,16 +497,7 @@ router.get('/resources', async (req, res) => { } }); -/** - * @openapi - * /api/quantum/resources/utilization: - * get: - * tags: [Quantum] - * summary: Get resource utilization - * responses: - * '200': - * description: Utilization retrieved - */ +// Get resource utilization router.get('/resources/utilization', async (req, res) => { try { const utilization = quantum_resource_manager.get_resource_utilization(); @@ -656,16 +515,7 @@ router.get('/resources/utilization', async (req, res) => { } }); -/** - * @openapi - * /api/quantum/jobs: - * post: - * tags: [Quantum] - * summary: Submit quantum job - * responses: - * '200': - * description: Job submitted - */ +// Submit job router.post('/jobs', [ body('job_id').notEmpty(), body('user_id').notEmpty(), @@ -707,22 +557,7 @@ router.post('/jobs', [ } }); -/** - * @openapi - * /api/quantum/jobs/{jobId}/schedule: - * post: - * tags: [Quantum] - * summary: Schedule quantum job - * parameters: - * - in: path - * name: jobId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Job scheduled - */ +// Schedule job router.post('/jobs/:jobId/schedule', [ param('jobId').notEmpty(), body('scheduler_name').optional().isString() @@ -748,16 +583,7 @@ router.post('/jobs/:jobId/schedule', [ // ==================== Hybrid Computing ==================== -/** - * @openapi - * /api/quantum/hybrid/strategies: - * get: - * tags: [Quantum] - * summary: Get available hybrid computing strategies - * responses: - * '200': - * description: Strategies retrieved - */ +// Get available strategies router.get('/hybrid/strategies', async (req, res) => { try { const strategies = hybrid_computing_service.get_available_strategies(); @@ -783,16 +609,7 @@ router.get('/hybrid/strategies', async (req, res) => { } }); -/** - * @openapi - * /api/quantum/hybrid/execute: - * post: - * tags: [Quantum] - * summary: Execute hybrid computing task - * responses: - * '200': - * description: Task executed - */ +// Execute hybrid task router.post('/hybrid/execute', [ body('task_id').notEmpty(), body('task_type').notEmpty().isIn(['classification', 'regression', 'optimization', 'clustering']), @@ -831,16 +648,7 @@ router.post('/hybrid/execute', [ // ==================== Error Correction ==================== -/** - * @openapi - * /api/quantum/error-correction/codes: - * get: - * tags: [Quantum] - * summary: Get available error correction codes - * responses: - * '200': - * description: Codes retrieved - */ +// Get available error codes router.get('/error-correction/codes', async (req, res) => { try { const codes = quantum_error_correction_service.get_available_codes(); @@ -866,16 +674,7 @@ router.get('/error-correction/codes', async (req, res) => { } }); -/** - * @openapi - * /api/quantum/error-correction/apply: - * post: - * tags: [Quantum] - * summary: Apply error correction to circuit - * responses: - * '200': - * description: Error correction applied - */ +// Apply error correction router.post('/error-correction/apply', [ body('circuit').notEmpty(), body('code_name').notEmpty(), @@ -901,16 +700,7 @@ router.post('/error-correction/apply', [ } }); -/** - * @openapi - * /api/quantum/error-correction/performance: - * get: - * tags: [Quantum] - * summary: Get error correction performance - * responses: - * '200': - * description: Performance retrieved - */ +// Get error correction performance router.get('/error-correction/performance', async (req, res) => { try { const performance = quantum_error_correction_service.analyze_performance(); @@ -930,16 +720,6 @@ router.get('/error-correction/performance', async (req, res) => { // ==================== Health Check ==================== -/** - * @openapi - * /api/quantum/health: - * get: - * tags: [Quantum] - * summary: Health check - * responses: - * '200': - * description: Health status - */ router.get('/health', async (req, res) => { try { const health = { diff --git a/backend/src/routes/quantumEncryption.js b/backend/src/routes/quantumEncryption.js index 306375e..ec6bded 100644 --- a/backend/src/routes/quantumEncryption.js +++ b/backend/src/routes/quantumEncryption.js @@ -1,10 +1,3 @@ -/** - * @openapi - * tags: - * - name: Quantum Encryption - * description: Quantum-resistant encryption, key management, and security services - */ - /** * Quantum Encryption API Routes * RESTful endpoints for quantum-resistant encryption services @@ -33,27 +26,9 @@ const validateRequest = (req, res, next) => { }; /** - * @openapi - * /api/quantum-encryption/keys/generate: - * post: - * tags: [Quantum Encryption] - * summary: Generate quantum-resistant key pair - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * algorithm: - * type: string - * default: CRYSTALS_KYBER - * securityLevel: - * type: integer - * default: 4 - * responses: - * '200': - * description: Key pair generated + * @route POST /api/quantum-encryption/keys/generate + * @desc Generate quantum-resistant key pair + * @access Private */ router.post('/keys/generate', validateRequest, async (req, res) => { try { @@ -81,20 +56,9 @@ router.post('/keys/generate', validateRequest, async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/keys/{keyId}: - * get: - * tags: [Quantum Encryption] - * summary: Get public key by ID - * parameters: - * - in: path - * name: keyId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Public key retrieved + * @route GET /api/quantum-encryption/keys/:keyId + * @desc Get public key by ID + * @access Private */ router.get('/keys/:keyId', async (req, res) => { try { @@ -117,14 +81,9 @@ router.get('/keys/:keyId', async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/keys: - * get: - * tags: [Quantum Encryption] - * summary: List all active keys - * responses: - * '200': - * description: Keys listed + * @route GET /api/quantum-encryption/keys + * @desc List all active keys + * @access Private */ router.get('/keys', async (req, res) => { try { @@ -152,28 +111,9 @@ router.get('/keys', async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/encrypt: - * post: - * tags: [Quantum Encryption] - * summary: Encrypt data using quantum-resistant encryption - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * data: - * type: string - * keyId: - * type: string - * algorithm: - * type: string - * default: CRYSTALS_KYBER - * responses: - * '200': - * description: Data encrypted + * @route POST /api/quantum-encryption/encrypt + * @desc Encrypt data using quantum-resistant encryption + * @access Private */ router.post('/encrypt', validateRequest, async (req, res) => { try { @@ -196,6 +136,7 @@ router.post('/encrypt', validateRequest, async (req, res) => { let encryptedPackage; if (keyId) { + // Use existing key const publicKey = await QuantumKeyManagement.getPublicKey(keyId); encryptedPackage = await QuantumEncryption.encrypt( data, @@ -204,6 +145,7 @@ router.post('/encrypt', validateRequest, async (req, res) => { metadata ); } else { + // Use hybrid encryption encryptedPackage = await HybridEncryption.encrypt(data, { compatibilityMode, algorithm, @@ -228,25 +170,9 @@ router.post('/encrypt', validateRequest, async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/decrypt: - * post: - * tags: [Quantum Encryption] - * summary: Decrypt data using quantum-resistant encryption - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * encryptedPackage: - * type: object - * keyId: - * type: string - * responses: - * '200': - * description: Data decrypted + * @route POST /api/quantum-encryption/decrypt + * @desc Decrypt data using quantum-resistant encryption + * @access Private */ router.post('/decrypt', validateRequest, async (req, res) => { try { @@ -262,6 +188,7 @@ router.post('/decrypt', validateRequest, async (req, res) => { let decryptedData; if (keyId) { + // Use specific key const privateKey = await QuantumKeyManagement.getPrivateKey(keyId, { operation: 'decryption' }); @@ -272,6 +199,7 @@ router.post('/decrypt', validateRequest, async (req, res) => { encryptedPackage.algorithm ); } else { + // Auto-detect and decrypt decryptedData = await HybridEncryption.decrypt(encryptedPackage, { autoDetect }); @@ -293,28 +221,9 @@ router.post('/decrypt', validateRequest, async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/sign: - * post: - * tags: [Quantum Encryption] - * summary: Sign data using quantum-resistant digital signature - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * data: - * type: string - * keyId: - * type: string - * algorithm: - * type: string - * default: CRYSTALS_DILITHIUM - * responses: - * '200': - * description: Data signed + * @route POST /api/quantum-encryption/sign + * @desc Sign data using quantum-resistant digital signature + * @access Private */ router.post('/sign', validateRequest, async (req, res) => { try { @@ -353,27 +262,9 @@ router.post('/sign', validateRequest, async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/verify: - * post: - * tags: [Quantum Encryption] - * summary: Verify quantum-resistant digital signature - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * signedData: - * type: object - * keyId: - * type: string - * algorithm: - * type: string - * responses: - * '200': - * description: Signature verification result + * @route POST /api/quantum-encryption/verify + * @desc Verify quantum-resistant digital signature + * @access Private */ router.post('/verify', validateRequest, async (req, res) => { try { @@ -410,14 +301,9 @@ router.post('/verify', validateRequest, async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/migrate: - * post: - * tags: [Quantum Encryption] - * summary: Migrate encrypted data to quantum-resistant format - * responses: - * '200': - * description: Migration completed + * @route POST /api/quantum-encryption/migrate + * @desc Migrate encrypted data to quantum-resistant format + * @access Private */ router.post('/migrate', validateRequest, async (req, res) => { try { @@ -456,20 +342,9 @@ router.post('/migrate', validateRequest, async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/keys/{keyId}/rotate: - * post: - * tags: [Quantum Encryption] - * summary: Rotate cryptographic key - * parameters: - * - in: path - * name: keyId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Key rotated + * @route POST /api/quantum-encryption/keys/:keyId/rotate + * @desc Rotate cryptographic key + * @access Private */ router.post('/keys/:keyId/rotate', async (req, res) => { try { @@ -497,20 +372,9 @@ router.post('/keys/:keyId/rotate', async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/keys/{keyId}/revoke: - * post: - * tags: [Quantum Encryption] - * summary: Revoke cryptographic key - * parameters: - * - in: path - * name: keyId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Key revoked + * @route POST /api/quantum-encryption/keys/:keyId/revoke + * @desc Revoke cryptographic key + * @access Private */ router.post('/keys/:keyId/revoke', async (req, res) => { try { @@ -534,14 +398,9 @@ router.post('/keys/:keyId/revoke', async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/health: - * get: - * tags: [Quantum Encryption] - * summary: Get quantum encryption system health status - * responses: - * '200': - * description: Health status retrieved + * @route GET /api/quantum-encryption/health + * @desc Get quantum encryption system health status + * @access Private */ router.get('/health', async (req, res) => { try { @@ -562,14 +421,9 @@ router.get('/health', async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/agility-test: - * post: - * tags: [Quantum Encryption] - * summary: Perform cryptographic agility test - * responses: - * '200': - * description: Agility test completed + * @route POST /api/quantum-encryption/agility-test + * @desc Perform cryptographic agility test + * @access Private */ router.post('/agility-test', validateRequest, async (req, res) => { try { @@ -593,14 +447,9 @@ router.post('/agility-test', validateRequest, async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/compatibility-test: - * post: - * tags: [Quantum Encryption] - * summary: Perform encryption compatibility test - * responses: - * '200': - * description: Compatibility test completed + * @route POST /api/quantum-encryption/compatibility-test + * @desc Perform encryption compatibility test + * @access Private */ router.post('/compatibility-test', validateRequest, async (req, res) => { try { @@ -624,23 +473,9 @@ router.post('/compatibility-test', validateRequest, async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/security-analysis: - * post: - * tags: [Quantum Encryption] - * summary: Analyze encryption security level - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * encryptedPackage: - * type: object - * responses: - * '200': - * description: Security analysis completed + * @route POST /api/quantum-encryption/security-analysis + * @desc Analyze encryption security level + * @access Private */ router.post('/security-analysis', validateRequest, async (req, res) => { try { @@ -671,14 +506,9 @@ router.post('/security-analysis', validateRequest, async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/threats/alerts: - * get: - * tags: [Quantum Encryption] - * summary: Get active security alerts - * responses: - * '200': - * description: Alerts retrieved + * @route GET /api/quantum-encryption/threats/alerts + * @desc Get active security alerts + * @access Private */ router.get('/threats/alerts', async (req, res) => { try { @@ -707,20 +537,9 @@ router.get('/threats/alerts', async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/threats/alerts/{alertId}/acknowledge: - * post: - * tags: [Quantum Encryption] - * summary: Acknowledge security alert - * parameters: - * - in: path - * name: alertId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Alert acknowledged + * @route POST /api/quantum-encryption/threats/alerts/:alertId/acknowledge + * @desc Acknowledge security alert + * @access Private */ router.post('/threats/alerts/:alertId/acknowledge', async (req, res) => { try { @@ -745,14 +564,9 @@ router.post('/threats/alerts/:alertId/acknowledge', async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/threats/scan: - * post: - * tags: [Quantum Encryption] - * summary: Perform threat scan - * responses: - * '200': - * description: Threat scan completed + * @route POST /api/quantum-encryption/threats/scan + * @desc Perform threat scan + * @access Private */ router.post('/threats/scan', async (req, res) => { try { @@ -774,14 +588,9 @@ router.post('/threats/scan', async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/migration/plans: - * get: - * tags: [Quantum Encryption] - * summary: List migration plans - * responses: - * '200': - * description: Migration plans listed + * @route GET /api/quantum-encryption/migration/plans + * @desc List migration plans + * @access Private */ router.get('/migration/plans', async (req, res) => { try { @@ -810,14 +619,9 @@ router.get('/migration/plans', async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/migration/plans: - * post: - * tags: [Quantum Encryption] - * summary: Create migration plan - * responses: - * '200': - * description: Migration plan created + * @route POST /api/quantum-encryption/migration/plans + * @desc Create migration plan + * @access Private */ router.post('/migration/plans', validateRequest, async (req, res) => { try { @@ -855,20 +659,9 @@ router.post('/migration/plans', validateRequest, async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/migration/plans/{migrationId}/execute: - * post: - * tags: [Quantum Encryption] - * summary: Execute migration - * parameters: - * - in: path - * name: migrationId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Migration executed + * @route POST /api/quantum-encryption/migration/plans/:migrationId/execute + * @desc Execute migration + * @access Private */ router.post('/migration/plans/:migrationId/execute', async (req, res) => { try { @@ -893,14 +686,9 @@ router.post('/migration/plans/:migrationId/execute', async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/migration/readiness: - * get: - * tags: [Quantum Encryption] - * summary: Check migration readiness - * responses: - * '200': - * description: Migration readiness retrieved + * @route GET /api/quantum-encryption/migration/readiness + * @desc Check migration readiness + * @access Private */ router.get('/migration/readiness', async (req, res) => { try { @@ -921,14 +709,9 @@ router.get('/migration/readiness', async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/audit: - * post: - * tags: [Quantum Encryption] - * summary: Perform security audit - * responses: - * '200': - * description: Security audit completed + * @route POST /api/quantum-encryption/audit + * @desc Perform security audit + * @access Private */ router.post('/audit', validateRequest, async (req, res) => { try { @@ -962,20 +745,9 @@ router.post('/audit', validateRequest, async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/compliance/{framework}: - * get: - * tags: [Quantum Encryption] - * summary: Get compliance report - * parameters: - * - in: path - * name: framework - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Compliance report retrieved + * @route GET /api/quantum-encryption/compliance/:framework + * @desc Get compliance report + * @access Private */ router.get('/compliance/:framework', async (req, res) => { try { @@ -1002,14 +774,9 @@ router.get('/compliance/:framework', async (req, res) => { }); /** - * @openapi - * /api/quantum-encryption/algorithms: - * get: - * tags: [Quantum Encryption] - * summary: Get supported quantum-resistant algorithms - * responses: - * '200': - * description: Algorithms retrieved + * @route GET /api/quantum-encryption/algorithms + * @desc Get supported quantum-resistant algorithms + * @access Public */ router.get('/algorithms', (req, res) => { try { diff --git a/backend/src/routes/quizRoutes.ts b/backend/src/routes/quizRoutes.ts index 8f74fce..4e225f4 100644 --- a/backend/src/routes/quizRoutes.ts +++ b/backend/src/routes/quizRoutes.ts @@ -1,10 +1,3 @@ -/** - * @openapi - * tags: - * - name: Quizzes - * description: Quizzes endpoint - */ - import { Router } from "express"; import quizController from "../controllers/quizController"; import { requirePermission } from "../middleware/rbac"; @@ -24,126 +17,30 @@ import { const router: Router = Router(); // Quiz CRUD endpoints -/** - * @openapi - * /api/quizzes: - * post: - * tags: [Quizzes] - * summary: Create a new quiz - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * title: - * type: string - * questions: - * type: array - * timeLimit: - * type: integer - * responses: - * '201': - * description: Quiz created - * '400': - * description: Validation error - * $ref: '#/components/schemas/Error' - */ router.post( "/", requirePermission(PERMISSIONS.QUIZ_CREATE), validate(createQuizSchema), quizController.createQuiz, ); -/** - * @openapi - * /api/quizzes: - * get: - * tags: [Quizzes] - * summary: Get all quizzes - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Quizzes retrieved - */ router.get( "/", requirePermission(PERMISSIONS.QUIZ_READ), validate(getQuizzesQuerySchema), quizController.getQuizzes, ); -/** - * @openapi - * /api/quizzes/{id}: - * get: - * tags: [Quizzes] - * summary: Get quiz by ID - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Quiz retrieved - * '404': - * description: Quiz not found - */ router.get( "/:id", requirePermission(PERMISSIONS.QUIZ_READ), validate(quizIdParamSchema), quizController.getQuizById, ); -/** - * @openapi - * /api/quizzes/{id}: - * put: - * tags: [Quizzes] - * summary: Update quiz - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Quiz updated - */ router.put( "/:id", requirePermission(PERMISSIONS.QUIZ_UPDATE), validate(updateQuizSchema), quizController.updateQuiz, ); -/** - * @openapi - * /api/quizzes/{id}: - * delete: - * tags: [Quizzes] - * summary: Delete quiz - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Quiz deleted - */ router.delete( "/:id", requirePermission(PERMISSIONS.QUIZ_DELETE), @@ -151,24 +48,7 @@ router.delete( quizController.deleteQuiz, ); -/** - * @openapi - * /api/quizzes/{id}/publish: - * post: - * tags: [Quizzes] - * summary: Toggle quiz publish status - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Publish status toggled - */ +// Quiz publishing router.post( "/:id/publish", requirePermission(PERMISSIONS.QUIZ_UPDATE), @@ -176,129 +56,31 @@ router.post( quizController.toggleQuizPublish, ); -/** - * @openapi - * /api/quizzes/{id}/submit: - * post: - * tags: [Quizzes] - * summary: Submit quiz answers - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: string - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * answers: - * type: array - * responses: - * '200': - * description: Submission received - */ +// Quiz submission and grading router.post( "/:id/submit", requirePermission(PERMISSIONS.PROGRESS_TRACK), validate(submitQuizSchema), quizController.submitQuiz, ); -/** - * @openapi - * /api/quizzes/{id}/submission: - * get: - * tags: [Quizzes] - * summary: Get user submission for quiz - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: string - * responses: - * '200': - * description: User submission retrieved - */ router.get( "/:id/submission", requirePermission(PERMISSIONS.PROGRESS_TRACK), validate(submissionQuerySchema), quizController.getUserSubmission, ); -/** - * @openapi - * /api/quizzes/{id}/results: - * get: - * tags: [Quizzes] - * summary: Get quiz results - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Results retrieved - */ router.get( "/:id/results", requirePermission(PERMISSIONS.PROGRESS_TRACK), validate(resultsQuerySchema), quizController.getQuizResults, ); -/** - * @openapi - * /api/quizzes/{id}/statistics: - * get: - * tags: [Quizzes] - * summary: Get quiz statistics - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Statistics retrieved - */ router.get( "/:id/statistics", requirePermission(PERMISSIONS.ANALYTICS_READ), validate(quizIdParamSchema), quizController.getQuizStatistics, ); -/** - * @openapi - * /api/quizzes/{id}/grading-statistics: - * get: - * tags: [Quizzes] - * summary: Get grading statistics - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Grading statistics retrieved - */ router.get( "/:id/grading-statistics", requirePermission(PERMISSIONS.COURSE_GRADE), diff --git a/backend/src/routes/rbacRoutes.js b/backend/src/routes/rbacRoutes.js index 67821ce..e8e96d7 100644 --- a/backend/src/routes/rbacRoutes.js +++ b/backend/src/routes/rbacRoutes.js @@ -1,182 +1,54 @@ -/** - * @openapi - * tags: - * - name: RBAC - * description: Role-based access control management - */ - -const express = require("express"); +const express = require('express'); const router = express.Router(); -const { authenticate, authorize } = require("../middleware/auth"); -const rbacController = require("../controllers/rbacController"); - -router.use(authenticate, authorize("admin")); - -/** - * @openapi - * /api/rbac/roles: - * get: - * tags: [RBAC] - * summary: List all roles - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Roles listed - * post: - * tags: [RBAC] - * summary: Create new role - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Role created - */ -router.get("/roles", rbacController.listRoles); -router.post("/roles", rbacController.createRole); - -/** - * @openapi - * /api/rbac/roles/{roleId}: - * get: - * tags: [RBAC] - * summary: Get role by ID - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: roleId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Role retrieved - * put: - * tags: [RBAC] - * summary: Update role - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: roleId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Role updated - * delete: - * tags: [RBAC] - * summary: Delete role - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: roleId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Role deleted - */ -router.get("/roles/:roleId", rbacController.getRole); -router.put("/roles/:roleId", rbacController.updateRole); -router.delete("/roles/:roleId", rbacController.deleteRole); +const rbacController = require('../controllers/rbacController'); +const { requirePermission, protectRoleEscalation } = require('../middleware/rbac'); +const { PERMISSIONS } = require('../utils/roles'); +// Assuming we have a standard auth middleware already to populate req.user +// For now, using a placeholder requirement +const { verifyToken } = require('../middleware/ipfsAuth'); /** - * @openapi - * /api/rbac/users/{userId}/roles: - * get: - * tags: [RBAC] - * summary: Get user roles - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: User roles retrieved - * post: - * tags: [RBAC] - * summary: Assign role to user - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Role assigned + * Placeholder auth middleware for RBAC routes + * Ideally, there should be a global auth middleware */ -router.get("/users/:userId/roles", rbacController.getUserRoles); -router.post("/users/:userId/roles", rbacController.assignRole); +const rbacAuth = (req, res, next) => { + const authHeader = req.headers.authorization; + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return res.status(401).json({ success: false, message: 'Authentication required' }); + } + const token = authHeader.substring(7); + try { + req.user = verifyToken(token); + next(); + } catch (err) { + return res.status(401).json({ success: false, message: 'Invalid token' }); + } +}; /** - * @openapi - * /api/rbac/users/{userId}/roles/{roleId}: - * delete: - * tags: [RBAC] - * summary: Remove role from user - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * - in: path - * name: roleId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Role removed + * RBAC Management Routes + * Only admins can access these */ -router.delete("/users/:userId/roles/:roleId", rbacController.removeRole); /** - * @openapi - * /api/rbac/permissions: - * get: - * tags: [RBAC] - * summary: List all permissions - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Permissions listed + * Assign a role to a user + * POST /api/rbac/assign-role */ -router.get("/permissions", rbacController.listPermissions); +router.post('/assign-role', + rbacAuth, + requirePermission(PERMISSIONS.USER_ASSIGN_ROLE), + protectRoleEscalation, + rbacController.assignRole +); /** - * @openapi - * /api/rbac/roles/{roleId}/permissions: - * put: - * tags: [RBAC] - * summary: Update role permissions - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: roleId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Permissions updated + * Get all available roles and permissions (Admin/Auditor only) + * GET /api/rbac/permissions */ -router.put("/roles/:roleId/permissions", rbacController.updateRolePermissions); +router.get('/permissions', + rbacAuth, + requirePermission(PERMISSIONS.ADMIN_PANEL), + rbacController.getAvailablePermissions +); module.exports = router; diff --git a/backend/src/routes/recommendations.js b/backend/src/routes/recommendations.js index 7170cd7..deeafae 100644 --- a/backend/src/routes/recommendations.js +++ b/backend/src/routes/recommendations.js @@ -1,75 +1,745 @@ /** - * @openapi - * tags: - * - name: Recommendations - * description: Personalized content recommendations + * Recommendation API Routes + * RESTful endpoints for course recommendations and ML services */ -const express = require("express"); +const express = require('express'); const router = express.Router(); -const { authenticate } = require("../middleware/auth"); -const recommendationController = require("../controllers/recommendationController"); - -router.use(authenticate); - -/** - * @openapi - * /api/recommendations/{userId}: - * get: - * tags: [Recommendations] - * summary: Get personalized recommendations for user - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Recommendations retrieved - */ -router.get("/:userId", recommendationController.getRecommendations); - -/** - * @openapi - * /api/recommendations/{userId}/similar: - * get: - * tags: [Recommendations] - * summary: Get similar content recommendations - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Similar content retrieved - */ -router.get("/:userId/similar", recommendationController.getSimilarContent); - -/** - * @openapi - * /api/recommendations/{userId}/trending: - * get: - * tags: [Recommendations] - * summary: Get trending content for user - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Trending content retrieved - */ -router.get("/:userId/trending", recommendationController.getTrendingContent); +const RecommendationEngine = require('../services/recommendationEngine'); +const UserBehaviorTracker = require('../services/userBehaviorTracker'); +const ABTestingFramework = require('../services/abTestingFramework'); + +// Middleware for request validation +const validateRequest = (req, res, next) => { + try { + if (!req.body && req.method !== 'GET') { + return res.status(400).json({ error: 'Request body is required' }); + } + next(); + } catch (error) { + res.status(400).json({ error: 'Invalid request format' }); + } +}; + +/** + * @route GET /api/recommendations/health + * @desc Check recommendation engine health + * @access Public + */ +router.get('/health', async (req, res) => { + try { + const health = await RecommendationEngine.healthCheck(); + + res.json({ + success: true, + data: health + }); + + } catch (error) { + console.error('Health check error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * @route GET /api/recommendations/user/:userId + * @desc Get personalized recommendations for a user + * @access Private + */ +router.get('/user/:userId', async (req, res) => { + try { + const { userId } = req.params; + const { + count = 10, + algorithm = 'hybrid', + includeExplanations = true, + category = null, + difficulty = null + } = req.query; + + const context = {}; + if (category) context.category = category; + if (difficulty) context.difficulty = difficulty; + + const recommendations = await RecommendationEngine.getRecommendations(userId, { + count: parseInt(count), + algorithm, + includeExplanations: includeExplanations === 'true', + context + }); + + // Track recommendation request + await UserBehaviorTracker.trackRecommendationRequest(userId, { + algorithm, + count: parseInt(count), + context + }); + + res.json({ + success: true, + data: recommendations + }); + + } catch (error) { + console.error('Get recommendations error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * @route POST /api/recommendations/user/:userId + * @desc Get personalized recommendations with advanced options + * @access Private + */ +router.post('/user/:userId', validateRequest, async (req, res) => { + try { + const { userId } = req.params; + const { + count = 10, + algorithm = 'hybrid', + includeExplanations = true, + context = {}, + filters = {} + } = req.body; + + const recommendations = await RecommendationEngine.getRecommendations(userId, { + count, + algorithm, + includeExplanations, + context: { ...context, ...filters } + }); + + // Track recommendation request + await UserBehaviorTracker.trackRecommendationRequest(userId, { + algorithm, + count, + context: { ...context, ...filters } + }); + + res.json({ + success: true, + data: recommendations + }); + + } catch (error) { + console.error('Get recommendations error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * @route GET /api/recommendations/similar/:courseId + * @desc Get courses similar to a given course + * @access Public + */ +router.get('/similar/:courseId', async (req, res) => { + try { + const { courseId } = req.params; + const { count = 10, algorithm = 'content_based' } = req.query; + + const similarCourses = await RecommendationEngine.getSimilarCourses(courseId, { + count: parseInt(count), + algorithm + }); + + res.json({ + success: true, + data: similarCourses + }); + + } catch (error) { + console.error('Get similar courses error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * @route GET /api/recommendations/popular + * @desc Get popular courses + * @access Public + */ +router.get('/popular', async (req, res) => { + try { + const { + count = 20, + category = null, + timeRange = '7d' + } = req.query; + + const popularCourses = await RecommendationEngine.getPopularCourses({ + count: parseInt(count), + category, + timeRange + }); + + res.json({ + success: true, + data: popularCourses + }); + + } catch (error) { + console.error('Get popular courses error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * @route GET /api/recommendations/trending + * @desc Get trending courses + * @access Public + */ +router.get('/trending', async (req, res) => { + try { + const { + count = 20, + category = null, + timeRange = '24h' + } = req.query; + + const trendingCourses = await RecommendationEngine.getTrendingCourses({ + count: parseInt(count), + category, + timeRange + }); + + res.json({ + success: true, + data: trendingCourses + }); + + } catch (error) { + console.error('Get trending courses error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * @route POST /api/recommendations/user/:userId/profile + * @desc Update user profile with new interactions + * @access Private + */ +router.post('/user/:userId/profile', validateRequest, async (req, res) => { + try { + const { userId } = req.params; + const { interactions } = req.body; + + if (!interactions || !Array.isArray(interactions)) { + return res.status(400).json({ + success: false, + error: 'Interactions array is required' + }); + } + + const result = await RecommendationEngine.updateUserProfile(userId, interactions); + + // Track profile update + await UserBehaviorTracker.trackProfileUpdate(userId, { + interactionCount: interactions.length, + timestamp: new Date().toISOString() + }); + + res.json({ + success: true, + data: result, + message: 'User profile updated successfully' + }); + + } catch (error) { + console.error('Update user profile error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * @route GET /api/recommendations/user/:userId/explanation/:courseId + * @desc Get explanation for why a course is recommended + * @access Private + */ +router.get('/user/:userId/explanation/:courseId', async (req, res) => { + try { + const { userId, courseId } = req.params; + + const explanation = await RecommendationEngine.getRecommendationExplanation(userId, courseId); + + res.json({ + success: true, + data: explanation + }); + + } catch (error) { + console.error('Get explanation error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * @route POST /api/recommendations/user/:userId/interaction + * @desc Track user interaction with a course + * @access Private + */ +router.post('/user/:userId/interaction', validateRequest, async (req, res) => { + try { + const { userId } = req.params; + const { + courseId, + interactionType, // 'view', 'like', 'enroll', 'complete', 'rate' + rating = null, + duration = null, + metadata = {} + } = req.body; + + if (!courseId || !interactionType) { + return res.status(400).json({ + success: false, + error: 'Course ID and interaction type are required' + }); + } + + const interaction = { + user_id: userId, + course_id: courseId, + interaction_type: interactionType, + rating: rating, + duration: duration, + timestamp: new Date().toISOString(), + metadata: metadata + }; + + // Track interaction + await UserBehaviorTracker.trackInteraction(interaction); + + res.json({ + success: true, + message: 'Interaction tracked successfully' + }); + + } catch (error) { + console.error('Track interaction error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * @route POST /api/recommendations/models/train + * @desc Train recommendation models + * @access Private + */ +router.post('/models/train', validateRequest, async (req, res) => { + try { + const { + algorithms = ['collaborative', 'content_based'], + forceRetrain = false + } = req.body; + + const result = await RecommendationEngine.trainModels({ + algorithms, + forceRetrain + }); + + res.json({ + success: true, + data: result, + message: 'Model training completed' + }); + + } catch (error) { + console.error('Train models error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * @route POST /api/recommendations/models/evaluate + * @desc Evaluate model performance + * @access Private + */ +router.post('/models/evaluate', validateRequest, async (req, res) => { + try { + const { + algorithms = ['collaborative', 'content_based'], + metrics = ['precision', 'recall', 'ndcg'] + } = req.body; + + const result = await RecommendationEngine.evaluateModels({ + algorithms, + metrics + }); + + res.json({ + success: true, + data: result + }); + + } catch (error) { + console.error('Evaluate models error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * @route GET /api/recommendations/statistics + * @desc Get recommendation system statistics + * @access Private + */ +router.get('/statistics', async (req, res) => { + try { + const { timeRange = '24h' } = req.query; + + const statistics = await RecommendationEngine.getStatistics(timeRange); + + res.json({ + success: true, + data: statistics + }); + + } catch (error) { + console.error('Get statistics error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * @route GET /api/recommendations/metrics + * @desc Get system metrics + * @access Private + */ +router.get('/metrics', async (req, res) => { + try { + const metrics = await RecommendationEngine.getMetrics(); + + res.json({ + success: true, + data: metrics + }); + + } catch (error) { + console.error('Get metrics error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * @route POST /api/recommendations/ab-test/assign + * @desc Assign user to A/B test group + * @access Private + */ +router.post('/ab-test/assign', validateRequest, async (req, res) => { + try { + const { userId, testName, variants } = req.body; + + if (!userId || !testName || !variants) { + return res.status(400).json({ + success: false, + error: 'User ID, test name, and variants are required' + }); + } + + const assignment = await ABTestingFramework.assignUserToTest(userId, testName, variants); + + res.json({ + success: true, + data: assignment + }); + + } catch (error) { + console.error('A/B test assignment error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * @route POST /api/recommendations/ab-test/track + * @desc Track A/B test event + * @access Private + */ +router.post('/ab-test/track', validateRequest, async (req, res) => { + try { + const { userId, testName, event, value } = req.body; + + if (!userId || !testName || !event) { + return res.status(400).json({ + success: false, + error: 'User ID, test name, and event are required' + }); + } + + await ABTestingFramework.trackEvent(userId, testName, event, value); + + res.json({ + success: true, + message: 'A/B test event tracked successfully' + }); + + } catch (error) { + console.error('A/B test tracking error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * @route GET /api/recommendations/ab-test/results/:testName + * @desc Get A/B test results + * @access Private + */ +router.get('/ab-test/results/:testName', async (req, res) => { + try { + const { testName } = req.params; + + const results = await ABTestingFramework.getTestResults(testName); + + res.json({ + success: true, + data: results + }); + + } catch (error) { + console.error('Get A/B test results error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * @route POST /api/recommendations/cache/clear + * @desc Clear recommendation cache + * @access Private + */ +router.post('/cache/clear', validateRequest, async (req, res) => { + try { + const { userId = null } = req.body; + + if (userId) { + await RecommendationEngine.clearUserCache(userId); + } else { + await RecommendationEngine.clearAllCache(); + } + + res.json({ + success: true, + message: userId ? `Cache cleared for user ${userId}` : 'All cache cleared' + }); + + } catch (error) { + console.error('Clear cache error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * @route GET /api/recommendations/algorithms + * @desc Get available recommendation algorithms + * @access Public + */ +router.get('/algorithms', (req, res) => { + try { + const algorithms = { + collaborative: { + name: 'Collaborative Filtering', + description: 'Recommends courses based on similar users\' preferences', + variants: ['user_based', 'item_based', 'matrix_factorization', 'implicit', 'lightfm'] + }, + content_based: { + name: 'Content-Based Filtering', + description: 'Recommends courses based on content similarity to user preferences', + variants: ['tfidf', 'semantic', 'hybrid'] + }, + hybrid: { + name: 'Hybrid Approach', + description: 'Combines collaborative and content-based methods', + variants: ['weighted', 'switching', 'cascade'] + } + }; + + res.json({ + success: true, + data: algorithms + }); + + } catch (error) { + console.error('Get algorithms error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * @route GET /api/recommendations/categories + * @desc Get available course categories + * @access Public + */ +router.get('/categories', (req, res) => { + try { + const categories = [ + 'Programming', + 'Data Science', + 'Web Development', + 'Mobile Development', + 'AI/ML', + 'Cloud Computing', + 'DevOps', + 'Cybersecurity', + 'Blockchain', + 'Game Development', + 'Design', + 'Business', + 'Marketing', + 'Photography', + 'Music' + ]; + + res.json({ + success: true, + data: categories + }); + + } catch (error) { + console.error('Get categories error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * @route POST /api/recommendations/batch + * @desc Get recommendations for multiple users + * @access Private + */ +router.post('/batch', validateRequest, async (req, res) => { + try { + const { userIds, options = {} } = req.body; + + if (!userIds || !Array.isArray(userIds)) { + return res.status(400).json({ + success: false, + error: 'User IDs array is required' + }); + } + + const results = {}; + + // Process in parallel with concurrency limit + const concurrencyLimit = 10; + const chunks = []; + + for (let i = 0; i < userIds.length; i += concurrencyLimit) { + chunks.push(userIds.slice(i, i + concurrencyLimit)); + } + + for (const chunk of chunks) { + const promises = chunk.map(async (userId) => { + try { + const recommendations = await RecommendationEngine.getRecommendations(userId, options); + return { userId, recommendations }; + } catch (error) { + return { userId, error: error.message }; + } + }); + + const chunkResults = await Promise.all(promises); + chunkResults.forEach(result => { + results[result.userId] = result.recommendations || { error: result.error }; + }); + } + + res.json({ + success: true, + data: { + results, + totalUsers: userIds.length, + processedUsers: Object.keys(results).length + } + }); + + } catch (error) { + console.error('Batch recommendations error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * @route GET /api/recommendations/user/:userId/history + * @desc Get user's recommendation history + * @access Private + */ +router.get('/user/:userId/history', async (req, res) => { + try { + const { userId } = req.params; + const { limit = 50, offset = 0 } = req.query; + + const history = await UserBehaviorTracker.getRecommendationHistory(userId, { + limit: parseInt(limit), + offset: parseInt(offset) + }); + + res.json({ + success: true, + data: history + }); + + } catch (error) { + console.error('Get recommendation history error:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); module.exports = router; diff --git a/backend/src/routes/search.js b/backend/src/routes/search.js index cff3193..f31f369 100644 --- a/backend/src/routes/search.js +++ b/backend/src/routes/search.js @@ -1,124 +1,162 @@ -/** - * @openapi - * tags: - * - name: Search - * description: Platform search functionality - */ - -const express = require("express"); -const router = express.Router(); -const { authenticate } = require("../middleware/auth"); -const searchController = require("../controllers/searchController"); - -/** - * @openapi - * /api/search: - * get: - * tags: [Search] - * summary: Search across platform - * security: - * - bearerAuth: [] - * parameters: - * - in: query - * name: q - * schema: - * type: string - * description: Search query - * - in: query - * name: type - * schema: - * type: string - * description: Content type filter - * responses: - * '200': - * description: Search results - */ -router.get("/", authenticate, searchController.search); - -/** - * @openapi - * /api/search/autocomplete: - * get: - * tags: [Search] - * summary: Autocomplete search - * security: - * - bearerAuth: [] - * parameters: - * - in: query - * name: q - * schema: - * type: string - * description: Search prefix - * responses: - * '200': - * description: Autocomplete suggestions - */ -router.get("/autocomplete", authenticate, searchController.autocomplete); - -/** - * @openapi - * /api/search/advanced: - * post: - * tags: [Search] - * summary: Advanced search with filters - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Advanced search results - */ -router.post("/advanced", authenticate, searchController.advancedSearch); - -/** - * @openapi - * /api/search/trending: - * get: - * tags: [Search] - * summary: Get trending searches - * responses: - * '200': - * description: Trending searches - */ -router.get("/trending", searchController.getTrending); - -/** - * @openapi - * /api/search/suggestions: - * get: - * tags: [Search] - * summary: Get search suggestions - * responses: - * '200': - * description: Suggestions retrieved - */ -router.get("/suggestions", searchController.getSuggestions); - -/** - * @openapi - * /api/search/history: - * get: - * tags: [Search] - * summary: Get search history - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Search history - */ -router.get("/history", authenticate, searchController.getSearchHistory); - -/** - * @openapi - * /api/search/history: - * delete: - * tags: [Search] - * summary: Clear search history - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Search history cleared - */ -router.delete("/history", authenticate, searchController.clearSearchHistory); - -module.exports = router; +const express = require('express'); +const { discoveryService } = require('../services/discoveryService'); + +const createSearchRouter = (service = discoveryService) => { + const router = express.Router(); + + router.get('/', (req, res) => { + try { + const result = service.search({ + query: req.query.q || req.query.query || '', + filters: req.query, + userId: req.query.userId, + sessionId: req.query.sessionId + }); + + res.json({ success: true, data: result }); + } catch (error) { + res.status(500).json({ success: false, message: 'Failed to execute search', error: error.message }); + } + }); + + router.get('/suggestions', (req, res) => { + try { + const sessionKey = service.getUserSessionKey(req.query.userId, req.query.sessionId); + const suggestions = service.getSuggestions(req.query.q || req.query.query || '', sessionKey, Number(req.query.limit) || 6); + res.json({ success: true, data: { suggestions } }); + } catch (error) { + res.status(500).json({ success: false, message: 'Failed to load suggestions', error: error.message }); + } + }); + + router.post('/voice', (req, res) => { + try { + const normalizedQuery = service.normalizeVoiceQuery(req.body.transcript || req.body.query || ''); + const result = service.search({ + query: normalizedQuery, + filters: req.body.filters || {}, + userId: req.body.userId, + sessionId: req.body.sessionId + }); + + res.json({ success: true, data: { normalizedQuery, result } }); + } catch (error) { + res.status(500).json({ success: false, message: 'Failed to process voice query', error: error.message }); + } + }); + + router.get('/recommendations', (req, res) => { + try { + const data = service.getRecommendations(req.query.userId, req.query.sessionId, Number(req.query.limit) || 6); + res.json({ success: true, data }); + } catch (error) { + res.status(500).json({ success: false, message: 'Failed to load recommendations', error: error.message }); + } + }); + + router.get('/trending', (req, res) => { + try { + res.json({ success: true, data: service.getTrending(Number(req.query.limit) || 6) }); + } catch (error) { + res.status(500).json({ success: false, message: 'Failed to load trending content', error: error.message }); + } + }); + + router.get('/similar/:courseId', (req, res) => { + try { + const data = service.getSimilar(req.params.courseId, Number(req.query.limit) || 4); + + if (!data) { + return res.status(404).json({ success: false, message: 'Course not found' }); + } + + return res.json({ success: true, data }); + } catch (error) { + return res.status(500).json({ success: false, message: 'Failed to load similar content', error: error.message }); + } + }); + + router.get('/learning-paths', (req, res) => { + try { + const data = service.getLearningPaths(req.query.q || req.query.query || '', req.query.userId, req.query.sessionId, Number(req.query.limit) || 4); + res.json({ success: true, data }); + } catch (error) { + res.status(500).json({ success: false, message: 'Failed to load learning paths', error: error.message }); + } + }); + + router.get('/curators', (req, res) => { + try { + res.json({ success: true, data: service.getCuratorRecommendations(Number(req.query.limit) || 3) }); + } catch (error) { + res.status(500).json({ success: false, message: 'Failed to load curator picks', error: error.message }); + } + }); + + router.get('/history', (req, res) => { + try { + const items = service.getSearchHistory(req.query.userId, req.query.sessionId); + res.json({ success: true, data: { items } }); + } catch (error) { + res.status(500).json({ success: false, message: 'Failed to load search history', error: error.message }); + } + }); + + router.get('/saved-searches', (req, res) => { + try { + const items = service.getSavedSearches(req.query.userId, req.query.sessionId); + res.json({ success: true, data: { items } }); + } catch (error) { + res.status(500).json({ success: false, message: 'Failed to load saved searches', error: error.message }); + } + }); + + router.post('/saved-searches', (req, res) => { + try { + const item = service.saveSearch(req.body.userId, req.body.sessionId, req.body); + res.status(201).json({ success: true, data: item }); + } catch (error) { + res.status(500).json({ success: false, message: 'Failed to save search', error: error.message }); + } + }); + + router.get('/alerts', (req, res) => { + try { + const items = service.getAlerts(req.query.userId, req.query.sessionId); + res.json({ success: true, data: { items } }); + } catch (error) { + res.status(500).json({ success: false, message: 'Failed to load search alerts', error: error.message }); + } + }); + + router.post('/alerts', (req, res) => { + try { + const item = service.createAlert(req.body.userId, req.body.sessionId, req.body); + res.status(201).json({ success: true, data: item }); + } catch (error) { + res.status(500).json({ success: false, message: 'Failed to create alert', error: error.message }); + } + }); + + router.post('/click', (req, res) => { + try { + const data = service.recordClick(req.body.userId, req.body.sessionId, req.body); + res.status(201).json({ success: true, data }); + } catch (error) { + res.status(500).json({ success: false, message: 'Failed to record search click', error: error.message }); + } + }); + + router.get('/analytics', (req, res) => { + try { + res.json({ success: true, data: service.getAnalytics() }); + } catch (error) { + res.status(500).json({ success: false, message: 'Failed to load search analytics', error: error.message }); + } + }); + + return router; +}; + +module.exports = createSearchRouter; +module.exports.createSearchRouter = createSearchRouter; \ No newline at end of file diff --git a/backend/src/routes/secureCommRoutes.ts b/backend/src/routes/secureCommRoutes.ts index 68f939c..53c8c0a 100644 --- a/backend/src/routes/secureCommRoutes.ts +++ b/backend/src/routes/secureCommRoutes.ts @@ -1,75 +1,117 @@ +import express from 'express'; +import secureCommController from '../controllers/secureCommController'; +import { authenticateToken } from '../middleware/auth'; +import { handleValidationErrors } from '../middleware/validation'; +import { body, param } from 'express-validator'; + +const router: import('express').Router = express.Router(); + /** - * @openapi - * tags: - * - name: Secure Communications - * description: End-to-end encrypted communications + * @route POST /api/secure-comm/generate-keypair + * @desc Generate quantum-resistant key pair + * @access Private */ +router.post( + '/generate-keypair', + authenticateToken, + secureCommController.generateKeyPair +); -import express, { Request, Response } from "express"; -import { secureCommController } from "../controllers/secureCommController"; +/** + * @route POST /api/secure-comm/establish-secret + * @desc Establish shared secret between users + * @access Private + */ +router.post( + '/establish-secret', + authenticateToken, + [ + body('privateKey').notEmpty().withMessage('Private key is required'), + body('peerPublicKey').notEmpty().withMessage('Peer public key is required'), + body('userId').notEmpty().withMessage('User ID is required'), + body('peerId').notEmpty().withMessage('Peer ID is required') + ], + handleValidationErrors, + secureCommController.establishSharedSecret +); -const router = express.Router(); +/** + * @route POST /api/secure-comm/encrypt + * @desc Encrypt message + * @access Private + */ +router.post( + '/encrypt', + authenticateToken, + [ + body('message').notEmpty().withMessage('Message is required'), + body('sharedSecret').notEmpty().withMessage('Shared secret is required') + ], + handleValidationErrors, + secureCommController.encryptMessage +); /** - * @openapi - * /api/secure-comm/init: - * post: - * tags: [Secure Communications] - * summary: Initialize secure communication session - * responses: - * '200': - * description: Session initialized + * @route POST /api/secure-comm/decrypt + * @desc Decrypt message + * @access Private */ -router.post("/init", (req: Request, res: Response) => { - secureCommController.initializeSession(req, res); -}); +router.post( + '/decrypt', + authenticateToken, + [ + body('ciphertext').notEmpty().withMessage('Ciphertext is required'), + body('nonce').notEmpty().withMessage('Nonce is required'), + body('sharedSecret').notEmpty().withMessage('Shared secret is required') + ], + handleValidationErrors, + secureCommController.decryptMessage +); /** - * @openapi - * /api/secure-comm/send: - * post: - * tags: [Secure Communications] - * summary: Send encrypted message - * responses: - * '200': - * description: Message sent + * @route POST /api/secure-comm/sign + * @desc Sign message + * @access Private */ -router.post("/send", (req: Request, res: Response) => { - secureCommController.sendMessage(req, res); -}); +router.post( + '/sign', + authenticateToken, + [ + body('message').notEmpty().withMessage('Message is required'), + body('privateKey').notEmpty().withMessage('Private key is required') + ], + handleValidationErrors, + secureCommController.signMessage +); /** - * @openapi - * /api/secure-comm/receive/{sessionId}: - * get: - * tags: [Secure Communications] - * summary: Receive encrypted messages for session - * parameters: - * - in: path - * name: sessionId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Messages retrieved + * @route POST /api/secure-comm/verify + * @desc Verify message signature + * @access Private */ -router.get("/receive/:sessionId", (req: Request, res: Response) => { - secureCommController.receiveMessages(req, res); -}); +router.post( + '/verify', + authenticateToken, + [ + body('message').notEmpty().withMessage('Message is required'), + body('signature').notEmpty().withMessage('Signature is required'), + body('publicKey').notEmpty().withMessage('Public key is required') + ], + handleValidationErrors, + secureCommController.verifySignature +); /** - * @openapi - * /api/secure-comm/end: - * post: - * tags: [Secure Communications] - * summary: End secure communication session - * responses: - * '200': - * description: Session ended + * @route GET /api/secure-comm/stats/:userId + * @desc Get communication statistics + * @access Private */ -router.post("/end", (req: Request, res: Response) => { - secureCommController.endSession(req, res); -}); +router.get( + '/stats/:userId', + authenticateToken, + [param('userId').notEmpty().withMessage('User ID is required')], + handleValidationErrors, + secureCommController.getStats +); export default router; diff --git a/backend/src/routes/smartWallet.ts b/backend/src/routes/smartWallet.ts index c7738a6..db26ea0 100644 --- a/backend/src/routes/smartWallet.ts +++ b/backend/src/routes/smartWallet.ts @@ -1,8 +1,6 @@ /** - * @openapi - * tags: - * - name: Smart Wallet - * description: Smart contract wallet operations including multisig, recovery, session keys + * Smart Wallet Routes + * API endpoints for smart contract wallet operations */ import express, { Request, Response } from 'express'; @@ -13,260 +11,24 @@ import { validateRequest } from '../middleware/validation'; const router: import('express').Router = express.Router(); const wrap = (fn: any) => (req: Request, res: Response) => fn(req, res); +// Apply authentication middleware to all routes router.use(authenticate as any); -/** - * @openapi - * /api/smart-wallet/create: - * post: - * tags: [Smart Wallet] - * summary: Create smart wallet - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Wallet created - */ router.post('/create', validateRequest as any, wrap(smartWalletController.createSmartWallet) as any); - -/** - * @openapi - * /api/smart-wallet/execute: - * post: - * tags: [Smart Wallet] - * summary: Execute wallet transaction - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Transaction executed - */ router.post('/execute', validateRequest as any, wrap(smartWalletController.executeTransaction) as any); - -/** - * @openapi - * /api/smart-wallet/execute-batch: - * post: - * tags: [Smart Wallet] - * summary: Execute batch transactions - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Batch executed - */ router.post('/execute-batch', validateRequest as any, wrap(smartWalletController.executeBatchTransactions) as any); - -/** - * @openapi - * /api/smart-wallet/recovery/setup: - * post: - * tags: [Smart Wallet] - * summary: Setup social recovery - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Recovery setup - */ router.post('/recovery/setup', validateRequest as any, wrap(smartWalletController.setupSocialRecovery) as any); - -/** - * @openapi - * /api/smart-wallet/recovery/initiate: - * post: - * tags: [Smart Wallet] - * summary: Initiate wallet recovery - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Recovery initiated - */ router.post('/recovery/initiate', validateRequest as any, wrap(smartWalletController.initiateRecovery) as any); - -/** - * @openapi - * /api/smart-wallet/recovery/support: - * post: - * tags: [Smart Wallet] - * summary: Support recovery request - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Recovery supported - */ router.post('/recovery/support', validateRequest as any, wrap(smartWalletController.supportRecovery) as any); - -/** - * @openapi - * /api/smart-wallet/recovery/{recoveryId}: - * get: - * tags: [Smart Wallet] - * summary: Get recovery request details - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: recoveryId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Recovery request retrieved - */ router.get('/recovery/:recoveryId', wrap(smartWalletController.getRecoveryRequest) as any); - -/** - * @openapi - * /api/smart-wallet/multisig/setup: - * post: - * tags: [Smart Wallet] - * summary: Setup multi-signature wallet - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Multi-sig setup - */ router.post('/multisig/setup', validateRequest as any, wrap(smartWalletController.setupMultiSig) as any); - -/** - * @openapi - * /api/smart-wallet/multisig/propose: - * post: - * tags: [Smart Wallet] - * summary: Propose multi-sig transaction - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Transaction proposed - */ router.post('/multisig/propose', validateRequest as any, wrap(smartWalletController.proposeTransaction) as any); - -/** - * @openapi - * /api/smart-wallet/multisig/pending/{walletAddress}: - * get: - * tags: [Smart Wallet] - * summary: Get pending multi-sig transactions - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: walletAddress - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Pending transactions retrieved - */ router.get('/multisig/pending/:walletAddress', wrap(smartWalletController.getPendingTransactions) as any); - -/** - * @openapi - * /api/smart-wallet/session-key/create: - * post: - * tags: [Smart Wallet] - * summary: Create session key - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Session key created - */ router.post('/session-key/create', validateRequest as any, wrap(smartWalletController.createSessionKey) as any); - -/** - * @openapi - * /api/smart-wallet/session-key/active/{walletAddress}: - * get: - * tags: [Smart Wallet] - * summary: Get active session keys - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: walletAddress - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Session keys retrieved - */ router.get('/session-key/active/:walletAddress', wrap(smartWalletController.getActiveSessionKeys) as any); - -/** - * @openapi - * /api/smart-wallet/activity/{walletAddress}: - * get: - * tags: [Smart Wallet] - * summary: Get wallet activity - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: walletAddress - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Activity retrieved - */ router.get('/activity/:walletAddress', wrap(smartWalletController.getWalletActivity) as any); - -/** - * @openapi - * /api/smart-wallet/alerts/{walletAddress}: - * get: - * tags: [Smart Wallet] - * summary: Get wallet activity alerts - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: walletAddress - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Alerts retrieved - */ router.get('/alerts/:walletAddress', wrap(smartWalletController.getActivityAlerts) as any); - -/** - * @openapi - * /api/smart-wallet/credentials/stats: - * get: - * tags: [Smart Wallet] - * summary: Get credential renewal statistics - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Statistics retrieved - */ router.get('/credentials/stats', wrap(smartWalletController.getCredentialRenewalStats) as any); - -/** - * @openapi - * /api/smart-wallet/credentials/auto-renewal: - * post: - * tags: [Smart Wallet] - * summary: Enable auto-renewal for credentials - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Auto-renewal enabled - */ router.post('/credentials/auto-renewal', validateRequest as any, wrap(smartWalletController.enableAutoRenewal) as any); export default router; diff --git a/backend/src/routes/smartWalletRoutes.ts b/backend/src/routes/smartWalletRoutes.ts index 11c0daa..90df7af 100644 --- a/backend/src/routes/smartWalletRoutes.ts +++ b/backend/src/routes/smartWalletRoutes.ts @@ -1,114 +1,272 @@ /** - * @openapi - * tags: - * - name: Smart Wallet - * description: Blockchain smart wallet management + * Smart Wallet Routes + * API endpoints for smart contract wallet operations */ -import { Router } from "express"; -import * as smartWalletController from "../controllers/smartWalletController"; -import { authMiddleware } from "../middleware/authMiddlewares"; +import { Router, RequestHandler } from 'express'; +import * as smartWalletController from '../controllers/smartWalletController'; +import { authenticate } from '../middleware/auth'; +import { handleValidationErrors } from '../middleware/validation'; +import { body, param, query } from 'express-validator'; const router: Router = Router(); +// Apply authentication middleware to all routes +router.use(authenticate); + +/** + * @route POST /api/smart-wallet/create + * @desc Create a new smart contract wallet + * @access Private + */ +router.post( + '/create', + [ + body('ownerAddress').isEthereumAddress().withMessage('Invalid owner address'), + body('guardians').optional().isArray().withMessage('Guardians must be an array'), + body('guardians.*.address').optional().isEthereumAddress().withMessage('Invalid guardian address'), + body('threshold').optional().isInt({ min: 1 }).withMessage('Threshold must be at least 1'), + handleValidationErrors, + ], + smartWalletController.createSmartWallet +); + +/** + * @route POST /api/smart-wallet/execute + * @desc Execute a transaction through smart wallet + * @access Private + */ +router.post( + '/execute', + [ + body('walletAddress').isEthereumAddress().withMessage('Invalid wallet address'), + body('to').isEthereumAddress().withMessage('Invalid recipient address'), + body('value').isString().withMessage('Value must be a string'), + body('data').isString().withMessage('Data must be a string'), + body('signature').isString().withMessage('Signature is required'), + handleValidationErrors, + ], + smartWalletController.executeTransaction +); + +/** + * @route POST /api/smart-wallet/execute-batch + * @desc Execute batch transactions + * @access Private + */ +router.post( + '/execute-batch', + [ + body('walletAddress').isEthereumAddress().withMessage('Invalid wallet address'), + body('transactions').isArray({ min: 1 }).withMessage('Transactions array is required'), + body('transactions.*.to').isEthereumAddress().withMessage('Invalid recipient address'), + body('transactions.*.value').isString().withMessage('Value must be a string'), + body('transactions.*.data').isString().withMessage('Data must be a string'), + body('signature').isString().withMessage('Signature is required'), + handleValidationErrors, + ], + smartWalletController.executeBatchTransactions +); + +/** + * @route POST /api/smart-wallet/social-recovery/setup + * @desc Setup social recovery for wallet + * @access Private + */ +router.post( + '/social-recovery/setup', + [ + body('walletAddress').isEthereumAddress().withMessage('Invalid wallet address'), + body('guardians').isArray({ min: 1 }).withMessage('At least one guardian is required'), + body('guardians.*.address').isEthereumAddress().withMessage('Invalid guardian address'), + body('threshold').isInt({ min: 1 }).withMessage('Threshold must be at least 1'), + handleValidationErrors, + ], + smartWalletController.setupSocialRecovery +); + +/** + * @route POST /api/smart-wallet/social-recovery/initiate + * @desc Initiate wallet recovery + * @access Private + */ +router.post( + '/social-recovery/initiate', + [ + body('walletAddress').isEthereumAddress().withMessage('Invalid wallet address'), + body('newOwner').isEthereumAddress().withMessage('Invalid new owner address'), + body('guardianAddress').isEthereumAddress().withMessage('Invalid guardian address'), + body('guardianSignature').isString().withMessage('Guardian signature is required'), + handleValidationErrors, + ], + smartWalletController.initiateRecovery +); + +/** + * @route POST /api/smart-wallet/social-recovery/support + * @desc Support a recovery request + * @access Private + */ +router.post( + '/social-recovery/support', + [ + body('recoveryId').isString().withMessage('Recovery ID is required'), + body('guardianAddress').isEthereumAddress().withMessage('Invalid guardian address'), + body('guardianSignature').isString().withMessage('Guardian signature is required'), + handleValidationErrors, + ], + smartWalletController.supportRecovery +); + +/** + * @route GET /api/smart-wallet/social-recovery/:recoveryId + * @desc Get recovery request details + * @access Private + */ +router.get( + '/social-recovery/:recoveryId', + [ + param('recoveryId').isString().withMessage('Recovery ID is required'), + handleValidationErrors, + ], + smartWalletController.getRecoveryRequest +); + /** - * @openapi - * /api/smart-wallet/{userId}: - * get: - * tags: [Smart Wallet] - * summary: Get wallet details for user - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Wallet details retrieved - */ -router.get("/:userId", authMiddleware, smartWalletController.getWallet); - -/** - * @openapi - * /api/smart-wallet/{userId}/balance: - * get: - * tags: [Smart Wallet] - * summary: Get wallet balance - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Balance retrieved - */ -router.get("/:userId/balance", authMiddleware, smartWalletController.getBalance); - -/** - * @openapi - * /api/smart-wallet/{userId}/deposit: - * post: - * tags: [Smart Wallet] - * summary: Deposit funds to wallet - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Deposit processed - */ -router.post("/:userId/deposit", authMiddleware, smartWalletController.deposit); - -/** - * @openapi - * /api/smart-wallet/{userId}/withdraw: - * post: - * tags: [Smart Wallet] - * summary: Withdraw funds from wallet - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Withdrawal processed - */ -router.post("/:userId/withdraw", authMiddleware, smartWalletController.withdraw); - -/** - * @openapi - * /api/smart-wallet/{userId}/transactions: - * get: - * tags: [Smart Wallet] - * summary: Get transaction history - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Transactions retrieved - */ -router.get("/:userId/transactions", authMiddleware, smartWalletController.getTransactions); + * @route POST /api/smart-wallet/multi-sig/setup + * @desc Setup multi-signature for wallet + * @access Private + */ +router.post( + '/multi-sig/setup', + [ + body('walletAddress').isEthereumAddress().withMessage('Invalid wallet address'), + body('signers').isArray({ min: 1 }).withMessage('At least one signer is required'), + body('signers.*').isEthereumAddress().withMessage('Invalid signer address'), + body('threshold').isInt({ min: 1 }).withMessage('Threshold must be at least 1'), + handleValidationErrors, + ], + smartWalletController.setupMultiSig +); + +/** + * @route POST /api/smart-wallet/multi-sig/propose + * @desc Propose a multi-sig transaction + * @access Private + */ +router.post( + '/multi-sig/propose', + [ + body('walletAddress').isEthereumAddress().withMessage('Invalid wallet address'), + body('to').isEthereumAddress().withMessage('Invalid recipient address'), + body('value').isString().withMessage('Value must be a string'), + body('data').isString().withMessage('Data must be a string'), + body('proposer').isEthereumAddress().withMessage('Invalid proposer address'), + handleValidationErrors, + ], + smartWalletController.proposeTransaction +); + +/** + * @route GET /api/smart-wallet/multi-sig/:walletAddress/pending + * @desc Get pending multi-sig transactions + * @access Private + */ +router.get( + '/multi-sig/:walletAddress/pending', + [ + param('walletAddress').isEthereumAddress().withMessage('Invalid wallet address'), + handleValidationErrors, + ], + smartWalletController.getPendingTransactions +); + +/** + * @route POST /api/smart-wallet/session-key/create + * @desc Create a session key + * @access Private + */ +router.post( + '/session-key/create', + [ + body('walletAddress').isEthereumAddress().withMessage('Invalid wallet address'), + body('permissions').isObject().withMessage('Permissions object is required'), + body('permissions.allowedContracts').optional().isArray().withMessage('Allowed contracts must be an array'), + body('permissions.allowedMethods').optional().isArray().withMessage('Allowed methods must be an array'), + body('permissions.spendingLimit').isString().withMessage('Spending limit must be a string'), + body('validUntil').isISO8601().withMessage('Valid until must be a valid date'), + handleValidationErrors, + ], + smartWalletController.createSessionKey +); + +/** + * @route GET /api/smart-wallet/session-key/:walletAddress + * @desc Get active session keys + * @access Private + */ +router.get( + '/session-key/:walletAddress', + [ + param('walletAddress').isEthereumAddress().withMessage('Invalid wallet address'), + handleValidationErrors, + ], + smartWalletController.getActiveSessionKeys +); + +/** + * @route GET /api/smart-wallet/activity/:walletAddress + * @desc Get wallet activity + * @access Private + */ +router.get( + '/activity/:walletAddress', + [ + param('walletAddress').isEthereumAddress().withMessage('Invalid wallet address'), + query('limit').optional().isInt({ min: 1, max: 1000 }).withMessage('Limit must be between 1 and 1000'), + handleValidationErrors, + ], + smartWalletController.getWalletActivity +); + +/** + * @route GET /api/smart-wallet/activity/:walletAddress/alerts + * @desc Get activity alerts + * @access Private + */ +router.get( + '/activity/:walletAddress/alerts', + [ + param('walletAddress').isEthereumAddress().withMessage('Invalid wallet address'), + query('acknowledged').optional().isBoolean().withMessage('Acknowledged must be a boolean'), + handleValidationErrors, + ], + smartWalletController.getActivityAlerts +); + +/** + * @route GET /api/smart-wallet/credentials/renewal-stats + * @desc Get credential renewal statistics + * @access Private + */ +router.get( + '/credentials/renewal-stats', + smartWalletController.getCredentialRenewalStats +); + +/** + * @route POST /api/smart-wallet/credentials/enable-auto-renewal + * @desc Enable auto-renewal for credential + * @access Private + */ +router.post( + '/credentials/enable-auto-renewal', + [ + body('credentialId').isString().withMessage('Credential ID is required'), + body('renewalThreshold').isInt({ min: 1 }).withMessage('Renewal threshold must be at least 1'), + handleValidationErrors, + ], + smartWalletController.enableAutoRenewal +); export default router; diff --git a/backend/src/routes/swarmLearning.js b/backend/src/routes/swarmLearning.js index 6ce1be6..53d9fbb 100644 --- a/backend/src/routes/swarmLearning.js +++ b/backend/src/routes/swarmLearning.js @@ -1,10 +1,3 @@ -/** - * @openapi - * tags: - * - name: Swarm Learning - * description: Swarm intelligence based learning system - */ - const express = require('express'); const router = express.Router(); const SwarmLearningController = require('../controllers/swarmLearningController'); @@ -16,297 +9,115 @@ const swarmController = new SwarmLearningController(); // Rate limiting for sensitive operations const sensitiveOperationLimit = rateLimit({ - windowMs: 15 * 60 * 1000, - max: 10, + windowMs: 15 * 60 * 1000, // 15 minutes + max: 10, // limit each IP to 10 requests per windowMs message: { error: 'Too many requests, please try again later' } }); const agentOperationLimit = rateLimit({ - windowMs: 1 * 60 * 1000, - max: 30, + windowMs: 1 * 60 * 1000, // 1 minute + max: 30, // limit each IP to 30 agent operations per minute message: { error: 'Too many agent operations, please try again later' } }); -/** - * @openapi - * /api/swarm-learning/initialize: - * post: - * tags: [Swarm Learning] - * summary: Initialize swarm learning system - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: System initialized - */ +// System Management Routes router.post('/initialize', authenticateToken, requireAdmin, sensitiveOperationLimit, async (req, res) => { await swarmController.initialize(req, res); }); -/** - * @openapi - * /api/swarm-learning/shutdown: - * post: - * tags: [Swarm Learning] - * summary: Shutdown swarm learning system - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: System shut down - */ router.post('/shutdown', authenticateToken, requireAdmin, sensitiveOperationLimit, async (req, res) => { await swarmController.shutdown(req, res); }); -/** - * @openapi - * /api/swarm-learning/swarms: - * post: - * tags: [Swarm Learning] - * summary: Create a new swarm - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Swarm created - */ +// Swarm Management Routes router.post('/swarms', authenticateToken, async (req, res) => { await swarmController.createSwarm(req, res); }); -/** - * @openapi - * /api/swarm-learning/swarms/{taskId}/start: - * post: - * tags: [Swarm Learning] - * summary: Start swarm learning task - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: taskId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Swarm learning started - */ router.post('/swarms/:taskId/start', authenticateToken, async (req, res) => { await swarmController.startSwarmLearning(req, res); }); -/** - * @openapi - * /api/swarm-learning/swarms/status: - * get: - * tags: [Swarm Learning] - * summary: Get swarm status - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Swarm status retrieved - */ router.get('/swarms/status', authenticateToken, async (req, res) => { await swarmController.getSwarmStatus(req, res); }); -/** - * @openapi - * /api/swarm-learning/agents: - * post: - * tags: [Swarm Learning] - * summary: Register new agent - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Agent registered - */ +// Agent Management Routes router.post('/agents', authenticateToken, agentOperationLimit, async (req, res) => { await swarmController.registerAgent(req, res); }); -/** - * @openapi - * /api/swarm-learning/agents/{agentId}: - * get: - * tags: [Swarm Learning] - * summary: Get agent details - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: agentId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Agent details retrieved - */ router.get('/agents/:agentId', authenticateToken, async (req, res) => { await swarmController.getAgentDetails(req, res); }); -/** - * @openapi - * /api/swarm-learning/tasks/{taskId}: - * get: - * tags: [Swarm Learning] - * summary: Get task details - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: taskId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Task details retrieved - */ +// Task Management Routes router.get('/tasks/:taskId', authenticateToken, async (req, res) => { await swarmController.getTaskDetails(req, res); }); -/** - * @openapi - * /api/swarm-learning/behaviors: - * get: - * tags: [Swarm Learning] - * summary: Get swarm behaviors - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Behaviors retrieved - */ +// Emergent Behavior Routes router.get('/behaviors', authenticateToken, async (req, res) => { - await swarmController.getBehaviors(req, res); + await swarmController.getEmergentBehaviors(req, res); }); -/** - * @openapi - * /api/swarm-learning/analytics: - * get: - * tags: [Swarm Learning] - * summary: Get analytics data - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Analytics retrieved - */ +// Analytics Routes router.get('/analytics', authenticateToken, async (req, res) => { await swarmController.getAnalytics(req, res); }); -/** - * @openapi - * /api/swarm-learning/analytics/report: - * get: - * tags: [Swarm Learning] - * summary: Generate analytics report - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Report generated - */ router.get('/analytics/report', authenticateToken, async (req, res) => { - await swarmController.getAnalyticsReport(req, res); + await swarmController.getReport(req, res); }); -/** - * @openapi - * /api/swarm-learning/analytics/export: - * get: - * tags: [Swarm Learning] - * summary: Export analytics data - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Data exported - */ router.get('/analytics/export', authenticateToken, async (req, res) => { - await swarmController.exportAnalytics(req, res); + await swarmController.exportData(req, res); }); -/** - * @openapi - * /api/swarm-learning/alerts: - * get: - * tags: [Swarm Learning] - * summary: Get swarm alerts - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Alerts retrieved - */ +// Alert Management Routes router.get('/alerts', authenticateToken, async (req, res) => { await swarmController.getAlerts(req, res); }); -/** - * @openapi - * /api/swarm-learning/alerts/{alertId}/acknowledge: - * post: - * tags: [Swarm Learning] - * summary: Acknowledge alert - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: alertId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Alert acknowledged - */ router.post('/alerts/:alertId/acknowledge', authenticateToken, async (req, res) => { await swarmController.acknowledgeAlert(req, res); }); -/** - * @openapi - * /api/swarm-learning/configuration: - * put: - * tags: [Swarm Learning] - * summary: Update swarm configuration - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Configuration updated - */ +// Configuration Routes router.put('/configuration', authenticateToken, requireAdmin, sensitiveOperationLimit, async (req, res) => { await swarmController.updateConfiguration(req, res); }); -/** - * @openapi - * /api/swarm-learning/health: - * get: - * tags: [Swarm Learning] - * summary: Health check - * responses: - * '200': - * description: Health status - */ +// Health check endpoint router.get('/health', async (req, res) => { - await swarmController.healthCheck(req, res); + try { + // Basic health check - in a real implementation, this would check all components + res.status(200).json({ + status: 'healthy', + timestamp: new Date().toISOString(), + service: 'swarm-learning' + }); + } catch (error) { + res.status(503).json({ + status: 'unhealthy', + timestamp: new Date().toISOString(), + error: error.message + }); + } +}); + +// Error handling middleware +router.use((error, req, res, next) => { + console.error('Swarm Learning Route Error:', error); + + res.status(error.status || 500).json({ + error: error.message || 'Internal server error', + details: process.env.NODE_ENV === 'development' ? error.stack : undefined + }); }); module.exports = router; diff --git a/backend/src/routes/syncRoutes.ts b/backend/src/routes/syncRoutes.ts index 8fbbb65..61566f0 100644 --- a/backend/src/routes/syncRoutes.ts +++ b/backend/src/routes/syncRoutes.ts @@ -1,10 +1,3 @@ -/** - * @openapi - * tags: - * - name: Sync - * description: Device synchronization and offline queue management - */ - import { Router } from "express"; import * as syncController from "../controllers/syncController"; import { validate } from "../middleware/validate"; @@ -22,141 +15,21 @@ import { const router: Router = Router(); -/** - * @openapi - * /api/sync/devices/register: - * post: - * tags: [Sync] - * summary: Register device for sync - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * deviceId: - * type: string - * platform: - * type: string - * responses: - * '200': - * description: Device registered - */ +// Device registration and management router.post("/devices/register", validate(registerDeviceSchema), syncController.registerDevice); - -/** - * @openapi - * /api/sync/devices/heartbeat: - * post: - * tags: [Sync] - * summary: Send device heartbeat - * responses: - * '200': - * description: Heartbeat received - */ router.post("/devices/heartbeat", validate(heartbeatSchema), syncController.heartbeat); - -/** - * @openapi - * /api/sync/devices/{deviceId}: - * delete: - * tags: [Sync] - * summary: Unregister device - * parameters: - * - in: path - * name: deviceId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Device unregistered - */ router.delete("/devices/:deviceId", validate(deviceIdParamSchema), syncController.unregisterDevice); - -/** - * @openapi - * /api/sync/users/{userId}/devices: - * get: - * tags: [Sync] - * summary: Get devices for user - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Devices retrieved - */ router.get("/users/:userId/devices", validate(userIdParamSchema), syncController.getDevices); -/** - * @openapi - * /api/sync/users/{userId}/status: - * get: - * tags: [Sync] - * summary: Get sync status for user - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Sync status retrieved - */ +// Sync status tracking router.get("/users/:userId/status", validate(getSyncStatusQuerySchema), syncController.getSyncStatus); -/** - * @openapi - * /api/sync/sync: - * post: - * tags: [Sync] - * summary: Sync entity data across devices - * responses: - * '200': - * description: Data synced - */ +// Entity sync (real-time sync when change is detected) router.post("/sync", validate(syncEntitySchema), syncController.syncEntity); -/** - * @openapi - * /api/sync/queue: - * post: - * tags: [Sync] - * summary: Enqueue sync operation for offline processing - * responses: - * '200': - * description: Operation queued - */ +// Offline queue router.post("/queue", validate(enqueueSyncSchema), syncController.enqueueSync); - -/** - * @openapi - * /api/sync/queue/process: - * post: - * tags: [Sync] - * summary: Process offline queue - * responses: - * '200': - * description: Queue processed - */ router.post("/queue/process", validate(processQueueSchema), syncController.processQueue); - -/** - * @openapi - * /api/sync/queue/status: - * get: - * tags: [Sync] - * summary: Get queue sync status - * responses: - * '200': - * description: Queue status retrieved - */ router.get("/queue/status", validate(queueStatusQuerySchema), syncController.getQueueStatus); export default router; diff --git a/backend/src/routes/tenantAnalytics.js b/backend/src/routes/tenantAnalytics.js index 9792c5b..a152e57 100644 --- a/backend/src/routes/tenantAnalytics.js +++ b/backend/src/routes/tenantAnalytics.js @@ -1,95 +1,313 @@ +const express = require('express'); +const tenantAnalyticsService = require('../services/tenantAnalyticsService'); +const { tenantMiddleware, requireTenantPermission } = require('../middleware/tenant'); +const { authenticateToken } = require('../middleware/auth'); +const Joi = require('joi'); + +const router = express.Router(); + +// Validation schemas +const reportRequestSchema = Joi.object({ + reportType: Joi.string().valid('cross-tenant', 'tenant-specific', 'performance').default('summary'), + dateRange: Joi.string().valid('7d', '30d', '90d', '1y').default('30d'), + format: Joi.string().valid('json', 'csv', 'xlsx', 'pdf').default('json') +}); + /** - * @openapi - * tags: - * - name: Tenant Analytics - * description: Per-tenant analytics and reporting + * GET /api/analytics/tenants/cross-tenant + * Get cross-tenant analytics (admin only) */ +router.get('/cross-tenant', + authenticateToken, + requireTenantPermission('analytics', 'read'), + async (req, res) => { + try { + const { dateRange = '30d' } = req.query; + + const analytics = await tenantAnalyticsService.getCrossTenantAnalytics(dateRange); -const express = require("express"); -const router = express.Router(); -const { authenticate, authorize } = require("../middleware/auth"); -const tenantAnalyticsController = require("../controllers/tenantAnalyticsController"); + res.json({ + success: true, + data: analytics + }); + } catch (error) { + console.error('Cross-tenant analytics error:', error); + res.status(500).json({ + success: false, + message: error.message + }); + } + } +); + +/** + * GET /api/analytics/tenants/:tenantId + * Get tenant-specific analytics + */ +router.get('/:tenantId', + tenantMiddleware, + requireTenantPermission('analytics', 'read'), + async (req, res) => { + try { + const { dateRange = '30d' } = req.query; + + const analytics = await tenantAnalyticsService.getTenantAnalytics( + req.tenantId, + dateRange + ); -router.use(authenticate, authorize("admin")); + res.json({ + success: true, + data: analytics + }); + } catch (error) { + console.error('Tenant analytics error:', error); + res.status(500).json({ + success: false, + message: error.message + }); + } + } +); /** - * @openapi - * /api/tenant-analytics/{tenantId}/overview: - * get: - * tags: [Tenant Analytics] - * summary: Get analytics overview for tenant - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: tenantId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Overview retrieved + * GET /api/analytics/tenants/:tenantId/performance + * Get tenant performance metrics */ -router.get("/:tenantId/overview", tenantAnalyticsController.getTenantOverview); +router.get('/:tenantId/performance', + tenantMiddleware, + requireTenantPermission('analytics', 'read'), + async (req, res) => { + try { + const performance = await tenantAnalyticsService.getPerformanceMetrics(req.tenantId); + + res.json({ + success: true, + data: performance + }); + } catch (error) { + console.error('Performance metrics error:', error); + res.status(500).json({ + success: false, + message: error.message + }); + } + } +); /** - * @openapi - * /api/tenant-analytics/{tenantId}/users: - * get: - * tags: [Tenant Analytics] - * summary: Get user analytics for tenant - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: tenantId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: User analytics retrieved + * POST /api/analytics/tenants/:tenantId/reports + * Generate analytics report for tenant */ -router.get("/:tenantId/users", tenantAnalyticsController.getTenantUserAnalytics); +router.post('/:tenantId/reports', + tenantMiddleware, + requireTenantPermission('analytics', 'export'), + async (req, res) => { + try { + const { error, value } = reportRequestSchema.validate(req.body); + if (error) { + return res.status(400).json({ + success: false, + message: 'Validation error', + details: error.details.map(d => d.message) + }); + } + + const report = await tenantAnalyticsService.generateReport( + req.tenantId, + value.reportType, + value.dateRange + ); + + res.json({ + success: true, + message: 'Report generated successfully', + data: report + }); + } catch (error) { + console.error('Generate report error:', error); + res.status(500).json({ + success: false, + message: error.message + }); + } + } +); /** - * @openapi - * /api/tenant-analytics/{tenantId}/revenue: - * get: - * tags: [Tenant Analytics] - * summary: Get revenue analytics for tenant - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: tenantId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Revenue analytics retrieved + * GET /api/analytics/tenants/:tenantId/export + * Export analytics data in various formats */ -router.get("/:tenantId/revenue", tenantAnalyticsController.getTenantRevenueAnalytics); +router.get('/:tenantId/export', + tenantMiddleware, + requireTenantPermission('analytics', 'export'), + async (req, res) => { + try { + const { format = 'json', dateRange = '30d' } = req.query; + + if (!['json', 'csv', 'xlsx', 'pdf'].includes(format)) { + return res.status(400).json({ + success: false, + message: 'Invalid export format' + }); + } + + const exportedData = await tenantAnalyticsService.exportData( + req.tenantId, + format, + dateRange + ); + + if (format === 'json') { + res.json({ + success: true, + data: exportedData + }); + } else { + // For other formats, set appropriate headers and send file + res.setHeader('Content-Type', this.getContentType(format)); + res.setHeader( + 'Content-Disposition', + `attachment; filename="tenant-analytics-${req.tenantId}-${dateRange}.${format}"` + ); + res.send(exportedData); + } + } catch (error) { + console.error('Export data error:', error); + res.status(500).json({ + success: false, + message: error.message + }); + } + } +); + +/** + * POST /api/analytics/tenants/reports + * Generate cross-tenant analytics report (admin only) + */ +router.post('/reports', + authenticateToken, + requireTenantPermission('analytics', 'export'), + async (req, res) => { + try { + const { error, value } = reportRequestSchema.validate(req.body); + if (error) { + return res.status(400).json({ + success: false, + message: 'Validation error', + details: error.details.map(d => d.message) + }); + } + + const report = await tenantAnalyticsService.generateReport( + null, + value.reportType, + value.dateRange + ); + + res.json({ + success: true, + message: 'Cross-tenant report generated successfully', + data: report + }); + } catch (error) { + console.error('Generate cross-tenant report error:', error); + res.status(500).json({ + success: false, + message: error.message + }); + } + } +); + +/** + * GET /api/analytics/tenants/export + * Export cross-tenant analytics data (admin only) + */ +router.get('/export', + authenticateToken, + requireTenantPermission('analytics', 'export'), + async (req, res) => { + try { + const { format = 'json', dateRange = '30d' } = req.query; + + if (!['json', 'csv', 'xlsx', 'pdf'].includes(format)) { + return res.status(400).json({ + success: false, + message: 'Invalid export format' + }); + } + + const exportedData = await tenantAnalyticsService.exportData( + null, + format, + dateRange + ); + + if (format === 'json') { + res.json({ + success: true, + data: exportedData + }); + } else { + // For other formats, set appropriate headers and send file + res.setHeader('Content-Type', this.getContentType(format)); + res.setHeader( + 'Content-Disposition', + `attachment; filename="cross-tenant-analytics-${dateRange}.${format}"` + ); + res.send(exportedData); + } + } catch (error) { + console.error('Export cross-tenant data error:', error); + res.status(500).json({ + success: false, + message: error.message + }); + } + } +); + +/** + * GET /api/analytics/tenants/performance + * Get system performance metrics (admin only) + */ +router.get('/performance', + authenticateToken, + requireTenantPermission('analytics', 'read'), + async (req, res) => { + try { + const performance = await tenantAnalyticsService.getPerformanceMetrics(); + + res.json({ + success: true, + data: performance + }); + } catch (error) { + console.error('System performance metrics error:', error); + res.status(500).json({ + success: false, + message: error.message + }); + } + } +); /** - * @openapi - * /api/tenant-analytics/{tenantId}/export: - * post: - * tags: [Tenant Analytics] - * summary: Export analytics report for tenant - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: tenantId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Report exported + * Helper method to get content type for export formats */ -router.post("/:tenantId/export", tenantAnalyticsController.exportTenantReport); +function getContentType(format) { + switch (format) { + case 'csv': + return 'text/csv'; + case 'xlsx': + return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; + case 'pdf': + return 'application/pdf'; + default: + return 'application/json'; + } +} module.exports = router; diff --git a/backend/src/routes/tenants.js b/backend/src/routes/tenants.js index 35ff649..ec931fa 100644 --- a/backend/src/routes/tenants.js +++ b/backend/src/routes/tenants.js @@ -1,123 +1,446 @@ +const express = require('express'); +const tenantService = require('../services/tenantService'); +const { tenantMiddleware, checkResourceLimits, requireTenantPermission } = require('../middleware/tenant'); +const { authenticateToken } = require('../middleware/auth'); +const Joi = require('joi'); + +const router = express.Router(); + +// Validation schemas +const createTenantSchema = Joi.object({ + name: Joi.string().required().min(2).max(100), + subdomain: Joi.string().required().min(3).max(50).pattern(/^[a-z0-9-]+$/), + domain: Joi.string().optional(), + plan: Joi.string().valid('starter', 'professional', 'enterprise').default('starter'), + branding: Joi.object({ + logo: Joi.string().optional(), + primaryColor: Joi.string().optional(), + secondaryColor: Joi.string().optional(), + theme: Joi.string().valid('light', 'dark', 'auto').optional(), + customCSS: Joi.string().optional(), + favicon: Joi.string().optional(), + companyName: Joi.string().optional(), + supportEmail: Joi.string().email().optional() + }).optional(), + settings: Joi.object({ + allowPublicRegistration: Joi.boolean().optional(), + requireEmailVerification: Joi.boolean().optional(), + enableSSO: Joi.boolean().optional(), + ssoProvider: Joi.string().optional(), + defaultLanguage: Joi.string().optional(), + timezone: Joi.string().optional(), + maxUsers: Joi.number().integer().min(1).optional(), + maxStorage: Joi.number().integer().min(1).optional() + }).optional(), + contact: Joi.object({ + firstName: Joi.string().required(), + lastName: Joi.string().required(), + email: Joi.string().email().required(), + phone: Joi.string().optional(), + address: Joi.object({ + street: Joi.string().optional(), + city: Joi.string().optional(), + state: Joi.string().optional(), + country: Joi.string().optional(), + zipCode: Joi.string().optional() + }).optional() + }).required(), + adminUser: Joi.object({ + profile: Joi.object({ + firstName: Joi.string().required(), + lastName: Joi.string().required(), + email: Joi.string().email().required(), + phone: Joi.string().optional() + }).required(), + auth: Joi.object({ + password: Joi.string().min(8).required() + }).required() + }).required() +}); + +const createTenantUserSchema = Joi.object({ + profile: Joi.object({ + firstName: Joi.string().required(), + lastName: Joi.string().required(), + email: Joi.string().email().required(), + phone: Joi.string().optional(), + bio: Joi.string().optional(), + timezone: Joi.string().optional(), + language: Joi.string().optional() + }).required(), + stellar: Joi.object({ + publicKey: Joi.string().required().pattern(/^G[0-9A-Z]{55}$/), + secretKey: Joi.string().optional() + }).required(), + roles: Joi.array().items(Joi.string().valid('super_admin', 'tenant_admin', 'instructor', 'student', 'ta')).default(['student']), + permissions: Joi.array().items(Joi.object({ + resource: Joi.string().required(), + actions: Joi.array().items(Joi.string()).required() + })).optional(), + academic: Joi.object({ + studentId: Joi.string().optional(), + grade: Joi.string().optional(), + department: Joi.string().optional(), + major: Joi.string().optional(), + enrollmentDate: Joi.date().optional(), + graduationDate: Joi.date().optional() + }).optional() +}); + +const updateTenantSettingsSchema = Joi.object({ + allowPublicRegistration: Joi.boolean().optional(), + requireEmailVerification: Joi.boolean().optional(), + enableSSO: Joi.boolean().optional(), + ssoProvider: Joi.string().optional(), + defaultLanguage: Joi.string().optional(), + timezone: Joi.string().optional(), + maxUsers: Joi.number().integer().min(1).optional(), + maxStorage: Joi.number().integer().min(1).optional() +}); + +const updateTenantBrandingSchema = Joi.object({ + logo: Joi.string().optional(), + primaryColor: Joi.string().pattern(/^#[0-9A-Fa-f]{6}$/).optional(), + secondaryColor: Joi.string().pattern(/^#[0-9A-Fa-f]{6}$/).optional(), + theme: Joi.string().valid('light', 'dark', 'auto').optional(), + customCSS: Joi.string().optional(), + favicon: Joi.string().optional(), + companyName: Joi.string().optional(), + supportEmail: Joi.string().email().optional() +}); + +// Public routes + /** - * @openapi - * tags: - * - name: Tenants - * description: Multi-tenant management + * POST /api/tenants + * Create a new tenant (public endpoint for self-service signup) */ +router.post('/', async (req, res) => { + try { + const { error, value } = createTenantSchema.validate(req.body); + if (error) { + return res.status(400).json({ + success: false, + message: 'Validation error', + details: error.details.map(d => d.message) + }); + } -const express = require("express"); -const router = express.Router(); -const { authenticate, authorize } = require("../middleware/auth"); -const tenantController = require("../controllers/tenantController"); + const result = await tenantService.createTenant( + { + name: value.name, + subdomain: value.subdomain, + domain: value.domain, + plan: value.plan, + branding: value.branding, + settings: value.settings, + contact: value.contact + }, + value.adminUser + ); + + res.status(201).json({ + success: true, + message: result.message, + data: { + tenant: { + id: result.tenant._id, + name: result.tenant.name, + subdomain: result.tenant.subdomain, + status: result.tenant.status, + plan: result.tenant.plan + }, + adminUser: { + id: result.adminUser._id, + profile: result.adminUser.profile, + roles: result.adminUser.roles + } + } + }); + } catch (error) { + console.error('Create tenant error:', error); + res.status(400).json({ + success: false, + message: error.message + }); + } +}); + +/** + * GET /api/tenants/:domain + * Get tenant by domain or subdomain (public endpoint for tenant discovery) + */ +router.get('/:domain', async (req, res) => { + try { + const tenant = await tenantService.getTenantByDomain(req.params.domain); + + if (!tenant) { + return res.status(404).json({ + success: false, + message: 'Tenant not found' + }); + } + + // Return only public information + res.json({ + success: true, + data: { + id: tenant._id, + name: tenant.name, + subdomain: tenant.subdomain, + branding: { + logo: tenant.branding.logo, + primaryColor: tenant.branding.primaryColor, + secondaryColor: tenant.branding.secondaryColor, + theme: tenant.branding.theme, + companyName: tenant.branding.companyName + }, + settings: { + allowPublicRegistration: tenant.settings.allowPublicRegistration, + requireEmailVerification: tenant.settings.requireEmailVerification, + defaultLanguage: tenant.settings.defaultLanguage + } + } + }); + } catch (error) { + console.error('Get tenant error:', error); + res.status(500).json({ + success: false, + message: error.message + }); + } +}); + +// Tenant-protected routes + +/** + * POST /api/tenants/:tenantId/users + * Create user within tenant + */ +router.post('/:tenantId/users', + tenantMiddleware, + checkResourceLimits('users'), + requireTenantPermission('users', 'create'), + async (req, res) => { + try { + const { error, value } = createTenantUserSchema.validate(req.body); + if (error) { + return res.status(400).json({ + success: false, + message: 'Validation error', + details: error.details.map(d => d.message) + }); + } + + const user = await tenantService.createTenantUser(req.tenantId, value); -router.use(authenticate, authorize("admin")); + res.status(201).json({ + success: true, + message: 'User created successfully', + data: user + }); + } catch (error) { + console.error('Create tenant user error:', error); + res.status(400).json({ + success: false, + message: error.message + }); + } + } +); /** - * @openapi - * /api/tenants: - * get: - * tags: [Tenants] - * summary: List all tenants - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Tenants listed - * post: - * tags: [Tenants] - * summary: Create new tenant - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Tenant created + * GET /api/tenants/:tenantId/users + * Get tenant users with pagination and filtering */ -router.get("/", tenantController.listTenants); -router.post("/", tenantController.createTenant); +router.get('/:tenantId/users', + tenantMiddleware, + requireTenantPermission('users', 'read'), + async (req, res) => { + try { + const options = { + page: parseInt(req.query.page) || 1, + limit: parseInt(req.query.limit) || 20, + search: req.query.search, + role: req.query.role, + status: req.query.status, + sortBy: req.query.sortBy || 'createdAt', + sortOrder: req.query.sortOrder || 'desc' + }; + + const result = await tenantService.getTenantUsers(req.tenantId, options); + + res.json({ + success: true, + data: result.users, + pagination: result.pagination + }); + } catch (error) { + console.error('Get tenant users error:', error); + res.status(500).json({ + success: false, + message: error.message + }); + } + } +); + +/** + * PUT /api/tenants/:tenantId/settings + * Update tenant settings + */ +router.put('/:tenantId/settings', + tenantMiddleware, + requireTenantPermission('settings', 'update'), + async (req, res) => { + try { + const { error, value } = updateTenantSettingsSchema.validate(req.body); + if (error) { + return res.status(400).json({ + success: false, + message: 'Validation error', + details: error.details.map(d => d.message) + }); + } + + const tenant = await tenantService.updateTenantSettings(req.tenantId, value); + + res.json({ + success: true, + message: 'Settings updated successfully', + data: tenant.settings + }); + } catch (error) { + console.error('Update tenant settings error:', error); + res.status(400).json({ + success: false, + message: error.message + }); + } + } +); /** - * @openapi - * /api/tenants/{tenantId}: - * get: - * tags: [Tenants] - * summary: Get tenant details - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: tenantId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Tenant details retrieved - * put: - * tags: [Tenants] - * summary: Update tenant - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: tenantId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Tenant updated - * delete: - * tags: [Tenants] - * summary: Delete tenant - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: tenantId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Tenant deleted + * PUT /api/tenants/:tenantId/branding + * Update tenant branding */ -router.get("/:tenantId", tenantController.getTenant); -router.put("/:tenantId", tenantController.updateTenant); -router.delete("/:tenantId", tenantController.deleteTenant); +router.put('/:tenantId/branding', + tenantMiddleware, + requireTenantPermission('branding', 'update'), + async (req, res) => { + try { + const { error, value } = updateTenantBrandingSchema.validate(req.body); + if (error) { + return res.status(400).json({ + success: false, + message: 'Validation error', + details: error.details.map(d => d.message) + }); + } + + const tenant = await tenantService.updateTenantBranding(req.tenantId, value); + + res.json({ + success: true, + message: 'Branding updated successfully', + data: tenant.branding + }); + } catch (error) { + console.error('Update tenant branding error:', error); + res.status(400).json({ + success: false, + message: error.message + }); + } + } +); /** - * @openapi - * /api/tenants/{tenantId}/config: - * get: - * tags: [Tenants] - * summary: Get tenant configuration - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: tenantId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Configuration retrieved - * put: - * tags: [Tenants] - * summary: Update tenant configuration - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: tenantId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Configuration updated + * GET /api/tenants/:tenantId/usage + * Get tenant usage statistics */ -router.get("/:tenantId/config", tenantController.getTenantConfig); -router.put("/:tenantId/config", tenantController.updateTenantConfig); +router.get('/:tenantId/usage', + tenantMiddleware, + requireTenantPermission('analytics', 'read'), + async (req, res) => { + try { + const usage = await tenantService.getTenantUsage(req.tenantId); + + res.json({ + success: true, + data: usage + }); + } catch (error) { + console.error('Get tenant usage error:', error); + res.status(500).json({ + success: false, + message: error.message + }); + } + } +); + +/** + * PUT /api/tenants/:tenantId/status + * Update tenant status (admin only) + */ +router.put('/:tenantId/status', + tenantMiddleware, + requireTenantPermission('admin', 'manage'), + async (req, res) => { + try { + const { status } = req.body; + + if (!['active', 'inactive', 'suspended', 'trial'].includes(status)) { + return res.status(400).json({ + success: false, + message: 'Invalid status value' + }); + } + + const tenant = await tenantService.updateTenantStatus(req.tenantId, status); + + res.json({ + success: true, + message: 'Tenant status updated successfully', + data: { + id: tenant._id, + status: tenant.status + } + }); + } catch (error) { + console.error('Update tenant status error:', error); + res.status(400).json({ + success: false, + message: error.message + }); + } + } +); + +/** + * DELETE /api/tenants/:tenantId + * Delete tenant (admin only) + */ +router.delete('/:tenantId', + tenantMiddleware, + requireTenantPermission('admin', 'delete'), + async (req, res) => { + try { + const result = await tenantService.deleteTenant(req.tenantId); + + res.json({ + success: true, + message: result.message + }); + } catch (error) { + console.error('Delete tenant error:', error); + res.status(400).json({ + success: false, + message: error.message + }); + } + } +); module.exports = router; diff --git a/backend/src/routes/timeLockCredentials.ts b/backend/src/routes/timeLockCredentials.ts index b9b5f4d..6c9a2bf 100644 --- a/backend/src/routes/timeLockCredentials.ts +++ b/backend/src/routes/timeLockCredentials.ts @@ -1,73 +1,443 @@ +import express, { Request, Response } from 'express'; +import { body, param, validationResult } from 'express-validator'; +import { timeLockCredentialService } from '../services/timeLockCredentialService'; +import { authenticateToken } from '../middleware/auth'; +import logger from '../utils/logger'; + +const router: import('express').Router = express.Router(); + +// Middleware to validate requests +const validateRequest = (req: Request, res: Response, next: Function) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ + success: false, + errors: errors.array() + }); + } + next(); +}; + /** - * @openapi - * tags: - * - name: Time Lock Credentials - * description: Time-based credential release system + * @route POST /api/time-lock/issue + * @desc Issue a new time-locked credential + * @access Private (Institutions only) */ +router.post( + '/issue', + authenticateToken, + [ + body('recipient').notEmpty().withMessage('Recipient address is required'), + body('credentialHash').notEmpty().withMessage('Credential hash is required'), + body('metadata').notEmpty().withMessage('Metadata is required'), + body('releaseTime').isISO8601().withMessage('Valid release time is required'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { recipient, credentialHash, metadata, releaseTime } = req.body; + const issuer = (req.user as any)?.address; // From auth middleware -import { Router } from "express"; -import * as timeLockController from "../controllers/timeLockController"; + if (!issuer) { + return res.status(401).json({ + success: false, + message: 'Unauthorized' + }); + } -const router: Router = Router(); + const credential = await timeLockCredentialService.issueCredential({ + issuer, + recipient, + credentialHash, + metadata, + releaseTime: new Date(releaseTime), + }); + + res.status(201).json({ + success: true, + data: credential, + message: 'Time-locked credential issued successfully', + }); + } catch (error: any) { + logger.error('Error issuing credential:', error); + res.status(400).json({ + success: false, + message: error.message || 'Failed to issue credential', + }); + } + } +); /** - * @openapi - * /api/time-lock-credentials/create: - * post: - * tags: [Time Lock Credentials] - * summary: Create time-locked credential - * responses: - * '200': - * description: Credential created + * @route POST /api/time-lock/release/:credentialId + * @desc Release a time-locked credential + * @access Private */ -router.post("/create", timeLockController.createTimeLockCredential); +router.post( + '/release/:credentialId', + authenticateToken, + [ + param('credentialId').notEmpty().withMessage('Credential ID is required'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { credentialId } = req.params; + const caller = (req.user as any)?.address; + + if (!caller) { + return res.status(401).json({ + success: false, + message: 'Unauthorized' + }); + } + + const credential = await timeLockCredentialService.releaseCredential( + credentialId, + caller + ); + + res.json({ + success: true, + data: credential, + message: 'Credential released successfully', + }); + } catch (error: any) { + logger.error('Error releasing credential:', error); + + if (error.message.includes('not found')) { + return res.status(404).json({ + success: false, + message: error.message, + }); + } + + if (error.message.includes('Time lock') || + error.message.includes('Already released') || + error.message.includes('Revoked')) { + return res.status(400).json({ + success: false, + message: error.message, + }); + } + + res.status(500).json({ + success: false, + message: 'Failed to release credential', + }); + } + } +); /** - * @openapi - * /api/time-lock-credentials/release: - * post: - * tags: [Time Lock Credentials] - * summary: Release time-locked credential - * responses: - * '200': - * description: Credential released + * @route POST /api/time-lock/batch-release + * @desc Batch release multiple credentials + * @access Private */ -router.post("/release", timeLockController.releaseCredential); +router.post( + '/batch-release', + authenticateToken, + [ + body('credentialIds').isArray({ min: 1 }).withMessage('Credential IDs must be an array with at least one item'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { credentialIds } = req.body; + const caller = (req.user as any)?.address; + + if (!caller) { + return res.status(401).json({ + success: false, + message: 'Unauthorized' + }); + } + + const results = await timeLockCredentialService.batchReleaseCredentials( + credentialIds, + caller + ); + + const successCount = results.filter(r => r.success).length; + const failCount = results.length - successCount; + + res.json({ + success: true, + data: { + results, + summary: { + total: results.length, + successful: successCount, + failed: failCount, + }, + }, + message: `Batch release completed: ${successCount}/${results.length} successful`, + }); + } catch (error: any) { + logger.error('Error in batch release:', error); + res.status(500).json({ + success: false, + message: 'Failed to process batch release', + }); + } + } +); /** - * @openapi - * /api/time-lock-credentials/{credentialId}: - * get: - * tags: [Time Lock Credentials] - * summary: Get time-locked credential details - * parameters: - * - in: path - * name: credentialId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Credential details retrieved + * @route POST /api/time-lock/emergency-revoke/:credentialId + * @desc Emergency revoke a credential + * @access Private (Admin only) */ -router.get("/:credentialId", timeLockController.getCredential); +router.post( + '/emergency-revoke/:credentialId', + authenticateToken, + [ + param('credentialId').notEmpty().withMessage('Credential ID is required'), + body('reason').notEmpty().withMessage('Revoke reason is required'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { credentialId } = req.params; + const { reason } = req.body; + const admin = (req.user as any)?.address; + + if (!admin) { + return res.status(401).json({ + success: false, + message: 'Unauthorized' + }); + } + + // Check if user has emergency admin privileges + const emergencyAdmin = process.env.EMERGENCY_ADMIN_ADDRESS; + if (admin !== emergencyAdmin) { + return res.status(403).json({ + success: false, + message: 'Emergency admin privileges required', + }); + } + + const credential = await timeLockCredentialService.emergencyRevoke( + credentialId, + admin, + reason + ); + + res.json({ + success: true, + data: credential, + message: 'Credential emergency revoked successfully', + }); + } catch (error: any) { + logger.error('Error in emergency revoke:', error); + + if (error.message.includes('not found')) { + return res.status(404).json({ + success: false, + message: error.message, + }); + } + + res.status(400).json({ + success: false, + message: error.message || 'Failed to emergency revoke credential', + }); + } + } +); /** - * @openapi - * /api/time-lock-credentials/{credentialId}/status: - * get: - * tags: [Time Lock Credentials] - * summary: Get credential release status - * parameters: - * - in: path - * name: credentialId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Status retrieved + * @route POST /api/time-lock/schedule + * @desc Create a release schedule for multiple credentials + * @access Private */ -router.get("/:credentialId/status", timeLockController.getStatus); +router.post( + '/schedule', + authenticateToken, + [ + body('credentialIds').isArray({ min: 1 }).withMessage('Credential IDs must be an array'), + body('releaseTimes').isArray({ min: 1 }).withMessage('Release times must be an array'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { credentialIds, releaseTimes } = req.body; + const creator = (req.user as any)?.address; + + if (!creator) { + return res.status(401).json({ + success: false, + message: 'Unauthorized' + }); + } + + const scheduleId = await timeLockCredentialService.createReleaseSchedule({ + creator, + credentialIds, + releaseTimes: releaseTimes.map((t: string) => new Date(t)), + }); + + res.status(201).json({ + success: true, + data: { scheduleId }, + message: 'Release schedule created successfully', + }); + } catch (error: any) { + logger.error('Error creating schedule:', error); + res.status(400).json({ + success: false, + message: error.message || 'Failed to create schedule', + }); + } + } +); + +/** + * @route GET /api/time-lock/upcoming/:recipient + * @desc Get upcoming releases for notification + * @access Private + */ +router.get( + '/upcoming/:recipient', + authenticateToken, + [ + param('recipient').notEmpty().withMessage('Recipient address is required'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { recipient } = req.params; + const timeWindow = parseInt(req.query.timeWindow as string) || 86400000; // Default 24h + + const upcoming = await timeLockCredentialService.getUpcomingReleases( + recipient, + timeWindow + ); + + res.json({ + success: true, + data: upcoming, + count: upcoming.length, + }); + } catch (error: any) { + logger.error('Error getting upcoming releases:', error); + res.status(500).json({ + success: false, + message: 'Failed to get upcoming releases', + }); + } + } +); + +/** + * @route GET /api/time-lock/recipient/:recipient + * @desc Get all credentials by recipient + * @access Private + */ +router.get( + '/recipient/:recipient', + authenticateToken, + [ + param('recipient').notEmpty().withMessage('Recipient address is required'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { recipient } = req.params; + + const credentials = await timeLockCredentialService.getCredentialsByRecipient( + recipient + ); + + res.json({ + success: true, + data: credentials, + count: credentials.length, + }); + } catch (error: any) { + logger.error('Error getting credentials:', error); + res.status(500).json({ + success: false, + message: 'Failed to get credentials', + }); + } + } +); + +/** + * @route GET /api/time-lock/issuer/:issuer + * @desc Get all credentials by issuer + * @access Private + */ +router.get( + '/issuer/:issuer', + authenticateToken, + [ + param('issuer').notEmpty().withMessage('Issuer address is required'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { issuer } = req.params; + + const credentials = await timeLockCredentialService.getCredentialsByIssuer( + issuer + ); + + res.json({ + success: true, + data: credentials, + count: credentials.length, + }); + } catch (error: any) { + logger.error('Error getting credentials:', error); + res.status(500).json({ + success: false, + message: 'Failed to get credentials', + }); + } + } +); + +/** + * @route GET /api/time-lock/audit/:credentialId + * @desc Get audit trail for a credential + * @access Private + */ +router.get( + '/audit/:credentialId', + authenticateToken, + [ + param('credentialId').notEmpty().withMessage('Credential ID is required'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { credentialId } = req.params; + + const auditTrail = await timeLockCredentialService.getAuditTrail(credentialId); + + res.json({ + success: true, + data: auditTrail, + }); + } catch (error: any) { + logger.error('Error getting audit trail:', error); + + if (error.message.includes('not found')) { + return res.status(404).json({ + success: false, + message: error.message, + }); + } + + res.status(500).json({ + success: false, + message: 'Failed to get audit trail', + }); + } + } +); export default router; diff --git a/backend/src/routes/transactionRoutes.js b/backend/src/routes/transactionRoutes.js index 8559d93..35d7683 100644 --- a/backend/src/routes/transactionRoutes.js +++ b/backend/src/routes/transactionRoutes.js @@ -1,93 +1,362 @@ +const express = require('express'); +const { TransactionQueue } = require('./services/transactionQueue'); +const { StellarService } = require('./services/stellarService'); +const { MonitoringService } = require('./services/monitoringService'); +const { authenticateToken: authMiddleware } = require('../middleware/auth'); +const { + validateTransaction, + validateBulkTransaction, + validatePaginationQuery, + validateTransactionFilter, + validateAnalyticsQuery, + validateTransactionDependencies, + validateUserTierLimits +} = require('../middleware/validation'); + +const router = express.Router(); + +// Initialize services +const transactionQueue = new TransactionQueue(); +const stellarService = new StellarService(); +const monitoringService = new MonitoringService(); + /** - * @openapi - * tags: - * - name: Transaction Routes - * description: API transaction routing and management + * Submit a new transaction to the queue + * POST /api/transactions/submit */ +router.post('/submit', authMiddleware, validateTransaction, validateTransactionDependencies, async (req, res) => { + try { + const { type, payload, priority, userId, dependencies } = req.body; + + const transaction = await transactionQueue.enqueue({ + type, + payload, + priority: priority || 'medium', + userId, + dependencies: dependencies || [], + submittedAt: new Date(), + }); -const express = require("express"); -const router = express.Router(); -const { authenticate } = require("../middleware/auth"); -const transactionRoutesController = require("../controllers/transactionRoutesController"); + monitoringService.trackTransactionSubmitted(transaction); + + res.status(201).json({ + success: true, + data: { + transactionId: transaction.id, + status: transaction.status, + queuePosition: transaction.queuePosition, + estimatedProcessingTime: transaction.estimatedProcessingTime, + }, + }); + } catch (error) { + console.error('Transaction submission error:', error); + res.status(500).json({ + success: false, + message: 'Failed to submit transaction', + error: error.message, + }); + } +}); + +/** + * Get transaction status + * GET /api/transactions/:transactionId/status + */ +router.get('/:transactionId/status', authMiddleware, async (req, res) => { + try { + const { transactionId } = req.params; + const transaction = await transactionQueue.getTransaction(transactionId); + + if (!transaction) { + return res.status(404).json({ + success: false, + message: 'Transaction not found', + }); + } + + res.json({ + success: true, + data: { + id: transaction.id, + status: transaction.status, + type: transaction.type, + priority: transaction.priority, + submittedAt: transaction.submittedAt, + processedAt: transaction.processedAt, + completedAt: transaction.completedAt, + retryCount: transaction.retryCount, + maxRetries: transaction.maxRetries, + stellarTransactionHash: transaction.stellarTransactionHash, + errorMessage: transaction.errorMessage, + queuePosition: transaction.queuePosition, + estimatedProcessingTime: transaction.estimatedProcessingTime, + }, + }); + } catch (error) { + console.error('Transaction status query error:', error); + res.status(500).json({ + success: false, + message: 'Failed to get transaction status', + error: error.message, + }); + } +}); -router.use(authenticate); +/** + * Get queue statistics + * GET /api/transactions/queue/stats + */ +router.get('/queue/stats', authMiddleware, async (req, res) => { + try { + const stats = await transactionQueue.getQueueStats(); + const monitoringStats = await monitoringService.getQueueMetrics(); + + res.json({ + success: true, + data: { + ...stats, + monitoring: monitoringStats, + timestamp: new Date(), + }, + }); + } catch (error) { + console.error('Queue stats error:', error); + res.status(500).json({ + success: false, + message: 'Failed to get queue statistics', + error: error.message, + }); + } +}); /** - * @openapi - * /api/transaction-routes: - * get: - * tags: [Transaction Routes] - * summary: List all transaction routes - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Routes listed + * Get user's transaction history + * GET /api/transactions/user/:userId */ -router.get("/", transactionRoutesController.listRoutes); +router.get('/user/:userId', authMiddleware, validatePaginationQuery, validateTransactionFilter, async (req, res) => { + try { + const { userId } = req.params; + const { page = 1, limit = 20, status, type } = req.query; + + const transactions = await transactionQueue.getUserTransactions(userId, { + page: parseInt(page), + limit: parseInt(limit), + status, + type, + }); + + res.json({ + success: true, + data: transactions, + }); + } catch (error) { + console.error('User transactions query error:', error); + res.status(500).json({ + success: false, + message: 'Failed to get user transactions', + error: error.message, + }); + } +}); /** - * @openapi - * /api/transaction-routes/{routeId}: - * get: - * tags: [Transaction Routes] - * summary: Get transaction route details - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: routeId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Route details retrieved - * put: - * tags: [Transaction Routes] - * summary: Update transaction route - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: routeId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Route updated - * delete: - * tags: [Transaction Routes] - * summary: Delete transaction route - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: routeId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Route deleted + * Cancel a pending transaction + * DELETE /api/transactions/:transactionId */ -router.get("/:routeId", transactionRoutesController.getRoute); -router.put("/:routeId", transactionRoutesController.updateRoute); -router.delete("/:routeId", transactionRoutesController.deleteRoute); +router.delete('/:transactionId', authMiddleware, async (req, res) => { + try { + const { transactionId } = req.params; + const userId = req.user.id; + + const success = await transactionQueue.cancelTransaction(transactionId, userId); + + if (!success) { + return res.status(404).json({ + success: false, + message: 'Transaction not found or cannot be cancelled', + }); + } + + monitoringService.trackTransactionCancelled(transactionId); + + res.json({ + success: true, + message: 'Transaction cancelled successfully', + }); + } catch (error) { + console.error('Transaction cancellation error:', error); + res.status(500).json({ + success: false, + message: 'Failed to cancel transaction', + error: error.message, + }); + } +}); + +/** + * Retry a failed transaction + * POST /api/transactions/:transactionId/retry + */ +router.post('/:transactionId/retry', authMiddleware, async (req, res) => { + try { + const { transactionId } = req.params; + const userId = req.user.id; + + const transaction = await transactionQueue.retryTransaction(transactionId, userId); + + if (!transaction) { + return res.status(404).json({ + success: false, + message: 'Transaction not found or cannot be retried', + }); + } + + monitoringService.trackTransactionRetried(transaction); + + res.json({ + success: true, + data: { + transactionId: transaction.id, + status: transaction.status, + retryCount: transaction.retryCount, + queuePosition: transaction.queuePosition, + }, + }); + } catch (error) { + console.error('Transaction retry error:', error); + res.status(500).json({ + success: false, + message: 'Failed to retry transaction', + error: error.message, + }); + } +}); + +/** + * Submit bulk transactions + * POST /api/transactions/bulk + */ +router.post('/bulk', authMiddleware, validateBulkTransaction, validateUserTierLimits, async (req, res) => { + try { + const { transactions, options = {} } = req.body; + const userId = req.user.id; + + if (!Array.isArray(transactions) || transactions.length === 0) { + return res.status(400).json({ + success: false, + message: 'Transactions array is required and cannot be empty', + }); + } + + if (transactions.length > 100) { + return res.status(400).json({ + success: false, + message: 'Maximum 100 transactions allowed per bulk submission', + }); + } + + const results = await transactionQueue.enqueueBulk(transactions.map(tx => ({ + ...tx, + userId, + submittedAt: new Date(), + })), options); + + monitoringService.trackBulkTransactionSubmitted(results); + + res.status(201).json({ + success: true, + data: { + submitted: results.successful.length, + failed: results.failed.length, + transactionIds: results.successful.map(tx => tx.id), + errors: results.failed.map(f => ({ index: f.index, error: f.error })), + bulkId: results.bulkId, + }, + }); + } catch (error) { + console.error('Bulk transaction submission error:', error); + res.status(500).json({ + success: false, + message: 'Failed to submit bulk transactions', + error: error.message, + }); + } +}); + +/** + * Get transaction analytics + * GET /api/transactions/analytics + */ +router.get('/analytics', authMiddleware, validateAnalyticsQuery, async (req, res) => { + try { + const { timeRange = '24h', userId, type } = req.query; + + const analytics = await monitoringService.getTransactionAnalytics({ + timeRange, + userId, + type, + }); + + res.json({ + success: true, + data: analytics, + }); + } catch (error) { + console.error('Transaction analytics error:', error); + res.status(500).json({ + success: false, + message: 'Failed to get transaction analytics', + error: error.message, + }); + } +}); + +/** + * Get network status and gas optimization info + * GET /api/transactions/network/status + */ +router.get('/network/status', authMiddleware, async (req, res) => { + try { + const networkStatus = await stellarService.getNetworkStatus(); + const gasOptimization = await stellarService.getGasOptimizationInfo(); + + res.json({ + success: true, + data: { + network: networkStatus, + gasOptimization, + timestamp: new Date(), + }, + }); + } catch (error) { + console.error('Network status error:', error); + res.status(500).json({ + success: false, + message: 'Failed to get network status', + error: error.message, + }); + } +}); /** - * @openapi - * /api/transaction-routes/stats: - * get: - * tags: [Transaction Routes] - * summary: Get route usage statistics - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Statistics retrieved + * Webhook endpoint for Stellar transaction confirmations + * POST /api/transactions/webhook/stellar */ -router.get("/stats", transactionRoutesController.getRouteStats); +router.post('/webhook/stellar', async (req, res) => { + try { + const { transactionHash, status, result } = req.body; + + await transactionQueue.handleStellarWebhook(transactionHash, status, result); + + res.json({ success: true }); + } catch (error) { + console.error('Stellar webhook error:', error); + res.status(500).json({ + success: false, + message: 'Failed to process webhook', + error: error.message, + }); + } +}); module.exports = router; diff --git a/backend/src/routes/transactions.js b/backend/src/routes/transactions.js index 9893885..72368d1 100644 --- a/backend/src/routes/transactions.js +++ b/backend/src/routes/transactions.js @@ -1,103 +1,596 @@ +const express = require('express'); +const Transaction = require('../models/Transaction'); +const transactionQueue = require('../services/transactionQueue'); +const transactionProcessor = require('../workers/transactionProcessor'); +const transactionEvents = require('../events/transactionEvents'); +const stellarUtils = require('../utils/stellarUtils'); +const logger = require('../utils/logger'); +const { transactionLimiter } = require('../middleware/rateLimiter'); +const { validate } = require('../middleware/validate'); +const { + createTransactionSchema, + getTransactionsSchema, + getUserEventsQuerySchema, + transactionIdParamSchema, + userIdParamSchema, + accountIdParamSchema, + retryFailedSchema, + estimateFeeSchema, +} = require('../middleware/schemas/transactionSchemas'); + +const router = express.Router(); + /** - * @openapi - * tags: - * - name: Transactions - * description: Transaction history and management + * POST /api/transactions + * Create and queue a new transaction */ +router.post('/', transactionLimiter, validate({ body: createTransactionSchema }), async (req, res) => { + try { + const value = req.body; -const express = require("express"); -const router = express.Router(); -const { authenticate, authorize } = require("../middleware/auth"); -const transactionController = require("../controllers/transactionController"); + // Validate Stellar addresses if provided + if (!stellarUtils.validateAddress(value.sourceAccount)) { + return res.status(400).json({ + success: false, + message: 'Invalid source account address' + }); + } + + if (value.destinationAccount && !stellarUtils.validateAddress(value.destinationAccount)) { + return res.status(400).json({ + success: false, + message: 'Invalid destination account address' + }); + } + + // Create and queue transaction + const transaction = await transactionQueue.enqueueTransaction(value, { + priority: value.priority, + maxRetries: value.maxRetries + }); + + res.status(201).json({ + success: true, + message: 'Transaction queued successfully', + data: { + transactionId: transaction.id, + userId: transaction.userId, + type: transaction.type, + status: transaction.status, + priority: transaction.priority, + createdAt: transaction.createdAt + } + }); + + } catch (error) { + logger.error('Error creating transaction:', error); + res.status(500).json({ + success: false, + message: 'Internal server error', + error: error.message + }); + } +}); + +/** + * GET /api/transactions + * Get transactions with optional filtering + */ +router.get('/', validate({ query: getTransactionsSchema }), async (req, res) => { + try { + const value = req.query; + + const transactions = await Transaction.findByUser(value.userId, { + status: value.status, + type: value.type, + limit: value.limit + }); + + const totalCount = await Transaction.countDocuments({ + userId: value.userId, + ...(value.status && { status: value.status }), + ...(value.type && { type: value.type }) + }); + + res.json({ + success: true, + data: { + transactions: transactions.map(tx => ({ + id: tx.id, + userId: tx.userId, + type: tx.type, + status: tx.status, + priority: tx.priority, + sourceAccount: tx.sourceAccount, + destinationAccount: tx.destinationAccount, + amount: tx.amount, + asset: tx.asset, + stellarTransactionHash: tx.stellarTransactionHash, + stellarLedger: tx.stellarLedger, + retryCount: tx.retryCount, + lastError: tx.lastError, + createdAt: tx.createdAt, + updatedAt: tx.updatedAt, + submittedAt: tx.submittedAt, + completedAt: tx.completedAt + })), + pagination: { + total: totalCount, + limit: value.limit, + offset: value.offset, + hasMore: totalCount > value.offset + value.limit + } + } + }); + + } catch (error) { + logger.error('Error getting transactions:', error); + res.status(500).json({ + success: false, + message: 'Internal server error', + error: error.message + }); + } +}); + +/** + * GET /api/transactions/:transactionId + * Get specific transaction details + */ +router.get('/:transactionId', validate(transactionIdParamSchema, 'params'), async (req, res) => { + try { + const { transactionId } = req.params; + + const transaction = await Transaction.findOne({ id: transactionId }); + + if (!transaction) { + return res.status(404).json({ + success: false, + message: 'Transaction not found' + }); + } + + res.json({ + success: true, + data: { + id: transaction.id, + userId: transaction.userId, + type: transaction.type, + status: transaction.status, + priority: transaction.priority, + sourceAccount: transaction.sourceAccount, + destinationAccount: transaction.destinationAccount, + amount: transaction.amount, + asset: transaction.asset, + transactionXdr: transaction.transactionXdr, + signedTransactionXdr: transaction.signedTransactionXdr, + stellarTransactionHash: transaction.stellarTransactionHash, + stellarLedger: transaction.stellarLedger, + retryCount: transaction.retryCount, + maxRetries: transaction.maxRetries, + lastError: transaction.lastError, + metadata: transaction.metadata, + createdAt: transaction.createdAt, + updatedAt: transaction.updatedAt, + submittedAt: transaction.submittedAt, + completedAt: transaction.completedAt + } + }); + + } catch (error) { + logger.error('Error getting transaction:', error); + res.status(500).json({ + success: false, + message: 'Internal server error', + error: error.message + }); + } +}); + +/** + * GET /api/transactions/:transactionId/status + * Get transaction status with additional details + */ +router.get('/:transactionId/status', validate(transactionIdParamSchema, 'params'), async (req, res) => { + try { + const { transactionId } = req.params; + + const transaction = await Transaction.findOne({ id: transactionId }); + + if (!transaction) { + return res.status(404).json({ + success: false, + message: 'Transaction not found' + }); + } + + let stellarStatus = null; + if (transaction.stellarTransactionHash) { + try { + stellarStatus = await stellarUtils.getTransactionStatus(transaction.stellarTransactionHash); + } catch (error) { + logger.warn(`Error getting Stellar status for ${transactionId}:`, error); + } + } + + const queuePosition = await transactionQueue.getQueuePosition(transactionId); + + res.json({ + success: true, + data: { + transactionId: transaction.id, + status: transaction.status, + queuePosition, + stellarStatus, + retryCount: transaction.retryCount, + maxRetries: transaction.maxRetries, + lastError: transaction.lastError, + canRetry: transaction.canRetry(), + timestamps: { + createdAt: transaction.createdAt, + updatedAt: transaction.updatedAt, + submittedAt: transaction.submittedAt, + completedAt: transaction.completedAt + } + } + }); + + } catch (error) { + logger.error('Error getting transaction status:', error); + res.status(500).json({ + success: false, + message: 'Internal server error', + error: error.message + }); + } +}); + +/** + * POST /api/transactions/:transactionId/retry + * Retry a failed transaction + */ +router.post('/:transactionId/retry', validate(transactionIdParamSchema, 'params'), async (req, res) => { + try { + const { transactionId } = req.params; + + const transaction = await Transaction.findOne({ id: transactionId }); + + if (!transaction) { + return res.status(404).json({ + success: false, + message: 'Transaction not found' + }); + } + + if (!transaction.canRetry()) { + return res.status(400).json({ + success: false, + message: 'Transaction cannot be retried', + data: { + status: transaction.status, + retryCount: transaction.retryCount, + maxRetries: transaction.maxRetries + } + }); + } + + // Reset transaction for retry + await transaction.resetForRetry(); + + // Re-queue transaction + await transactionQueue.enqueueTransaction({ + id: transaction.id, + userId: transaction.userId, + type: transaction.type, + sourceAccount: transaction.sourceAccount, + destinationAccount: transaction.destinationAccount, + amount: transaction.amount, + asset: transaction.asset, + transactionXdr: transaction.transactionXdr, + signedTransactionXdr: transaction.signedTransactionXdr, + metadata: transaction.metadata + }, { + priority: transaction.priority, + maxRetries: transaction.maxRetries + }); + + res.json({ + success: true, + message: 'Transaction queued for retry', + data: { + transactionId: transaction.id, + retryCount: transaction.retryCount, + status: transaction.status + } + }); -router.use(authenticate, authorize("admin")); + } catch (error) { + logger.error('Error retrying transaction:', error); + res.status(500).json({ + success: false, + message: 'Internal server error', + error: error.message + }); + } +}); /** - * @openapi - * /api/transactions: - * get: - * tags: [Transactions] - * summary: List all transactions - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Transactions listed + * GET /api/transactions/queue/stats + * Get queue statistics */ -router.get("/", transactionController.listTransactions); +router.get('/queue/stats', validate({}), async (req, res) => { + try { + const queueStats = await transactionQueue.getQueueStats(); + const processorStats = transactionProcessor.getStats(); + const eventStats = transactionEvents.getStats(); + + res.json({ + success: true, + data: { + queue: queueStats, + processor: processorStats, + events: eventStats + } + }); + + } catch (error) { + logger.error('Error getting queue stats:', error); + res.status(500).json({ + success: false, + message: 'Internal server error', + error: error.message + }); + } +}); + +/** + * GET /api/transactions/user/:userId/pending + * Get pending transactions for a user + */ +router.get('/user/:userId/pending', validate(userIdParamSchema, 'params'), async (req, res) => { + try { + const { userId } = req.params; + + const pendingTransactions = await transactionQueue.getUserPendingTransactions(userId); + + res.json({ + success: true, + data: { + transactions: pendingTransactions.map(tx => ({ + id: tx.id, + type: tx.type, + priority: tx.priority, + createdAt: tx.createdAt, + queuePosition: null // Will be populated if needed + })), + count: pendingTransactions.length + } + }); + + } catch (error) { + logger.error('Error getting pending transactions:', error); + res.status(500).json({ + success: false, + message: 'Internal server error', + error: error.message + }); + } +}); + +/** + * GET /api/transactions/user/:userId/events + * Get recent events for a user + */ +router.get('/user/:userId/events', validate(getUserEventsQuerySchema), async (req, res) => { + try { + const { userId } = req.params; + const { limit = 10 } = req.query; + + const events = await transactionEvents.getUserRecentEvents(userId, parseInt(limit)); + + res.json({ + success: true, + data: { + events, + count: events.length + } + }); + + } catch (error) { + logger.error('Error getting user events:', error); + res.status(500).json({ + success: false, + message: 'Internal server error', + error: error.message + }); + } +}); + +/** + * POST /api/transactions/admin/clear-queue + * Clear the transaction queue (admin only) + */ +router.post('/admin/clear-queue', validate({}), async (req, res) => { + try { + // This should be protected by admin middleware in production + await transactionQueue.clearQueue(); + + res.json({ + success: true, + message: 'Transaction queue cleared successfully' + }); + + } catch (error) { + logger.error('Error clearing queue:', error); + res.status(500).json({ + success: false, + message: 'Internal server error', + error: error.message + }); + } +}); + +/** + * POST /api/transactions/admin/retry-failed + * Retry all failed transactions (admin only) + */ +router.post('/admin/retry-failed', validate(retryFailedSchema, 'body'), async (req, res) => { + try { + // This should be protected by admin middleware in production + const { limit = 10 } = req.body; + + const retriedCount = await transactionProcessor.retryFailedTransactions(limit); + + res.json({ + success: true, + message: `Retried ${retriedCount} failed transactions`, + data: { + retriedCount + } + }); + + } catch (error) { + logger.error('Error retrying failed transactions:', error); + res.status(500).json({ + success: false, + message: 'Internal server error', + error: error.message + }); + } +}); /** - * @openapi - * /api/transactions/{transactionId}: - * get: - * tags: [Transactions] - * summary: Get transaction details - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: transactionId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Transaction details retrieved + * GET /api/transactions/stellar/account/:accountId + * Get Stellar account information */ -router.get("/:transactionId", transactionController.getTransaction); +router.get('/stellar/account/:accountId', validate(accountIdParamSchema, 'params'), async (req, res) => { + try { + const { accountId } = req.params; + + if (!stellarUtils.validateAddress(accountId)) { + return res.status(400).json({ + success: false, + message: 'Invalid Stellar account address' + }); + } + + const accountInfo = await stellarUtils.getAccount(accountId); + const balances = await stellarUtils.getAccountBalances(accountId); + + res.json({ + success: true, + data: { + account: accountInfo, + balances + } + }); + + } catch (error) { + if (error.response && error.response.status === 404) { + return res.status(404).json({ + success: false, + message: 'Account not found' + }); + } + + logger.error('Error getting Stellar account:', error); + res.status(500).json({ + success: false, + message: 'Internal server error', + error: error.message + }); + } +}); /** - * @openapi - * /api/transactions/{transactionId}/verify: - * post: - * tags: [Transactions] - * summary: Verify transaction - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: transactionId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Transaction verified + * GET /api/transactions/stellar/fee-stats + * Get Stellar network fee statistics */ -router.post("/:transactionId/verify", transactionController.verifyTransaction); +router.get('/stellar/fee-stats', validate({}), async (req, res) => { + try { + const feeStats = await stellarUtils.getFeeStats(); + + res.json({ + success: true, + data: feeStats + }); + + } catch (error) { + logger.error('Error getting fee stats:', error); + res.status(500).json({ + success: false, + message: 'Internal server error', + error: error.message + }); + } +}); /** - * @openapi - * /api/transactions/user/{userId}: - * get: - * tags: [Transactions] - * summary: Get transactions by user - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: User transactions retrieved + * POST /api/transactions/stellar/estimate-fee + * Estimate transaction fee */ -router.get("/user/:userId", transactionController.getUserTransactions); +router.post('/stellar/estimate-fee', validate(estimateFeeSchema), async (req, res) => { + try { + const { priority = 'medium' } = req.body; + + const estimatedFee = await stellarUtils.estimateSmartFee(priority); + + res.json({ + success: true, + data: { + priority, + estimatedFee, + feeInXLM: (estimatedFee / 10000000).toFixed(7) // Convert stroops to XLM + } + }); + + } catch (error) { + logger.error('Error estimating fee:', error); + res.status(500).json({ + success: false, + message: 'Internal server error', + error: error.message + }); + } +}); /** - * @openapi - * /api/transactions/stats: - * get: - * tags: [Transactions] - * summary: Get transaction statistics - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Statistics retrieved + * POST /api/transactions/validate + * Validate transaction data before submission */ -router.get("/stats", transactionController.getTransactionStats); +router.post('/validate', validate({ body: createTransactionSchema }), async (req, res) => { + try { + const value = req.body; + + const validationResults = { + sourceAccount: stellarUtils.validateAddress(value.sourceAccount), + destinationAccount: value.destinationAccount ? stellarUtils.validateAddress(value.destinationAccount) : true, + asset: true + }; + + if (value.asset && value.asset.code !== 'XLM' && value.asset.issuer) { + validationResults.asset = stellarUtils.validateAddress(value.asset.issuer); + } + + const isValid = Object.values(validationResults).every(result => result === true); + + res.json({ + success: true, + data: { + isValid, + validationResults, + estimatedFee: await stellarUtils.estimateSmartFee(value.priority) + } + }); + + } catch (error) { + logger.error('Error validating transaction:', error); + res.status(500).json({ + success: false, + message: 'Internal server error', + error: error.message + }); + } +}); module.exports = router; diff --git a/backend/src/routes/translation.js b/backend/src/routes/translation.js deleted file mode 100644 index 5df1bf8..0000000 --- a/backend/src/routes/translation.js +++ /dev/null @@ -1,101 +0,0 @@ -/** - * @openapi - * tags: - * - name: Translation - * description: Multi-language translation services - */ - -const express = require("express"); -const router = express.Router(); -const { authenticate } = require("../middleware/auth"); -const translationController = require("../controllers/translationController"); - -/** - * @openapi - * /api/translation/translate: - * post: - * tags: [Translation] - * summary: Translate text content - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Text translated - */ -router.post("/translate", authenticate, translationController.translate); - -/** - * @openapi - * /api/translation/languages: - * get: - * tags: [Translation] - * summary: Get supported languages - * responses: - * '200': - * description: Languages retrieved - */ -router.get("/languages", translationController.getLanguages); - -/** - * @openapi - * /api/translation/auto-detect: - * post: - * tags: [Translation] - * summary: Auto-detect language - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Language detected - */ -router.post("/auto-detect", authenticate, translationController.detectLanguage); - -/** - * @openapi - * /api/translation/batch: - * post: - * tags: [Translation] - * summary: Translate content in batch - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Batch translated - */ -router.post("/batch", authenticate, translationController.batchTranslate); - -/** - * @openapi - * /api/translation/content/{contentId}: - * get: - * tags: [Translation] - * summary: Get translation status for content - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: contentId - * required: true - * schema: - * type: string - * responses: - * '200': - * description: Translation status retrieved - */ -router.get("/content/:contentId", authenticate, translationController.getContentTranslation); - -/** - * @openapi - * /api/translation/usage: - * get: - * tags: [Translation] - * summary: Get translation usage statistics - * security: - * - bearerAuth: [] - * responses: - * '200': - * description: Usage statistics retrieved - */ -router.get("/usage", authenticate, translationController.getUsageStats); - -module.exports = router; diff --git a/backend/src/routes/translation.ts b/backend/src/routes/translation.ts index 4642546..e465c4c 100644 --- a/backend/src/routes/translation.ts +++ b/backend/src/routes/translation.ts @@ -1,10 +1,3 @@ -/** - * @openapi - * tags: - * - name: Translation - * description: Real-time multi-language translation services - */ - import express, { Request, Response } from 'express'; import { body, param, validationResult } from 'express-validator'; import { realTimeTranslationService } from '../services/realTimeTranslationService'; @@ -25,38 +18,9 @@ const validateRequest = (req: Request, res: Response, next: Function) => { }; /** - * @openapi - * /api/translate/text: - * post: - * tags: [Translation] - * summary: Translate text content - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - text - * - sourceLanguage - * - targetLanguage - * properties: - * text: - * type: string - * sourceLanguage: - * type: string - * targetLanguage: - * type: string - * context: - * type: string - * contentType: - * type: string - * enum: [course, subtitle, interaction, general] - * responses: - * '200': - * description: Translation completed + * @route POST /api/translate/text + * @desc Translate text content + * @access Private */ router.post( '/text', @@ -97,27 +61,9 @@ router.post( ); /** - * @openapi - * /api/translate/batch: - * post: - * tags: [Translation] - * summary: Batch translate multiple texts - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - requests - * properties: - * requests: - * type: array - * responses: - * '200': - * description: Batch translation completed + * @route POST /api/translate/batch + * @desc Batch translate multiple texts + * @access Private */ router.post( '/batch', @@ -151,30 +97,9 @@ router.post( ); /** - * @openapi - * /api/translate/subtitles: - * post: - * tags: [Translation] - * summary: Translate and synchronize subtitles - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - segments - * - targetLanguage - * properties: - * segments: - * type: array - * targetLanguage: - * type: string - * responses: - * '200': - * description: Subtitle translation completed + * @route POST /api/translate/subtitles + * @desc Translate and synchronize subtitles + * @access Private */ router.post( '/subtitles', @@ -212,35 +137,9 @@ router.post( ); /** - * @openapi - * /api/translate/correction: - * post: - * tags: [Translation] - * summary: Submit translation correction - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - originalText - * - translation - * - correctedTranslation - * properties: - * originalText: - * type: string - * translation: - * type: string - * correctedTranslation: - * type: string - * context: - * type: string - * responses: - * '200': - * description: Correction submitted + * @route POST /api/translate/correction + * @desc Submit a translation correction for quality improvement + * @access Private */ router.post( '/correction', @@ -278,23 +177,9 @@ router.post( ); /** - * @openapi - * /api/translate/quality/{contentType}: - * get: - * tags: [Translation] - * summary: Get translation quality metrics - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: contentType - * required: false - * schema: - * type: string - * enum: [course, subtitle, interaction, general] - * responses: - * '200': - * description: Quality metrics retrieved + * @route GET /api/translate/quality/:contentType? + * @desc Get translation quality metrics + * @access Private */ router.get( '/quality/:contentType?', diff --git a/backend/src/routes/userRoutes.ts b/backend/src/routes/userRoutes.ts index a2e88d7..5b14021 100644 --- a/backend/src/routes/userRoutes.ts +++ b/backend/src/routes/userRoutes.ts @@ -1,10 +1,3 @@ -/** - * @openapi - * tags: - * - name: Users - * description: User profile and settings management - */ - import { Router, Request, Response, NextFunction } from "express"; import { body, param, validationResult } from "express-validator"; import { userController } from "../controllers/userController"; @@ -23,51 +16,8 @@ const validateRequest = (req: Request, res: Response, next: NextFunction) => { }; /** - * @openapi - * /api/users/profile/{address}: - * get: - * tags: [Users] - * summary: Get user profile by Stellar address - * parameters: - * - in: path - * name: address - * required: true - * schema: - * type: string - * description: Stellar wallet address - * responses: - * 200: - * description: User profile retrieved - * 400: - * description: Invalid address - * put: - * tags: [Users] - * summary: Update user profile - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * username: - * type: string - * email: - * type: string - * format: email - * bio: - * type: string - * parameters: - * - in: path - * name: address - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Profile updated - * 400: - * description: Validation error + * @route GET /api/users/profile/:address + * @desc Get user profile by Stellar address */ router.get( "/profile/:address", @@ -93,38 +43,8 @@ router.put( ); /** - * @openapi - * /api/users/settings/{userId}: - * get: - * tags: [Users] - * summary: Get user settings - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * responses: - * 200: - * description: User settings retrieved - * put: - * tags: [Users] - * summary: Update user settings - * parameters: - * - in: path - * name: userId - * required: true - * schema: - * type: string - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * responses: - * 200: - * description: Settings updated + * @route GET /api/users/settings/:userId + * @desc Get user settings */ router.get( "/settings/:userId", diff --git a/backend/src/routes/vrf.ts b/backend/src/routes/vrf.ts index 6a1d681..d917af3 100644 --- a/backend/src/routes/vrf.ts +++ b/backend/src/routes/vrf.ts @@ -1,55 +1,342 @@ +import express, { Request, Response } from 'express'; +import { body, param, validationResult } from 'express-validator'; +import { vrfService } from '../services/vrfService'; +import { authenticateToken } from '../middleware/auth'; +import logger from '../utils/logger'; + +const router: import('express').Router = express.Router(); + +const validateRequest = (req: Request, res: Response, next: Function) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ + success: false, + errors: errors.array() + }); + } + next(); +}; + /** - * @openapi - * tags: - * - name: VRF - * description: Verifiable Random Function for secure randomness + * @route POST /api/vrf/request + * @desc Request verifiable random number + * @access Private */ +router.post( + '/request', + authenticateToken, + [ + body('seed').notEmpty().withMessage('Seed is required'), + body('purpose').notEmpty().withMessage('Purpose is required'), + body('context').optional(), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { seed, purpose, context } = req.body; + const requester = (req.user as any)?.address; -import { Router } from "express"; -import * as vrfController from "../controllers/vrfController"; + if (!requester) { + return res.status(401).json({ + success: false, + message: 'Unauthorized' + }); + } -const router: Router = Router(); + const requestId = await vrfService.requestRandomness( + requester, + seed, + purpose, + context || '' + ); + + res.status(201).json({ + success: true, + data: { requestId }, + message: 'VRF request created successfully', + }); + } catch (error: any) { + logger.error('Error requesting randomness:', error); + res.status(400).json({ + success: false, + message: error.message || 'Failed to request randomness', + }); + } + } +); /** - * @openapi - * /api/vrf/generate: - * post: - * tags: [VRF] - * summary: Generate verifiable random value - * responses: - * '200': - * description: Random value generated + * @route POST /api/vrf/generate + * @desc Generate random number for specific purpose + * @access Private */ -router.post("/generate", vrfController.generateRandom); +router.post( + '/generate', + authenticateToken, + [ + body('purpose').notEmpty().withMessage('Purpose is required'), + body('seed').notEmpty().withMessage('Seed is required'), + body('min').isInt({ min: 0 }).withMessage('Minimum must be a positive integer'), + body('max').isInt({ min: 0 }).withMessage('Maximum must be a positive integer'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { purpose, seed, min, max } = req.body; + const requester = (req.user as any)?.address; + + if (!requester) { + return res.status(401).json({ + success: false, + message: 'Unauthorized' + }); + } + + const randomValue = await vrfService.generateRandomForPurpose( + requester, + purpose, + seed, + parseInt(min), + parseInt(max) + ); + + res.json({ + success: true, + data: { + purpose, + randomValue, + range: { min, max }, + }, + message: 'Random value generated successfully', + }); + } catch (error: any) { + logger.error('Error generating random value:', error); + res.status(400).json({ + success: false, + message: error.message || 'Failed to generate random value', + }); + } + } +); /** - * @openapi - * /api/vrf/verify: - * post: - * tags: [VRF] - * summary: Verify random value proof - * responses: - * '200': - * description: Proof verified + * @route GET /api/vrf/request/:requestId + * @desc Get VRF request details + * @access Private */ -router.post("/verify", vrfController.verifyProof); +router.get( + '/request/:requestId', + authenticateToken, + [ + param('requestId').notEmpty().withMessage('Request ID is required'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { requestId } = req.params; + + const request = await vrfService.getRequest(requestId); + + if (!request) { + return res.status(404).json({ + success: false, + message: 'Request not found', + }); + } + + res.json({ + success: true, + data: request, + }); + } catch (error: any) { + logger.error('Error getting request:', error); + res.status(500).json({ + success: false, + message: 'Failed to get request', + }); + } + } +); + +/** + * @route GET /api/vrf/user/:user/requests + * @desc Get all VRF requests by user + * @access Private + */ +router.get( + '/user/:user/requests', + authenticateToken, + [ + param('user').notEmpty().withMessage('User address is required'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { user } = req.params; + + const requests = await vrfService.getRequestsByUser(user); + + res.json({ + success: true, + data: requests, + count: requests.length, + }); + } catch (error: any) { + logger.error('Error getting requests:', error); + res.status(500).json({ + success: false, + message: 'Failed to get requests', + }); + } + } +); /** - * @openapi - * /api/vrf/{seed}: - * get: - * tags: [VRF] - * summary: Get VRF output for seed - * parameters: - * - in: path - * name: seed - * required: true - * schema: - * type: string - * responses: - * '200': - * description: VRF output retrieved + * @route GET /api/vrf/beacon/latest + * @desc Get latest randomness beacon + * @access Private */ -router.get("/:seed", vrfController.getVRFOutput); +router.get( + '/beacon/latest', + authenticateToken, + async (req: Request, res: Response) => { + try { + const beacon = await vrfService.getLatestBeacon(); + + if (!beacon) { + return res.status(404).json({ + success: false, + message: 'No beacon available', + }); + } + + res.json({ + success: true, + data: beacon, + }); + } catch (error: any) { + logger.error('Error getting beacon:', error); + res.status(500).json({ + success: false, + message: 'Failed to get beacon', + }); + } + } +); + +/** + * @route GET /api/vrf/stats + * @desc Get VRF system statistics + * @access Private + */ +router.get( + '/stats', + authenticateToken, + async (req: Request, res: Response) => { + try { + const stats = await vrfService.getStats(); + + res.json({ + success: true, + data: stats, + }); + } catch (error: any) { + logger.error('Error getting stats:', error); + res.status(500).json({ + success: false, + message: 'Failed to get statistics', + }); + } + } +); + +/** + * @route POST /api/vrf/commit + * @desc Commit to a value (commit-reveal scheme) + * @access Private + */ +router.post( + '/commit', + authenticateToken, + [ + body('commitmentHash').notEmpty().withMessage('Commitment hash is required'), + body('validUntil').isISO8601().withMessage('Valid until date is required'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { commitmentHash, validUntil } = req.body; + const committer = (req.user as any)?.address; + + if (!committer) { + return res.status(401).json({ + success: false, + message: 'Unauthorized' + }); + } + + await vrfService.commit( + committer, + commitmentHash, + new Date(validUntil) + ); + + res.json({ + success: true, + message: 'Commitment recorded successfully', + }); + } catch (error: any) { + logger.error('Error creating commitment:', error); + res.status(400).json({ + success: false, + message: error.message || 'Failed to create commitment', + }); + } + } +); + +/** + * @route POST /api/vrf/reveal + * @desc Reveal committed value + * @access Private + */ +router.post( + '/reveal', + authenticateToken, + [ + body('revealedValue').notEmpty().withMessage('Revealed value is required'), + ], + validateRequest, + async (req: Request, res: Response) => { + try { + const { revealedValue } = req.body; + const committer = (req.user as any)?.address; + + if (!committer) { + return res.status(401).json({ + success: false, + message: 'Unauthorized' + }); + } + + const isValid = await vrfService.reveal(committer, revealedValue); + + res.json({ + success: true, + data: { + isValid, + revealedValue, + }, + message: 'Value revealed successfully', + }); + } catch (error: any) { + logger.error('Error revealing value:', error); + res.status(400).json({ + success: false, + message: error.message || 'Failed to reveal value', + }); + } + } +); export default router;