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
JavaScript
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 };