-- Migration 001: Schema Enhancements -- Purpose: Add data integrity constraints and audit logging -- Date: 2026-01-19 -- Author: Claude Code -- ============================================================================= -- IMPORTANT: Disable FOREIGN KEY constraints during migration -- ============================================================================= PRAGMA foreign_keys = OFF; -- ============================================================================= -- STEP 1: user_deposits - Add CHECK constraint for balance >= 0 -- ============================================================================= -- SQLite requires table recreation to add CHECK constraints -- 1.1 Create backup table CREATE TABLE user_deposits_backup AS SELECT * FROM user_deposits; -- 1.2 Drop existing table DROP TABLE user_deposits; -- 1.3 Create new table with CHECK constraint CREATE TABLE user_deposits ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL UNIQUE, balance INTEGER NOT NULL DEFAULT 0 CHECK (balance >= 0), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ); -- 1.4 Restore data (negative balances will be rejected) INSERT INTO user_deposits (id, user_id, balance, created_at, updated_at) SELECT id, user_id, balance, created_at, updated_at FROM user_deposits_backup WHERE balance >= 0; -- 1.5 Log rejected records (if any) -- Note: In production, manually review these records before migration SELECT 'REJECTED NEGATIVE BALANCE:' as warning, * FROM user_deposits_backup WHERE balance < 0; -- 1.6 Drop backup table DROP TABLE user_deposits_backup; -- 1.7 Recreate index CREATE INDEX IF NOT EXISTS idx_deposits_user ON user_deposits(user_id); -- ============================================================================= -- STEP 2: deposit_transactions - Add length constraint for depositor_name -- ============================================================================= -- 2.1 Create backup table CREATE TABLE deposit_transactions_backup AS SELECT * FROM deposit_transactions; -- 2.2 Drop existing table DROP TABLE deposit_transactions; -- 2.3 Create new table with length constraint CREATE TABLE deposit_transactions ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, type TEXT NOT NULL CHECK(type IN ('deposit', 'withdrawal', 'refund')), amount INTEGER NOT NULL, status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending', 'confirmed', 'rejected', 'cancelled')), depositor_name TEXT CHECK (length(depositor_name) <= 15), description TEXT, confirmed_at DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ); -- 2.4 Restore data (truncate long depositor names) INSERT INTO deposit_transactions ( id, user_id, type, amount, status, depositor_name, description, confirmed_at, created_at ) SELECT id, user_id, type, amount, status, CASE WHEN depositor_name IS NULL THEN NULL WHEN length(depositor_name) > 15 THEN substr(depositor_name, 1, 15) ELSE depositor_name END as depositor_name, description, confirmed_at, created_at FROM deposit_transactions_backup; -- 2.5 Log truncated records (if any) SELECT 'TRUNCATED DEPOSITOR NAME:' as warning, id, depositor_name, length(depositor_name) as original_length FROM deposit_transactions_backup WHERE depositor_name IS NOT NULL AND length(depositor_name) > 15; -- 2.6 Drop backup table DROP TABLE deposit_transactions_backup; -- 2.7 Recreate indexes CREATE INDEX IF NOT EXISTS idx_transactions_user ON deposit_transactions(user_id); CREATE INDEX IF NOT EXISTS idx_transactions_status ON deposit_transactions(status, created_at DESC); -- ============================================================================= -- STEP 3: Audit Log Table -- ============================================================================= -- 3.1 Create audit_logs table CREATE TABLE IF NOT EXISTS audit_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, telegram_id TEXT, action TEXT NOT NULL, resource_type TEXT, resource_id INTEGER, details TEXT, ip_address TEXT, user_agent TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ); -- 3.2 Create indexes for query performance CREATE INDEX IF NOT EXISTS idx_audit_logs_user_id ON audit_logs(user_id); CREATE INDEX IF NOT EXISTS idx_audit_logs_telegram_id ON audit_logs(telegram_id); CREATE INDEX IF NOT EXISTS idx_audit_logs_action ON audit_logs(action); CREATE INDEX IF NOT EXISTS idx_audit_logs_resource ON audit_logs(resource_type, resource_id); CREATE INDEX IF NOT EXISTS idx_audit_logs_created_at ON audit_logs(created_at DESC); -- ============================================================================= -- VERIFICATION QUERIES -- ============================================================================= -- Count records in each table SELECT 'users' as table_name, COUNT(*) as count FROM users UNION ALL SELECT 'user_deposits', COUNT(*) FROM user_deposits UNION ALL SELECT 'deposit_transactions', COUNT(*) FROM deposit_transactions UNION ALL SELECT 'audit_logs', COUNT(*) FROM audit_logs; -- Verify CHECK constraints -- Attempt to insert invalid data (should fail) -- Uncomment to test: -- INSERT INTO user_deposits (user_id, balance) VALUES (999999, -1000); -- INSERT INTO deposit_transactions (user_id, type, amount, depositor_name) VALUES (999999, 'deposit', 1000, 'ThisIsAVeryLongNameThatExceedsFiftyCharactersAndShouldBeTruncatedOrRejected'); -- ============================================================================= -- Re-enable FOREIGN KEY constraints -- ============================================================================= PRAGMA foreign_keys = ON; -- ============================================================================= -- MIGRATION COMPLETE -- ============================================================================= SELECT 'Migration 001 completed successfully' as status, datetime('now') as timestamp;