// database.js import sqlite3 from 'sqlite3'; import { promisify } from 'util'; import { dirname } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const DB_PATH = new URL('../../db/shop.db', import.meta.url).pathname; const ALLOWED_TABLES = new Set([ 'users', 'crypto_wallets', 'transactions', 'products', 'purchases', 'locations', 'categories' ]); // Create database with verbose mode for better error reporting const db = new sqlite3.Database(DB_PATH, sqlite3.OPEN_CREATE | sqlite3.OPEN_READWRITE, (err) => { if (err) { console.error('Database connection error:', err); process.exit(1); } console.log('Connected to SQLite database'); }); // Enable foreign keys db.run('PRAGMA foreign_keys = ON'); // Promisify database operations const runAsync = (sql, params = []) => { return new Promise((resolve, reject) => { db.run(sql, params, function(err) { if (err) reject(err); else resolve(this); }); }); }; const allAsync = (sql, params = []) => { return new Promise((resolve, reject) => { db.all(sql, params, (err, rows) => { if (err) reject(err); else resolve(rows); }); }); }; const getAsync = (sql, params = []) => { return new Promise((resolve, reject) => { db.get(sql, params, (err, row) => { if (err) reject(err); else resolve(row); }); }); }; // Attach async methods to db object db.runAsync = runAsync; db.allAsync = allAsync; db.getAsync = getAsync; // Function to check if a column exists in a table const checkColumnExists = async (tableName, columnName) => { if (!ALLOWED_TABLES.has(tableName)) { throw new Error(`Invalid table name: ${tableName}`); } try { const result = await db.allAsync(` PRAGMA table_info(${tableName}) `); return result.some(column => column.name === columnName); } catch (error) { console.error(`Error checking column ${columnName} in table ${tableName}:`, error); return false; } }; // Function to clean up invalid foreign key references const cleanUpInvalidForeignKeys = async () => { try { // Clean up invalid foreign key references in crypto_wallets table await db.runAsync(` DELETE FROM crypto_wallets WHERE user_id NOT IN (SELECT id FROM users) `); console.log('Cleaned up invalid foreign key references in crypto_wallets table'); } catch (error) { console.error('Error cleaning up invalid foreign key references:', error); } }; // Initialize database tables const initDb = async () => { try { // Begin transaction for table creation await db.runAsync('BEGIN TRANSACTION'); // Create users table await db.runAsync(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, telegram_id TEXT UNIQUE NOT NULL, username TEXT, country TEXT, city TEXT, district TEXT, status INTEGER DEFAULT 0, total_balance REAL DEFAULT 0, bonus_balance REAL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); // Create crypto_wallets table await db.runAsync(` CREATE TABLE IF NOT EXISTS crypto_wallets ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, wallet_type TEXT NOT NULL, address TEXT NOT NULL, derivation_path TEXT NOT NULL, mnemonic TEXT NOT NULL, balance REAL DEFAULT 0, -- Добавлена колонка для хранения баланса created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, UNIQUE(user_id, wallet_type) ) `); // Check if balance column exists in crypto_wallets table const balanceExists = await checkColumnExists('crypto_wallets', 'balance'); if (!balanceExists) { await db.runAsync(` ALTER TABLE crypto_wallets ADD COLUMN balance REAL DEFAULT 0 `); console.log('Column balance added to crypto_wallets table'); } // Create transactions table await db.runAsync(` CREATE TABLE IF NOT EXISTS transactions ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, wallet_type TEXT NOT NULL, tx_hash TEXT NOT NULL, amount REAL NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ) `); // Check if user_id column exists in transactions table const user_idExists = await checkColumnExists('transactions', 'user_id'); if (!user_idExists) { await db.runAsync(` ALTER TABLE transactions ADD COLUMN user_id INTEGER NOT NULL `); console.log('Column user_id added to transactions table'); } // Check if wallet_type column exists in transactions table const wallet_typeExists = await checkColumnExists('transactions', 'wallet_type'); if (!wallet_typeExists) { await db.runAsync(` ALTER TABLE transactions ADD COLUMN wallet_type TEXT NOT NULL `); console.log('Column wallet_type added to transactions table'); } // Check if tx_hash column exists in transactions table const tx_hashExists = await checkColumnExists('transactions', 'tx_hash'); if (!tx_hashExists) { await db.runAsync(` ALTER TABLE transactions ADD COLUMN tx_hash TEXT NOT NULL `); console.log('Column tx_hash added to transactions table'); } // Create products table await db.runAsync(` CREATE TABLE IF NOT EXISTS products ( id INTEGER PRIMARY KEY AUTOINCREMENT, location_id INTEGER NOT NULL, category_id INTEGER NOT NULL, name TEXT NOT NULL, description TEXT, private_data TEXT, price REAL NOT NULL CHECK (price > 0), quantity_in_stock INTEGER DEFAULT 0 CHECK (quantity_in_stock >= 0), photo_url TEXT, hidden_photo_url TEXT, hidden_coordinates TEXT, hidden_description TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (location_id) REFERENCES locations(id) ON DELETE CASCADE, FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE ) `); // Create purchases table await db.runAsync(` CREATE TABLE IF NOT EXISTS purchases ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, product_id INTEGER NOT NULL, wallet_type TEXT NOT NULL, tx_hash TEXT NOT NULL, quantity INTEGER NOT NULL CHECK (quantity > 0), total_price REAL NOT NULL CHECK (total_price > 0), purchase_date DATETIME DEFAULT CURRENT_TIMESTAMP, status TEXT DEFAULT 'pending', FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE ) `); // Проверка наличия поля status в таблице purchases const statusExists = await checkColumnExists('purchases', 'status'); if (!statusExists) { await db.runAsync(` ALTER TABLE purchases ADD COLUMN status TEXT DEFAULT 'pending' `); console.log('Column status added to purchases table'); } // Create locations table await db.runAsync(` CREATE TABLE IF NOT EXISTS locations ( id INTEGER PRIMARY KEY AUTOINCREMENT, country TEXT NOT NULL, city TEXT NOT NULL, district TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE(country, city, district) ) `); // Create categories table await db.runAsync(` CREATE TABLE IF NOT EXISTS categories ( id INTEGER PRIMARY KEY AUTOINCREMENT, location_id INTEGER NOT NULL, name TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (location_id) REFERENCES locations(id) ON DELETE CASCADE, UNIQUE(location_id, name) ) `); // Commit transaction await db.runAsync('COMMIT'); console.log('Database tables initialized successfully'); } catch (error) { // Rollback transaction on error await db.runAsync('ROLLBACK'); console.error('Error initializing database tables:', error); throw error; } }; // Initialize the database (async () => { await initDb(); await cleanUpInvalidForeignKeys(); })().catch(error => { console.error('Database initialization failed:', error); process.exit(1); }); // Handle database errors db.on('error', (err) => { console.error('Database error:', err); }); // Handle process termination process.on('SIGINT', () => { db.close((err) => { if (err) { console.error('Error closing database:', err); } else { console.log('Database connection closed'); } process.exit(err ? 1 : 0); }); }); export default db;