integrate sub2api as upstream for auth/keys/usage via FastAPI BFF

Preserve local user table for superDream-specific features while syncing
user lifecycle, API key CRUD and usage queries through sub2api. Admin token
handles reads and user lifecycle; per-user tokens (Fernet-encrypted in DB)
handle key writes that admin endpoints do not expose.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xuyong
2026-04-17 21:23:08 +08:00
parent 20e842a60a
commit 35c0b7de16
30 changed files with 1707 additions and 803 deletions

View File

@@ -1,64 +1,112 @@
from datetime import date
from typing import List, Optional
from typing import Any, Dict, Optional
from fastapi import APIRouter, Depends, Query
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.core.dependencies import get_current_user
from app.datamodels.schemas import DashboardAPIKeysUsageRequest
from app.models import User
from app.services.usage_service import UsageService
from app.datamodels.schemas import (
UsageSummaryResponse, DailyUsageResponse,
ModelUsageResponse, KeyUsageResponse, UsageLogResponse,
)
router = APIRouter(prefix="/usage", tags=["usage"])
@router.get("/summary", response_model=UsageSummaryResponse)
async def usage_summary(
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
return await UsageService.summary(db, user.id)
@router.get("/daily", response_model=List[DailyUsageResponse])
async def usage_daily(
start: Optional[date] = Query(None),
end: Optional[date] = Query(None),
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
return await UsageService.daily(db, user.id, start, end)
@router.get("/by-model", response_model=List[ModelUsageResponse])
async def usage_by_model(
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
return await UsageService.by_model(db, user.id)
@router.get("/by-key", response_model=List[KeyUsageResponse])
async def usage_by_key(
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
return await UsageService.by_key(db, user.id)
@router.get("/logs", response_model=List[UsageLogResponse])
async def usage_logs(
@router.get("")
async def list_logs(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
page_size: int = Query(20, ge=1, le=100),
sort_by: str = Query("created_at"),
sort_order: str = Query("desc"),
api_key_id: Optional[int] = Query(None),
model: Optional[str] = Query(None),
key_id: Optional[str] = Query(None),
start: Optional[date] = Query(None),
end: Optional[date] = Query(None),
request_type: Optional[str] = Query(None),
stream: Optional[bool] = Query(None),
billing_type: Optional[int] = Query(None),
start_date: Optional[str] = Query(None),
end_date: Optional[str] = Query(None),
timezone: Optional[str] = Query(None),
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
return await UsageService.logs(db, user.id, page, size, model, key_id, start, end)
) -> Dict[str, Any]:
return await UsageService.list_logs(
db,
user,
page=page,
page_size=page_size,
sort_by=sort_by,
sort_order=sort_order,
api_key_id=api_key_id,
model=model,
request_type=request_type,
stream=stream,
billing_type=billing_type,
start_date=start_date,
end_date=end_date,
timezone=timezone,
)
@router.get("/stats")
async def stats(
period: Optional[str] = Query(None, regex="^(today|week|month)$"),
start_date: Optional[str] = Query(None),
end_date: Optional[str] = Query(None),
api_key_id: Optional[int] = Query(None),
timezone: Optional[str] = Query(None),
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
) -> Dict[str, Any]:
return await UsageService.stats(
db,
user,
period=period,
start_date=start_date,
end_date=end_date,
api_key_id=api_key_id,
timezone=timezone,
)
@router.get("/dashboard/stats")
async def dashboard_stats(
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
) -> Dict[str, Any]:
return await UsageService.dashboard_stats(db, user)
@router.get("/dashboard/trend")
async def dashboard_trend(
granularity: str = Query("day"),
start_date: Optional[str] = Query(None),
end_date: Optional[str] = Query(None),
timezone: Optional[str] = Query(None),
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
) -> Dict[str, Any]:
return await UsageService.dashboard_trend(
db, user, granularity=granularity, start_date=start_date, end_date=end_date, timezone=timezone
)
@router.get("/dashboard/models")
async def dashboard_models(
start_date: Optional[str] = Query(None),
end_date: Optional[str] = Query(None),
timezone: Optional[str] = Query(None),
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
) -> Dict[str, Any]:
return await UsageService.dashboard_models(
db, user, start_date=start_date, end_date=end_date, timezone=timezone
)
@router.post("/dashboard/api-keys-usage")
async def dashboard_api_keys_usage(
body: DashboardAPIKeysUsageRequest,
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
) -> Dict[str, Any]:
return await UsageService.dashboard_api_keys_usage(db, user, body.api_key_ids)