"""Database configuration and models for Claude Code Monitor.""" from sqlalchemy import ( create_engine, Column, Integer, String, Float, Boolean, DateTime, Index, CheckConstraint, ) from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from sqlalchemy.sql import func import os # Database URL DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./data/claude_monitor.db") # Create engine with WAL mode for better concurrency engine = create_engine( DATABASE_URL, connect_args={"check_same_thread": False} if "sqlite" in DATABASE_URL else {}, echo=False, ) # Session factory SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) # Base class for models Base = declarative_base() class Event(Base): """Event model for storing Claude Code events.""" __tablename__ = "events" id = Column(Integer, primary_key=True, index=True, autoincrement=True) session_id = Column(String, nullable=False, index=True) event_type = Column(String, nullable=False, index=True) # PreToolUse, PostToolUse, etc. tool_name = Column(String, nullable=True, index=True) # Bash, Read, Write, etc. tool_input = Column(String, nullable=True) # JSON string tool_output = Column(String, nullable=True) # Tool response success = Column(Boolean, nullable=True) # Success/failure (NULL for PreToolUse) description = Column(String, nullable=True) # Human-readable description timestamp = Column(Float, nullable=False, index=True) # Unix timestamp created_at = Column(DateTime, server_default=func.now()) __table_args__ = ( Index("idx_event_type_timestamp", "event_type", "timestamp"), Index("idx_session_timestamp", "session_id", "timestamp"), ) class Session(Base): """Session model for tracking Claude Code sessions.""" __tablename__ = "sessions" session_id = Column(String, primary_key=True) started_at = Column(Float, nullable=False, index=True) ended_at = Column(Float, nullable=True) event_count = Column(Integer, default=0) created_at = Column(DateTime, server_default=func.now()) class Statistics(Base): """Statistics model for cached aggregations (single-row table).""" __tablename__ = "statistics" id = Column(Integer, primary_key=True) total_events = Column(Integer, default=0) total_tools_used = Column(Integer, default=0) total_agents = Column(Integer, default=0) total_sessions = Column(Integer, default=0) last_updated = Column(Float, nullable=False) __table_args__ = (CheckConstraint("id = 1", name="single_row"),) class ToolUsage(Base): """Tool usage model for tracking unique tools.""" __tablename__ = "tool_usage" tool_name = Column(String, primary_key=True) usage_count = Column(Integer, default=1) first_used = Column(Float, nullable=False) last_used = Column(Float, nullable=False, index=True) def init_db(): """Initialize database: create tables and enable WAL mode.""" # Create all tables Base.metadata.create_all(bind=engine) # Enable WAL mode for SQLite if "sqlite" in DATABASE_URL: with engine.connect() as conn: conn.execute("PRAGMA journal_mode=WAL") conn.commit() # Initialize statistics table with single row db = SessionLocal() try: stats = db.query(Statistics).first() if not stats: stats = Statistics( id=1, total_events=0, total_tools_used=0, total_agents=0, total_sessions=0, last_updated=0.0, ) db.add(stats) db.commit() finally: db.close() def get_db(): """Dependency for getting database session.""" db = SessionLocal() try: yield db finally: db.close()