from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import text, inspect from typing import List, Dict, Any, Optional import logging from core.database import get_db, engine, Base # Import all models to ensure they are registered with Base.metadata from core.models import User, ClientUser, AuditLog, GeminiJob, PaymentTransaction, Contact, RateLimit, ApiKeyUsage router = APIRouter(prefix="/api/schema", tags=["schema"]) logger = logging.getLogger(__name__) @router.get("/tables") async def get_tables(): """ Get a list of all tables in the database. """ # We can inspect the metadata from the Base class since all models inherit from it # and are imported above. return sorted(list(Base.metadata.tables.keys())) @router.get("/table/{table_name}") async def get_table_data( table_name: str, page: int = Query(1, ge=1), per_page: int = Query(50, ge=1, le=1000), db: AsyncSession = Depends(get_db) ): """ Get data for a specific table with pagination. """ if table_name not in Base.metadata.tables: raise HTTPException(status_code=404, detail=f"Table {table_name} not found") table = Base.metadata.tables[table_name] # Get columns columns = [c.name for c in table.columns] # Calculate offset offset = (page - 1) * per_page # Construct query safely using SQLAlchemy Core # We use text() for dynamic table names but validate against metadata first try: # Get total count count_query = text(f"SELECT COUNT(*) FROM {table_name}") result = await db.execute(count_query) total = result.scalar() # Get data data_query = text(f"SELECT * FROM {table_name} LIMIT :limit OFFSET :offset") result = await db.execute(data_query, {"limit": per_page, "offset": offset}) # Convert rows to dicts # result.keys() gives column names, result.all() gives rows # We need to serialize datetime objects and others to JSON-friendly format rows = [] for row in result: row_dict = {} for idx, col in enumerate(result.keys()): val = row[idx] # Simple string conversion for non-JSON serializable types might be needed # FastAPI/Pydantic handles datetime usually, but let's be safe if needed. # For now, let's rely on FastAPI's default encoder. row_dict[col] = val rows.append(row_dict) return { "table": table_name, "columns": columns, "total": total, "page": page, "per_page": per_page, "data": rows } except Exception as e: logger.error(f"Error fetching data for table {table_name}: {e}") raise HTTPException(status_code=500, detail=str(e))