""" SQLite Database Setup for Telegram Bot Delegates to shared/database/app_db.py for unified database. All tables (telegram, trusted devices, backup codes) live in app.db. """ import aiosqlite import logging from datetime import datetime, timedelta from shared.database.app_db import DB_PATH, get_db as _get_app_db, init_app_db logger = logging.getLogger(__name__) # Re-export DB_PATH for backward compatibility (operations.py imports it) __all__ = [ 'get_db_connection', 'init_database', 'cleanup_expired_codes', 'cleanup_expired_sessions', 'cleanup_expired_email_codes', 'get_database_stats', 'DB_PATH', ] async def get_db_connection() -> aiosqlite.Connection: """ Get a database connection. Delegates to shared app_db. Returns: aiosqlite.Connection: Database connection """ return await _get_app_db() async def init_database() -> None: """ Initialize the database. Delegates to shared init_app_db(). Safe to call multiple times - only creates tables if they don't exist. """ await init_app_db() logger.info("Database initialized successfully (delegated to app_db)") async def cleanup_expired_codes() -> int: """ Delete expired authentication codes from the database. Returns: int: Number of expired codes deleted """ try: async with aiosqlite.connect(DB_PATH) as db: db.row_factory = aiosqlite.Row cursor = await db.execute(""" DELETE FROM telegram_auth_codes WHERE expires_at < ? """, (datetime.now(),)) await db.commit() deleted = cursor.rowcount if deleted > 0: logger.info(f"Cleaned up {deleted} expired auth codes") return deleted except Exception as e: logger.error(f"Failed to cleanup expired codes: {e}") return 0 async def cleanup_expired_sessions() -> int: """ Delete expired sessions from the database. Returns: int: Number of expired sessions deleted """ try: async with aiosqlite.connect(DB_PATH) as db: db.row_factory = aiosqlite.Row cursor = await db.execute(""" DELETE FROM telegram_sessions WHERE expires_at < ? """, (datetime.now(),)) await db.commit() deleted = cursor.rowcount if deleted > 0: logger.info(f"Cleaned up {deleted} expired sessions") return deleted except Exception as e: logger.error(f"Failed to cleanup expired sessions: {e}") return 0 async def cleanup_expired_email_codes() -> int: """ Delete expired and old used email codes from the database. Returns: int: Number of email codes deleted """ try: async with aiosqlite.connect(DB_PATH) as db: db.row_factory = aiosqlite.Row cursor = await db.execute(""" DELETE FROM email_auth_codes WHERE expires_at < ? OR (used = 1 AND used_at < ?) """, ( datetime.now(), datetime.now() - timedelta(days=1) )) await db.commit() deleted = cursor.rowcount if deleted > 0: logger.info(f"Cleaned up {deleted} expired/old email auth codes") return deleted except Exception as e: logger.error(f"Failed to cleanup email codes: {e}") return 0 async def get_database_stats() -> dict: """ Get database statistics for monitoring. Returns: dict: Database statistics """ try: async with aiosqlite.connect(DB_PATH) as db: db.row_factory = aiosqlite.Row stats = {} # Count users cursor = await db.execute("SELECT COUNT(*) FROM telegram_users") stats['total_users'] = (await cursor.fetchone())[0] cursor = await db.execute( "SELECT COUNT(*) FROM telegram_users WHERE is_active = 1" ) stats['active_users'] = (await cursor.fetchone())[0] # Count pending codes cursor = await db.execute(""" SELECT COUNT(*) FROM telegram_auth_codes WHERE used = 0 AND expires_at > ? """, (datetime.now(),)) stats['pending_codes'] = (await cursor.fetchone())[0] # Count active sessions cursor = await db.execute(""" SELECT COUNT(*) FROM telegram_sessions WHERE expires_at > ? """, (datetime.now(),)) stats['active_sessions'] = (await cursor.fetchone())[0] # Database file size if DB_PATH.exists(): stats['db_size_mb'] = DB_PATH.stat().st_size / (1024 * 1024) else: stats['db_size_mb'] = 0 return stats except Exception as e: logger.error(f"Failed to get database stats: {e}") return {}