feat(stats): T32-T33 implement dashboard and usage endpoints
Add statistics router with two endpoints: - GET /api/stats/dashboard: Aggregated dashboard statistics - Query param: days (1-365, default 30) - Auth required - Returns DashboardResponse - GET /api/usage: Detailed usage statistics with filtering - Required params: start_date, end_date - Optional filters: api_key_id, model - Pagination: skip, limit (max 1000) - Auth required - Returns List[UsageStatsResponse] Also add get_usage_stats() service function for querying individual usage records with filtering and pagination.
This commit is contained in:
@@ -8,6 +8,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
||||
from openrouter_monitor.config import get_settings
|
||||
from openrouter_monitor.routers import api_keys
|
||||
from openrouter_monitor.routers import auth
|
||||
from openrouter_monitor.routers import stats
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
@@ -31,6 +32,7 @@ app.add_middleware(
|
||||
# Include routers
|
||||
app.include_router(auth.router, prefix="/api/auth", tags=["authentication"])
|
||||
app.include_router(api_keys.router, prefix="/api/keys", tags=["api-keys"])
|
||||
app.include_router(stats.router)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Routers package for OpenRouter Monitor."""
|
||||
from openrouter_monitor.routers import api_keys
|
||||
from openrouter_monitor.routers import auth
|
||||
from openrouter_monitor.routers import stats
|
||||
|
||||
__all__ = ["auth", "api_keys"]
|
||||
__all__ = ["auth", "api_keys", "stats"]
|
||||
|
||||
118
src/openrouter_monitor/routers/stats.py
Normal file
118
src/openrouter_monitor/routers/stats.py
Normal file
@@ -0,0 +1,118 @@
|
||||
"""Statistics router for OpenRouter API Key Monitor.
|
||||
|
||||
T32-T33: Stats endpoints for dashboard and usage data.
|
||||
"""
|
||||
from datetime import date
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from openrouter_monitor.database import get_db
|
||||
from openrouter_monitor.dependencies import get_current_user
|
||||
from openrouter_monitor.models import User
|
||||
from openrouter_monitor.schemas.stats import (
|
||||
DashboardResponse,
|
||||
UsageStatsResponse,
|
||||
)
|
||||
from openrouter_monitor.services.stats import (
|
||||
get_dashboard_data,
|
||||
get_usage_stats,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["statistics"])
|
||||
|
||||
|
||||
@router.get(
|
||||
"/stats/dashboard",
|
||||
response_model=DashboardResponse,
|
||||
status_code=status.HTTP_200_OK,
|
||||
summary="Get dashboard statistics",
|
||||
description="Get aggregated statistics for the dashboard view.",
|
||||
)
|
||||
async def get_dashboard(
|
||||
days: int = Query(
|
||||
default=30,
|
||||
ge=1,
|
||||
le=365,
|
||||
description="Number of days to look back (1-365)",
|
||||
),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> DashboardResponse:
|
||||
"""Get dashboard statistics for the current user.
|
||||
|
||||
Args:
|
||||
days: Number of days to look back (default 30, max 365)
|
||||
db: Database session
|
||||
current_user: Authenticated user
|
||||
|
||||
Returns:
|
||||
DashboardResponse with summary, by_model, by_date, and top_models
|
||||
"""
|
||||
return get_dashboard_data(db, current_user.id, days)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/usage",
|
||||
response_model=List[UsageStatsResponse],
|
||||
status_code=status.HTTP_200_OK,
|
||||
summary="Get detailed usage statistics",
|
||||
description="Get detailed usage statistics with filtering and pagination.",
|
||||
)
|
||||
async def get_usage(
|
||||
start_date: date = Query(
|
||||
...,
|
||||
description="Start date for the query (YYYY-MM-DD)",
|
||||
),
|
||||
end_date: date = Query(
|
||||
...,
|
||||
description="End date for the query (YYYY-MM-DD)",
|
||||
),
|
||||
api_key_id: Optional[int] = Query(
|
||||
default=None,
|
||||
description="Filter by specific API key ID",
|
||||
),
|
||||
model: Optional[str] = Query(
|
||||
default=None,
|
||||
description="Filter by model name",
|
||||
),
|
||||
skip: int = Query(
|
||||
default=0,
|
||||
ge=0,
|
||||
description="Number of records to skip for pagination",
|
||||
),
|
||||
limit: int = Query(
|
||||
default=100,
|
||||
ge=1,
|
||||
le=1000,
|
||||
description="Maximum number of records to return (1-1000)",
|
||||
),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> List[UsageStatsResponse]:
|
||||
"""Get detailed usage statistics with filtering.
|
||||
|
||||
Args:
|
||||
start_date: Start date for the query period (required)
|
||||
end_date: End date for the query period (required)
|
||||
api_key_id: Optional filter by API key ID
|
||||
model: Optional filter by model name
|
||||
skip: Number of records to skip (pagination)
|
||||
limit: Maximum number of records to return
|
||||
db: Database session
|
||||
current_user: Authenticated user
|
||||
|
||||
Returns:
|
||||
List of UsageStatsResponse matching the filters
|
||||
"""
|
||||
return get_usage_stats(
|
||||
db=db,
|
||||
user_id=current_user.id,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
api_key_id=api_key_id,
|
||||
model=model,
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
)
|
||||
@@ -16,6 +16,7 @@ from openrouter_monitor.schemas.stats import (
|
||||
StatsByDate,
|
||||
StatsByModel,
|
||||
StatsSummary,
|
||||
UsageStatsResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -253,3 +254,61 @@ def get_dashboard_data(
|
||||
by_date=by_date,
|
||||
top_models=top_models,
|
||||
)
|
||||
|
||||
|
||||
def get_usage_stats(
|
||||
db: Session,
|
||||
user_id: int,
|
||||
start_date: date,
|
||||
end_date: date,
|
||||
api_key_id: Optional[int] = None,
|
||||
model: Optional[str] = None,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
) -> List[UsageStatsResponse]:
|
||||
"""Get detailed usage statistics with filtering.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
user_id: User ID to filter by
|
||||
start_date: Start date for the query period
|
||||
end_date: End date for the query period
|
||||
api_key_id: Optional filter by API key ID
|
||||
model: Optional filter by model name
|
||||
skip: Number of records to skip (pagination)
|
||||
limit: Maximum number of records to return
|
||||
|
||||
Returns:
|
||||
List of UsageStatsResponse matching the filters
|
||||
"""
|
||||
from openrouter_monitor.models import UsageStats
|
||||
|
||||
# Build base query with join to ApiKey for user filtering
|
||||
query = (
|
||||
db.query(UsageStats)
|
||||
.join(ApiKey, UsageStats.api_key_id == ApiKey.id)
|
||||
.filter(ApiKey.user_id == user_id)
|
||||
.filter(UsageStats.date >= start_date)
|
||||
.filter(UsageStats.date <= end_date)
|
||||
)
|
||||
|
||||
# Apply optional filters
|
||||
if api_key_id is not None:
|
||||
query = query.filter(UsageStats.api_key_id == api_key_id)
|
||||
|
||||
if model is not None:
|
||||
query = query.filter(UsageStats.model == model)
|
||||
|
||||
# Apply ordering and pagination
|
||||
results = (
|
||||
query.order_by(UsageStats.date.desc(), UsageStats.model)
|
||||
.offset(skip)
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
|
||||
# Convert to response schema
|
||||
return [
|
||||
UsageStatsResponse.model_validate(record)
|
||||
for record in results
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user