@codai/memorai
Version:
Universal Database & Storage Service for CODAI Ecosystem - CBD Backend
629 lines • 22.6 kB
JavaScript
/**
* MEMORAI REST API Server
* Provides HTTP endpoints for database, storage, memory, and sync operations
*/
import { EventEmitter } from 'events';
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
import multer from 'multer';
export class MemoraiAPIServer extends EventEmitter {
constructor(memoraiService, config) {
super();
this._isRunning = false;
this.memoraiService = memoraiService;
this.config = config;
this.app = express();
// Configure multer for file uploads
this.upload = multer({
storage: multer.memoryStorage(),
limits: {
fileSize: config.storage.maxFileSize
},
fileFilter: (req, file, cb) => {
if (config.storage.allowedTypes.length === 0) {
return cb(null, true);
}
const isAllowed = config.storage.allowedTypes.some(type => file.mimetype.startsWith(type) || file.mimetype === type);
if (isAllowed) {
cb(null, true);
}
else {
cb(new Error(`File type not allowed: ${file.mimetype}`));
}
}
});
this.setupMiddleware();
this.setupRoutes();
this.setupErrorHandling();
}
// ==================== SERVER LIFECYCLE ====================
async start(port = 3001, host = 'localhost') {
if (this._isRunning) {
throw new Error('API server is already running');
}
try {
// Ensure memorai service is initialized
if (!this.memoraiService.isReady) {
await this.memoraiService.initialize();
}
// Start HTTP server
this.server = this.app.listen(port, host, () => {
this._isRunning = true;
this.emit('started', { port, host });
console.log(`🌐 MEMORAI API Server started on http://${host}:${port}`);
});
}
catch (error) {
console.error('Failed to start API server:', error);
this.emit('error', error);
throw error;
}
}
async stop() {
if (!this._isRunning)
return;
try {
if (this.server) {
this.server.close(() => {
this._isRunning = false;
this.emit('stopped');
console.log('🌐 MEMORAI API Server stopped');
});
}
}
catch (error) {
console.error('Error stopping API server:', error);
this.emit('error', error);
throw error;
}
}
// ==================== MIDDLEWARE SETUP ====================
setupMiddleware() {
// Security middleware
this.app.use(helmet());
// CORS
this.app.use(cors({
origin: this.config.security.cors.origins,
credentials: this.config.security.cors.credentials
}));
// Rate limiting
if (this.config.security.rateLimit.enabled) {
this.app.use(rateLimit({
windowMs: this.config.security.rateLimit.windowMs,
max: this.config.security.rateLimit.maxRequests,
message: 'Too many requests from this IP, please try again later',
standardHeaders: true,
legacyHeaders: false
}));
}
// Body parsing
this.app.use(express.json({ limit: '10mb' }));
this.app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Request logging
this.app.use((req, res, next) => {
console.log(`📥 ${req.method} ${req.path} - ${req.ip}`);
next();
});
// Authentication middleware (mock for now)
this.app.use((req, res, next) => {
// TODO: Implement real authentication with @codai/auth
req.user = {
id: req.headers['x-user-id'] || 'anonymous',
email: req.headers['x-user-email'] || 'anonymous@example.com',
name: req.headers['x-user-name'] || 'Anonymous User',
roles: ['user']
};
req.tenant = {
id: req.headers['x-tenant-id'] || 'default',
name: 'Default Tenant'
};
next();
});
}
// ==================== ROUTES SETUP ====================
setupRoutes() {
// Health check
this.app.get('/health', this.handleHealthCheck.bind(this));
// API info
this.app.get('/api/info', this.handleAPIInfo.bind(this));
// Database routes
this.app.post('/api/v1/database/:table', this.handleDatabaseCreate.bind(this));
this.app.get('/api/v1/database/:table/:id', this.handleDatabaseGet.bind(this));
this.app.get('/api/v1/database/:table', this.handleDatabaseFind.bind(this));
this.app.put('/api/v1/database/:table/:id', this.handleDatabaseUpdate.bind(this));
this.app.delete('/api/v1/database/:table/:id', this.handleDatabaseDelete.bind(this));
this.app.post('/api/v1/database/query', this.handleDatabaseQuery.bind(this));
// Storage routes
this.app.post('/api/v1/storage/upload', this.upload.single('file'), this.handleStorageUpload.bind(this));
this.app.get('/api/v1/storage/:path(*)', this.handleStorageDownload.bind(this));
this.app.delete('/api/v1/storage/:path(*)', this.handleStorageDelete.bind(this));
this.app.get('/api/v1/storage', this.handleStorageList.bind(this));
// Memory routes
this.app.post('/api/v1/memory', this.handleMemoryStore.bind(this));
this.app.post('/api/v1/memory/search', this.handleMemorySearch.bind(this));
this.app.get('/api/v1/memory/:id', this.handleMemoryGet.bind(this));
this.app.put('/api/v1/memory/:id', this.handleMemoryUpdate.bind(this));
this.app.delete('/api/v1/memory/:id', this.handleMemoryDelete.bind(this));
// Analytics routes
this.app.post('/api/v1/analytics/track', this.handleAnalyticsTrack.bind(this));
this.app.post('/api/v1/analytics/query', this.handleAnalyticsQuery.bind(this));
// Cache routes
this.app.get('/api/v1/cache/:key', this.handleCacheGet.bind(this));
this.app.put('/api/v1/cache/:key', this.handleCacheSet.bind(this));
this.app.delete('/api/v1/cache/:key', this.handleCacheDelete.bind(this));
// Sync routes
this.app.get('/api/v1/sync/status', this.handleSyncStatus.bind(this));
this.app.post('/api/v1/sync/resolve-conflict', this.handleSyncResolveConflict.bind(this));
}
// ==================== ROUTE HANDLERS ====================
async handleHealthCheck(req, res) {
try {
const health = await this.memoraiService.getHealth();
res.json(health);
}
catch (error) {
res.status(500).json({
status: 'error',
error: error instanceof Error ? error.message : 'Unknown error'
});
}
}
async handleAPIInfo(req, res) {
res.json({
name: 'MEMORAI API',
version: '1.0.0',
description: 'Universal Database & Storage Service for CODAI Ecosystem',
endpoints: {
database: [
'POST /api/v1/database/:table',
'GET /api/v1/database/:table/:id',
'GET /api/v1/database/:table',
'PUT /api/v1/database/:table/:id',
'DELETE /api/v1/database/:table/:id',
'POST /api/v1/database/query'
],
storage: [
'POST /api/v1/storage/upload',
'GET /api/v1/storage/:path',
'DELETE /api/v1/storage/:path',
'GET /api/v1/storage'
],
memory: [
'POST /api/v1/memory',
'POST /api/v1/memory/search',
'GET /api/v1/memory/:id',
'PUT /api/v1/memory/:id',
'DELETE /api/v1/memory/:id'
],
analytics: [
'POST /api/v1/analytics/track',
'POST /api/v1/analytics/query'
],
cache: [
'GET /api/v1/cache/:key',
'PUT /api/v1/cache/:key',
'DELETE /api/v1/cache/:key'
],
sync: [
'GET /api/v1/sync/status',
'POST /api/v1/sync/resolve-conflict'
]
}
});
}
// Database handlers
async handleDatabaseCreate(req, res) {
try {
const { table } = req.params;
const data = req.body;
const result = await this.memoraiService.insert(table, data);
res.json(result);
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Database create failed'
});
}
}
async handleDatabaseGet(req, res) {
try {
const { table, id } = req.params;
const result = await this.memoraiService.find(table, [
{ field: 'id', operator: '=', value: id }
]);
if (result.data && result.data.length > 0) {
res.json({ success: true, data: result.data[0] });
}
else {
res.status(404).json({ success: false, error: 'Record not found' });
}
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Database get failed'
});
}
}
async handleDatabaseFind(req, res) {
try {
const { table } = req.params;
const { limit, offset, orderBy, ...filters } = req.query;
// Build conditions from query parameters
const conditions = Object.entries(filters).map(([field, value]) => ({
field,
operator: '=',
value
}));
const options = {};
if (limit)
options.limit = parseInt(limit);
if (offset)
options.offset = parseInt(offset);
if (orderBy) {
options.orderBy = [{ field: orderBy, direction: 'ASC' }];
}
const result = await this.memoraiService.find(table, conditions, options);
res.json(result);
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Database find failed'
});
}
}
async handleDatabaseUpdate(req, res) {
try {
const { table, id } = req.params;
const data = req.body;
const result = await this.memoraiService.update(table, id, data);
res.json(result);
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Database update failed'
});
}
}
async handleDatabaseDelete(req, res) {
try {
const { table, id } = req.params;
const result = await this.memoraiService.delete(table, id);
res.json(result);
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Database delete failed'
});
}
}
async handleDatabaseQuery(req, res) {
try {
const query = req.body;
const result = await this.memoraiService.query(query);
res.json(result);
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Database query failed'
});
}
}
// Storage handlers
async handleStorageUpload(req, res) {
try {
if (!req.file) {
res.status(400).json({
success: false,
error: 'No file provided'
});
return;
}
const storageUpload = {
file: req.file.buffer,
filename: req.file.originalname,
mimeType: req.file.mimetype,
size: req.file.size,
isPublic: req.body.isPublic === 'true',
tags: req.body.tags ? JSON.parse(req.body.tags) : [],
expiresAt: req.body.expiresAt ? new Date(req.body.expiresAt) : undefined
};
const result = await this.memoraiService.uploadFile(storageUpload, req.user.id);
res.json(result);
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'File upload failed'
});
}
}
async handleStorageDownload(req, res) {
try {
const filePath = req.params.path;
// Extract file ID from path (assuming format: /user/year/month/filename)
const pathParts = filePath.split('/');
const fileId = pathParts[pathParts.length - 1].split('.')[0];
const result = await this.memoraiService.downloadFile(fileId, req.user.id);
if (result.success && result.data) {
// Set appropriate headers
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', `attachment; filename="${filePath}"`);
res.send(result.data);
}
else {
res.status(404).json({
success: false,
error: 'File not found'
});
}
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'File download failed'
});
}
}
async handleStorageDelete(req, res) {
try {
const filePath = req.params.path;
const pathParts = filePath.split('/');
const fileId = pathParts[pathParts.length - 1].split('.')[0];
const result = await this.memoraiService.deleteFile(fileId, req.user.id);
res.json(result);
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'File deletion failed'
});
}
}
async handleStorageList(req, res) {
try {
// TODO: Implement file listing by user
res.json({
success: true,
data: [],
message: 'File listing not yet implemented'
});
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'File listing failed'
});
}
}
// Memory handlers
async handleMemoryStore(req, res) {
try {
const memory = {
...req.body,
userId: req.user.id
};
const result = await this.memoraiService.storeMemory(memory);
res.json(result);
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Memory storage failed'
});
}
}
async handleMemorySearch(req, res) {
try {
const query = {
...req.body,
userId: req.user.id
};
const result = await this.memoraiService.searchMemories(query);
res.json(result);
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Memory search failed'
});
}
}
async handleMemoryGet(req, res) {
try {
const { id } = req.params;
const result = await this.memoraiService.getMemory(id, req.user.id);
res.json(result);
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Memory retrieval failed'
});
}
}
async handleMemoryUpdate(req, res) {
try {
const { id } = req.params;
const updates = req.body;
// TODO: Implement memory update
res.json({
success: true,
message: 'Memory update not yet implemented'
});
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Memory update failed'
});
}
}
async handleMemoryDelete(req, res) {
try {
const { id } = req.params;
// TODO: Implement memory deletion
res.json({
success: true,
message: 'Memory deletion not yet implemented'
});
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Memory deletion failed'
});
}
}
// Analytics handlers
async handleAnalyticsTrack(req, res) {
try {
const event = {
...req.body,
userId: req.user.id,
timestamp: new Date()
};
const result = await this.memoraiService.trackEvent(event);
res.json(result);
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Event tracking failed'
});
}
}
async handleAnalyticsQuery(req, res) {
try {
// TODO: Implement analytics querying
res.json({
success: true,
data: [],
message: 'Analytics querying not yet implemented'
});
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Analytics query failed'
});
}
}
// Cache handlers
async handleCacheGet(req, res) {
try {
const { key } = req.params;
const value = await this.memoraiService.cacheGet(key);
if (value !== null) {
res.json({ success: true, data: value });
}
else {
res.status(404).json({ success: false, error: 'Cache key not found' });
}
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Cache get failed'
});
}
}
async handleCacheSet(req, res) {
try {
const { key } = req.params;
const { value, ttl, tags } = req.body;
await this.memoraiService.cacheSet(key, value, { ttl, tags });
res.json({ success: true });
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Cache set failed'
});
}
}
async handleCacheDelete(req, res) {
try {
const { key } = req.params;
const deleted = await this.memoraiService.cacheDelete(key);
res.json({ success: true, data: deleted });
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Cache delete failed'
});
}
}
// Sync handlers
async handleSyncStatus(req, res) {
try {
// TODO: Implement sync status
res.json({
success: true,
data: [],
message: 'Sync status not yet implemented'
});
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Sync status failed'
});
}
}
async handleSyncResolveConflict(req, res) {
try {
// TODO: Implement conflict resolution
res.json({
success: true,
message: 'Conflict resolution not yet implemented'
});
}
catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Conflict resolution failed'
});
}
}
// ==================== ERROR HANDLING ====================
setupErrorHandling() {
// 404 handler
this.app.use((req, res) => {
res.status(404).json({
success: false,
error: 'Endpoint not found',
path: req.path
});
});
// Global error handler
this.app.use((error, req, res, next) => {
console.error('API Error:', error);
res.status(500).json({
success: false,
error: 'Internal server error',
message: error.message
});
});
}
// ==================== GETTERS ====================
get isRunning() {
return this._isRunning;
}
get expressApp() {
return this.app;
}
}
// Export convenience function
export function createMemoraiAPIServer(memoraiService, config) {
return new MemoraiAPIServer(memoraiService, config);
}
//# sourceMappingURL=server.js.map