UNPKG

defarm-sdk

Version:

DeFarm SDK - On-premise blockchain data processing and tokenization engine for agriculture supply chain

1,093 lines (944 loc) 32.1 kB
const fs = require('fs').promises; const path = require('path'); const crypto = require('crypto'); /** * Database Manager for DeFarm SDK * Manages isolated database for on-premise data processing */ class DatabaseManager { constructor(config = {}) { this.config = { type: config.type || 'postgresql', host: config.host || 'localhost', port: config.port || (config.type === 'mysql' ? 3306 : 5432), user: config.user || 'defarm', password: config.password || '', database: config.database || 'defarm_sdk', ssl: config.ssl || false, pool: { min: config.pool?.min || 2, max: config.pool?.max || 10, idleTimeoutMillis: config.pool?.idleTimeoutMillis || 30000 }, schemas: { processing: 'defarm_processing', staging: 'defarm_staging', audit: 'defarm_audit', blockchain: 'defarm_blockchain' }, encryption: { enabled: config.encryption?.enabled !== false, algorithm: config.encryption?.algorithm || 'aes-256-gcm', key: config.encryption?.key || this.generateEncryptionKey() } }; this.connection = null; this.pool = null; this.initialized = false; this.transactionStack = []; } /** * Initialize database connection and setup */ async initialize() { if (this.initialized) return; console.log('🔌 Initializing DeFarm Database Manager...'); try { // Load database driver await this.loadDriver(); // Create connection pool await this.createConnectionPool(); // Setup database structure await this.setupDatabase(); // Initialize schemas await this.createSchemas(); // Create tables await this.createTables(); // Setup indexes await this.createIndexes(); // Setup triggers for audit await this.createAuditTriggers(); this.initialized = true; console.log('✅ Database Manager initialized successfully'); } catch (error) { console.error('❌ Database initialization failed:', error); throw error; } } /** * Load database driver dynamically */ async loadDriver() { if (this.config.type === 'postgresql') { try { const { Pool } = require('pg'); this.Pool = Pool; } catch (e) { throw new Error('PostgreSQL driver not installed. Run: npm install pg'); } } else if (this.config.type === 'mysql') { try { this.mysql = require('mysql2/promise'); } catch (e) { throw new Error('MySQL driver not installed. Run: npm install mysql2'); } } else if (this.config.type === 'sqlite') { try { this.sqlite3 = require('sqlite3'); this.sqlite = require('sqlite'); } catch (e) { // SQLite is optional, fall back to in-memory storage console.warn('⚠️ SQLite not available, using in-memory storage'); this.useInMemory = true; } } else { throw new Error(`Unsupported database type: ${this.config.type}`); } } /** * Create connection pool */ async createConnectionPool() { if (this.useInMemory) { // Use in-memory storage when SQLite is not available this.inMemoryDB = new Map(); console.log('📝 Using in-memory storage for testing'); return; } if (this.config.type === 'postgresql') { this.pool = new this.Pool({ host: this.config.host, port: this.config.port, user: this.config.user, password: this.config.password, database: this.config.database, ssl: this.config.ssl, ...this.config.pool }); // Test connection const client = await this.pool.connect(); await client.query('SELECT NOW()'); client.release(); } else if (this.config.type === 'mysql') { this.pool = await this.mysql.createPool({ host: this.config.host, port: this.config.port, user: this.config.user, password: this.config.password, database: this.config.database, ssl: this.config.ssl, waitForConnections: true, connectionLimit: this.config.pool.max, queueLimit: 0 }); // Test connection await this.pool.execute('SELECT NOW()'); } else if (this.config.type === 'sqlite') { // Open SQLite database this.db = await this.sqlite.open({ filename: this.config.database || './defarm_sdk.db', driver: this.sqlite3.Database }); // Test connection and create basic tables if needed await this.db.exec(` CREATE TABLE IF NOT EXISTS defarm_metadata ( key TEXT PRIMARY KEY, value TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); `); console.log('📦 SQLite database ready:', this.config.database || './defarm_sdk.db'); } } /** * Setup database if not exists */ async setupDatabase() { // This would be run during initial setup // Check if database exists and create if needed console.log('📁 Checking database setup...'); } /** * Create schemas for data isolation */ async createSchemas() { if (this.config.type !== 'postgresql') return; console.log('📂 Creating isolated schemas...'); for (const [key, schema] of Object.entries(this.config.schemas)) { try { await this.execute(`CREATE SCHEMA IF NOT EXISTS ${schema}`); console.log(` ✓ Schema '${schema}' ready`); } catch (error) { console.error(` ✗ Failed to create schema ${schema}:`, error.message); } } } /** * Create all required tables */ async createTables() { console.log('📊 Creating tables...'); // Agriculture assets table await this.execute(` CREATE TABLE IF NOT EXISTS ${this.getTableName('assets')} ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), asset_id VARCHAR(255) UNIQUE NOT NULL, asset_type VARCHAR(50) NOT NULL, owner_address VARCHAR(255), data JSONB NOT NULL, metadata JSONB, hash VARCHAR(64) NOT NULL, token_id VARCHAR(255), blockchain_tx VARCHAR(255), ipfs_hash VARCHAR(255), status VARCHAR(50) DEFAULT 'active', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, encrypted BOOLEAN DEFAULT false ) `); // Supply chain events table await this.execute(` CREATE TABLE IF NOT EXISTS ${this.getTableName('events')} ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), event_id VARCHAR(255) UNIQUE NOT NULL, event_type VARCHAR(50) NOT NULL, asset_id VARCHAR(255) NOT NULL, actor VARCHAR(255) NOT NULL, location JSONB, data JSONB NOT NULL, previous_hash VARCHAR(64), hash VARCHAR(64) NOT NULL, blockchain_tx VARCHAR(255), timestamp BIGINT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `); // Tokens table await this.execute(` CREATE TABLE IF NOT EXISTS ${this.getTableName('tokens', 'blockchain')} ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), token_id VARCHAR(255) UNIQUE NOT NULL, asset_id VARCHAR(255) NOT NULL, contract_address VARCHAR(255), owner_address VARCHAR(255) NOT NULL, amount NUMERIC NOT NULL, decimals INTEGER DEFAULT 18, metadata_uri TEXT, metadata JSONB, network VARCHAR(50) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `); // Processing queue table await this.execute(` CREATE TABLE IF NOT EXISTS ${this.getTableName('queue')} ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), job_id VARCHAR(255) UNIQUE NOT NULL, job_type VARCHAR(50) NOT NULL, priority INTEGER DEFAULT 0, status VARCHAR(50) DEFAULT 'pending', data JSONB NOT NULL, result JSONB, error TEXT, attempts INTEGER DEFAULT 0, max_retries INTEGER DEFAULT 3, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, started_at TIMESTAMP, completed_at TIMESTAMP, scheduled_for TIMESTAMP ) `); // Audit log table await this.execute(` CREATE TABLE IF NOT EXISTS ${this.getTableName('audit_log', 'audit')} ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), table_name VARCHAR(255) NOT NULL, operation VARCHAR(20) NOT NULL, record_id VARCHAR(255), user_id VARCHAR(255), old_data JSONB, new_data JSONB, changes JSONB, ip_address INET, user_agent TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `); // Compliance records table await this.execute(` CREATE TABLE IF NOT EXISTS ${this.getTableName('compliance')} ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), asset_id VARCHAR(255), certification_type VARCHAR(100) NOT NULL, issuer VARCHAR(255) NOT NULL, valid_from TIMESTAMP NOT NULL, valid_until TIMESTAMP NOT NULL, verification_hash VARCHAR(64) NOT NULL, document_uri TEXT, metadata JSONB, status VARCHAR(50) DEFAULT 'active', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `); // Analytics aggregates table await this.execute(` CREATE TABLE IF NOT EXISTS ${this.getTableName('analytics')} ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), metric_type VARCHAR(100) NOT NULL, period VARCHAR(50) NOT NULL, dimensions JSONB, values JSONB NOT NULL, calculated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(metric_type, period, dimensions) ) `); console.log(' ✓ All tables created'); } /** * Create indexes for performance */ async createIndexes() { console.log('🔍 Creating indexes...'); const indexes = [ `CREATE INDEX IF NOT EXISTS idx_assets_type ON ${this.getTableName('assets')}(asset_type)`, `CREATE INDEX IF NOT EXISTS idx_assets_owner ON ${this.getTableName('assets')}(owner_address)`, `CREATE INDEX IF NOT EXISTS idx_assets_status ON ${this.getTableName('assets')}(status)`, `CREATE INDEX IF NOT EXISTS idx_assets_created ON ${this.getTableName('assets')}(created_at DESC)`, `CREATE INDEX IF NOT EXISTS idx_events_asset ON ${this.getTableName('events')}(asset_id)`, `CREATE INDEX IF NOT EXISTS idx_events_type ON ${this.getTableName('events')}(event_type)`, `CREATE INDEX IF NOT EXISTS idx_events_timestamp ON ${this.getTableName('events')}(timestamp DESC)`, `CREATE INDEX IF NOT EXISTS idx_tokens_asset ON ${this.getTableName('tokens', 'blockchain')}(asset_id)`, `CREATE INDEX IF NOT EXISTS idx_tokens_owner ON ${this.getTableName('tokens', 'blockchain')}(owner_address)`, `CREATE INDEX IF NOT EXISTS idx_queue_status ON ${this.getTableName('queue')}(status, priority DESC)`, `CREATE INDEX IF NOT EXISTS idx_queue_scheduled ON ${this.getTableName('queue')}(scheduled_for)`, `CREATE INDEX IF NOT EXISTS idx_audit_table ON ${this.getTableName('audit_log', 'audit')}(table_name)`, `CREATE INDEX IF NOT EXISTS idx_audit_created ON ${this.getTableName('audit_log', 'audit')}(created_at DESC)`, `CREATE INDEX IF NOT EXISTS idx_compliance_asset ON ${this.getTableName('compliance')}(asset_id)`, `CREATE INDEX IF NOT EXISTS idx_compliance_type ON ${this.getTableName('compliance')}(certification_type)` ]; for (const index of indexes) { try { await this.execute(index); } catch (error) { console.warn(` ⚠ Index creation warning:`, error.message); } } console.log(' ✓ Indexes created'); } /** * Create audit triggers */ async createAuditTriggers() { if (this.config.type !== 'postgresql') return; console.log('🔒 Creating audit triggers...'); // Create audit function await this.execute(` CREATE OR REPLACE FUNCTION ${this.config.schemas.audit}.audit_trigger_function() RETURNS TRIGGER AS $$ BEGIN IF TG_OP = 'DELETE' THEN INSERT INTO ${this.getTableName('audit_log', 'audit')} ( table_name, operation, record_id, old_data, created_at ) VALUES ( TG_TABLE_NAME, TG_OP, OLD.id::text, row_to_json(OLD), NOW() ); RETURN OLD; ELSIF TG_OP = 'UPDATE' THEN INSERT INTO ${this.getTableName('audit_log', 'audit')} ( table_name, operation, record_id, old_data, new_data, created_at ) VALUES ( TG_TABLE_NAME, TG_OP, NEW.id::text, row_to_json(OLD), row_to_json(NEW), NOW() ); RETURN NEW; ELSIF TG_OP = 'INSERT' THEN INSERT INTO ${this.getTableName('audit_log', 'audit')} ( table_name, operation, record_id, new_data, created_at ) VALUES ( TG_TABLE_NAME, TG_OP, NEW.id::text, row_to_json(NEW), NOW() ); RETURN NEW; END IF; RETURN NULL; END; $$ LANGUAGE plpgsql `); // Create triggers for important tables const auditTables = ['assets', 'events', 'tokens', 'compliance']; for (const table of auditTables) { const triggerName = `audit_trigger_${table}`; const tableName = this.getTableName(table, table === 'tokens' ? 'blockchain' : 'processing'); try { await this.execute(`DROP TRIGGER IF EXISTS ${triggerName} ON ${tableName}`); await this.execute(` CREATE TRIGGER ${triggerName} AFTER INSERT OR UPDATE OR DELETE ON ${tableName} FOR EACH ROW EXECUTE FUNCTION ${this.config.schemas.audit}.audit_trigger_function() `); console.log(` ✓ Audit trigger for ${table}`); } catch (error) { console.warn(` ⚠ Trigger creation warning for ${table}:`, error.message); } } } /** * Store agriculture asset */ async storeAsset(assetData) { const id = crypto.randomUUID(); const hash = this.calculateHash(assetData); // Encrypt sensitive data if configured let dataToStore = assetData; let encrypted = false; if (this.config.encryption.enabled && assetData.sensitive) { dataToStore = this.encryptData(assetData); encrypted = true; } const asset = { id, asset_id: assetData.asset_id || id, asset_type: assetData.asset_type, owner_address: assetData.owner_wallet || assetData.owner_address, data: dataToStore, metadata: assetData.metadata || {}, hash, token_id: assetData.token_id || null, blockchain_tx: assetData.blockchain_tx || null, ipfs_hash: assetData.ipfs_hash || null, status: assetData.status || 'active', encrypted, created_at: new Date(), updated_at: new Date() }; // Use in-memory storage if available if (this.useInMemory && this.inMemoryDB) { const key = `assets:${asset.asset_id}`; this.inMemoryDB.set(key, asset); return asset; } const query = ` INSERT INTO ${this.getTableName('assets')} (id, asset_id, asset_type, owner_address, data, metadata, hash, token_id, blockchain_tx, ipfs_hash, status, encrypted) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) ON CONFLICT (asset_id) DO UPDATE SET data = EXCLUDED.data, metadata = EXCLUDED.metadata, hash = EXCLUDED.hash, token_id = COALESCE(EXCLUDED.token_id, ${this.getTableName('assets')}.token_id), blockchain_tx = COALESCE(EXCLUDED.blockchain_tx, ${this.getTableName('assets')}.blockchain_tx), ipfs_hash = COALESCE(EXCLUDED.ipfs_hash, ${this.getTableName('assets')}.ipfs_hash), updated_at = CURRENT_TIMESTAMP RETURNING * `; const values = [ id, assetData.asset_id || this.generateAssetId(), assetData.asset_type, assetData.owner_address || null, JSON.stringify(dataToStore), JSON.stringify(assetData.metadata || {}), hash, assetData.token_id || null, assetData.blockchain_tx || null, assetData.ipfs_hash || null, assetData.status || 'active', encrypted ]; const result = await this.execute(query, values); return result.rows[0]; } /** * Store supply chain event */ async storeEvent(eventData) { const id = crypto.randomUUID(); const hash = this.calculateHash(eventData); const event = { id, event_id: eventData.event_id || crypto.randomUUID(), event_type: eventData.event_type, asset_id: eventData.asset_id, actor: eventData.actor, location: eventData.location || {}, data: eventData.data || {}, previous_hash: eventData.previous_hash || null, hash, blockchain_tx: eventData.blockchain_tx || null, timestamp: eventData.timestamp || Date.now() }; // Use in-memory storage if available if (this.useInMemory && this.inMemoryDB) { const key = `events:${id}`; this.inMemoryDB.set(key, event); return event; } // Use database storage const query = ` INSERT INTO ${this.getTableName('events')} (id, event_id, event_type, asset_id, actor, location, data, previous_hash, hash, blockchain_tx, timestamp) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING * `; const values = [ event.id, event.event_id, event.event_type, event.asset_id, event.actor, JSON.stringify(event.location), JSON.stringify(event.data), event.previous_hash, event.hash, event.blockchain_tx, event.timestamp ]; const result = await this.execute(query, values); return result.rows[0]; } /** * Store token information */ async storeToken(tokenData) { const query = ` INSERT INTO ${this.getTableName('tokens', 'blockchain')} (token_id, asset_id, contract_address, owner_address, amount, decimals, metadata_uri, metadata, network) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (token_id) DO UPDATE SET owner_address = EXCLUDED.owner_address, amount = EXCLUDED.amount, metadata = EXCLUDED.metadata, updated_at = CURRENT_TIMESTAMP RETURNING * `; const values = [ tokenData.token_id, tokenData.asset_id, tokenData.contract_address || null, tokenData.owner_address, tokenData.amount, tokenData.decimals || 18, tokenData.metadata_uri || null, JSON.stringify(tokenData.metadata || {}), tokenData.network || 'polygon' ]; const result = await this.execute(query, values); return result.rows[0]; } /** * Query data with filters */ async query(filters = {}, options = {}) { // Use in-memory storage if available if (this.useInMemory && this.inMemoryDB) { const results = []; for (const [key, value] of this.inMemoryDB.entries()) { if (key.startsWith('assets:') || key.startsWith('events:')) { let matches = true; // Apply filters if (filters.asset_type && value.asset_type !== filters.asset_type) matches = false; if (filters.owner_address && value.owner_address !== filters.owner_address) matches = false; if (filters.asset_id && value.asset_id !== filters.asset_id) matches = false; if (matches) results.push(value); } } return results; } let query = `SELECT * FROM ${this.getTableName('assets')} WHERE 1=1`; const values = []; let paramCount = 0; // Build dynamic query based on filters if (filters.asset_type) { paramCount++; query += ` AND asset_type = $${paramCount}`; values.push(filters.asset_type); } if (filters.owner_address) { paramCount++; query += ` AND owner_address = $${paramCount}`; values.push(filters.owner_address); } if (filters.status) { paramCount++; query += ` AND status = $${paramCount}`; values.push(filters.status); } if (filters.date_range) { if (filters.date_range.from) { paramCount++; query += ` AND created_at >= $${paramCount}`; values.push(filters.date_range.from); } if (filters.date_range.to) { paramCount++; query += ` AND created_at <= $${paramCount}`; values.push(filters.date_range.to); } } if (filters.location && filters.location.country) { paramCount++; query += ` AND data->>'country' = $${paramCount}`; values.push(filters.location.country); } // Add sorting if (options.orderBy) { query += ` ORDER BY ${options.orderBy} ${options.order || 'ASC'}`; } else { query += ` ORDER BY created_at DESC`; } // Add pagination if (options.limit) { paramCount++; query += ` LIMIT $${paramCount}`; values.push(options.limit); } if (options.offset) { paramCount++; query += ` OFFSET $${paramCount}`; values.push(options.offset); } const result = await this.execute(query, values); // Decrypt data if needed if (this.config.encryption.enabled) { for (const row of result.rows) { if (row.encrypted) { row.data = this.decryptData(row.data); } } } return result.rows; } /** * Get asset history */ async getAssetHistory(assetId) { const query = ` SELECT * FROM ${this.getTableName('events')} WHERE asset_id = $1 ORDER BY timestamp ASC `; const result = await this.execute(query, [assetId]); return result.rows; } /** * Get asset by ID */ async getAsset(assetId) { const query = ` SELECT * FROM ${this.getTableName('assets')} WHERE asset_id = $1 `; const result = await this.execute(query, [assetId]); if (result.rows.length > 0) { const asset = result.rows[0]; // Decrypt if needed if (asset.encrypted && this.config.encryption.enabled) { asset.data = this.decryptData(asset.data); } return asset; } return null; } /** * Generic update method */ async update(table, data, where) { // Use in-memory storage if available if (this.useInMemory && this.inMemoryDB) { const prefix = table === 'assets' ? 'assets:' : table === 'events' ? 'events:' : `${table}:`; for (const [key, value] of this.inMemoryDB.entries()) { if (key.startsWith(prefix)) { let matches = true; // Check where conditions for (const [field, expectedValue] of Object.entries(where)) { if (value[field] !== expectedValue) { matches = false; break; } } if (matches) { // Update the record const updatedValue = { ...value, ...data }; this.inMemoryDB.set(key, updatedValue); return updatedValue; } } } return null; } // Build SQL update query const setClause = Object.keys(data).map((key, index) => `${key} = $${index + 1}`).join(', '); const whereClause = Object.keys(where).map((key, index) => `${key} = $${Object.keys(data).length + index + 1}`).join(' AND '); const query = `UPDATE ${this.getTableName(table)} SET ${setClause} WHERE ${whereClause} RETURNING *`; const values = [...Object.values(data), ...Object.values(where)]; const result = await this.execute(query, values); return result.rows[0]; } /** * Update asset status */ async updateAssetStatus(assetId, status) { const query = ` UPDATE ${this.getTableName('assets')} SET status = $1, updated_at = CURRENT_TIMESTAMP WHERE asset_id = $2 RETURNING * `; const result = await this.execute(query, [status, assetId]); return result.rows[0]; } /** * Store compliance record */ async storeCompliance(complianceData) { const query = ` INSERT INTO ${this.getTableName('compliance')} (asset_id, certification_type, issuer, valid_from, valid_until, verification_hash, document_uri, metadata, status) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING * `; const values = [ complianceData.asset_id || null, complianceData.certification_type, complianceData.issuer, complianceData.valid_from, complianceData.valid_until, complianceData.verification_hash, complianceData.document_uri || null, JSON.stringify(complianceData.metadata || {}), complianceData.status || 'active' ]; const result = await this.execute(query, values); return result.rows[0]; } /** * Get compliance records */ async getCompliance(assetId) { const query = ` SELECT * FROM ${this.getTableName('compliance')} WHERE asset_id = $1 AND status = 'active' ORDER BY valid_until DESC `; const result = await this.execute(query, [assetId]); return result.rows; } /** * Store analytics data */ async storeAnalytics(metricType, period, dimensions, values) { const query = ` INSERT INTO ${this.getTableName('analytics')} (metric_type, period, dimensions, values) VALUES ($1, $2, $3, $4) ON CONFLICT (metric_type, period, dimensions) DO UPDATE SET values = $4, calculated_at = CURRENT_TIMESTAMP RETURNING * `; const result = await this.execute(query, [ metricType, period, JSON.stringify(dimensions), JSON.stringify(values) ]); return result.rows[0]; } /** * Get analytics data */ async getAnalytics(metricType, period, dimensions = {}) { const query = ` SELECT * FROM ${this.getTableName('analytics')} WHERE metric_type = $1 AND period = $2 AND dimensions = $3 `; const result = await this.execute(query, [ metricType, period, JSON.stringify(dimensions) ]); return result.rows[0]; } /** * Begin transaction */ async beginTransaction() { const client = await this.pool.connect(); await client.query('BEGIN'); const transaction = { client, id: crypto.randomUUID(), startTime: Date.now() }; this.transactionStack.push(transaction); return transaction.id; } /** * Commit transaction */ async commitTransaction(transactionId) { const transaction = this.transactionStack.find(t => t.id === transactionId); if (!transaction) throw new Error('Transaction not found'); await transaction.client.query('COMMIT'); transaction.client.release(); this.transactionStack = this.transactionStack.filter(t => t.id !== transactionId); } /** * Rollback transaction */ async rollbackTransaction(transactionId) { const transaction = this.transactionStack.find(t => t.id === transactionId); if (!transaction) throw new Error('Transaction not found'); await transaction.client.query('ROLLBACK'); transaction.client.release(); this.transactionStack = this.transactionStack.filter(t => t.id !== transactionId); } /** * Get database statistics */ async getStats() { const stats = {}; // Get table counts const tables = ['assets', 'events', 'tokens', 'queue', 'compliance']; for (const table of tables) { try { const result = await this.execute( `SELECT COUNT(*) as count FROM ${this.getTableName(table, table === 'tokens' ? 'blockchain' : 'processing')}` ); stats[table] = parseInt(result.rows[0].count); } catch (error) { stats[table] = 0; } } // Get database size if (this.config.type === 'postgresql') { const sizeResult = await this.execute(` SELECT pg_database_size($1) as size `, [this.config.database]); stats.database_size = parseInt(sizeResult.rows[0].size); } // Get connection pool stats if (this.pool) { stats.pool = { total: this.pool.totalCount || 0, idle: this.pool.idleCount || 0, waiting: this.pool.waitingCount || 0 }; } return stats; } /** * Execute query */ async execute(query, values = []) { try { if (this.config.type === 'postgresql') { return await this.pool.query(query, values); } else if (this.config.type === 'mysql') { const [rows] = await this.pool.execute(query, values); return { rows }; } } catch (error) { console.error('Database query error:', error); throw error; } } /** * Get table name with schema */ getTableName(table, schema = 'processing') { if (this.config.type === 'postgresql') { const schemaName = this.config.schemas[schema] || this.config.schemas.processing; return `${schemaName}.${table}`; } return `defarm_${table}`; } /** * Calculate hash for data */ calculateHash(data) { const hash = crypto.createHash('sha256'); hash.update(JSON.stringify(data)); return hash.digest('hex'); } /** * Generate asset ID */ generateAssetId() { return `ASSET-${crypto.randomUUID().substring(0, 12).toUpperCase()}`; } /** * Generate encryption key */ generateEncryptionKey() { return crypto.randomBytes(32).toString('hex'); } /** * Encrypt sensitive data */ encryptData(data) { if (!this.config.encryption.enabled) return data; const algorithm = this.config.encryption.algorithm; const key = Buffer.from(this.config.encryption.key, 'hex'); const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv(algorithm, key, iv); let encrypted = cipher.update(JSON.stringify(data), 'utf8', 'hex'); encrypted += cipher.final('hex'); const authTag = cipher.getAuthTag(); return { encrypted: encrypted, iv: iv.toString('hex'), authTag: authTag.toString('hex') }; } /** * Decrypt sensitive data */ decryptData(encryptedData) { if (!this.config.encryption.enabled) return encryptedData; const algorithm = this.config.encryption.algorithm; const key = Buffer.from(this.config.encryption.key, 'hex'); const iv = Buffer.from(encryptedData.iv, 'hex'); const authTag = Buffer.from(encryptedData.authTag, 'hex'); const decipher = crypto.createDecipheriv(algorithm, key, iv); decipher.setAuthTag(authTag); let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return JSON.parse(decrypted); } /** * Cleanup old data */ async cleanup(olderThanDays = 90) { const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - olderThanDays); // Clean old queue jobs const queueResult = await this.execute(` DELETE FROM ${this.getTableName('queue')} WHERE status IN ('completed', 'failed') AND completed_at < $1 `, [cutoffDate]); // Clean old audit logs const auditResult = await this.execute(` DELETE FROM ${this.getTableName('audit_log', 'audit')} WHERE created_at < $1 `, [cutoffDate]); return { queue_deleted: queueResult.rowCount || 0, audit_deleted: auditResult.rowCount || 0 }; } /** * Close database connections */ async close() { if (this.pool) { await this.pool.end(); console.log('🔌 Database connections closed'); } } } module.exports = { DatabaseManager };