Complete Flask → FastAPI migration with: - FastAPI app with session auth, Azure AD SSO, rate limiting - SQLite-backed session store (survives restarts) - Bulk AI metadata generation with SSE progress - Admin panel (user management, audit log, AI usage) - Subpath deployment support (ROOT_PATH config) - Docker + deploy.sh for production deployment - Test suite (auth, upload, templates, imports, admin, sessions) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
126 lines
3.9 KiB
Python
126 lines
3.9 KiB
Python
"""Admin router: user management, audit log, AI usage stats."""
|
|
|
|
import logging
|
|
from typing import Dict
|
|
|
|
from fastapi import APIRouter, Request, Depends
|
|
from fastapi.responses import HTMLResponse, JSONResponse
|
|
from fastapi.templating import Jinja2Templates
|
|
|
|
from ..config import get_settings
|
|
from ..dependencies import get_current_admin, get_database
|
|
from ..services.admin_service import AdminService
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/admin", tags=["admin"])
|
|
|
|
_templates: Jinja2Templates = None
|
|
_admin_service: AdminService = None
|
|
|
|
|
|
def set_templates(templates: Jinja2Templates):
|
|
global _templates
|
|
_templates = templates
|
|
|
|
|
|
def _get_admin_service() -> AdminService:
|
|
global _admin_service
|
|
if _admin_service is None:
|
|
_admin_service = AdminService(database=get_database())
|
|
return _admin_service
|
|
|
|
|
|
@router.get("", response_class=HTMLResponse)
|
|
async def admin_dashboard(request: Request, user: Dict = Depends(get_current_admin)):
|
|
"""Admin dashboard page."""
|
|
svc = _get_admin_service()
|
|
stats = svc.get_dashboard_stats()
|
|
return _templates.TemplateResponse(
|
|
"admin.html",
|
|
{
|
|
"request": request,
|
|
"username": user["username"],
|
|
"stats": stats,
|
|
},
|
|
)
|
|
|
|
|
|
@router.get("/users")
|
|
async def list_users(
|
|
include_inactive: bool = False,
|
|
user: Dict = Depends(get_current_admin),
|
|
):
|
|
"""List all users."""
|
|
svc = _get_admin_service()
|
|
users = svc.list_users(include_inactive=include_inactive)
|
|
return {"success": True, "users": users}
|
|
|
|
|
|
@router.post("/users")
|
|
async def create_user(
|
|
request: Request,
|
|
user: Dict = Depends(get_current_admin),
|
|
):
|
|
"""Create a new user."""
|
|
try:
|
|
data = await request.json()
|
|
svc = _get_admin_service()
|
|
user_id = svc.create_user(
|
|
username=data.get("username", "").strip(),
|
|
email=data.get("email", "").strip(),
|
|
full_name=data.get("full_name", "").strip(),
|
|
role=data.get("role", "user"),
|
|
password=data.get("password"),
|
|
auth_method=data.get("auth_method", "local"),
|
|
)
|
|
if user_id:
|
|
db = get_database()
|
|
db.log_action(user["id"], "admin_create_user", f"Created user {data.get('username')} (ID: {user_id})")
|
|
return {"success": True, "user_id": user_id}
|
|
return JSONResponse({"error": "Failed to create user (username may already exist)"}, status_code=400)
|
|
except Exception as e:
|
|
return JSONResponse({"error": str(e)}, status_code=500)
|
|
|
|
|
|
@router.put("/users/{user_id}")
|
|
async def update_user(
|
|
user_id: int,
|
|
request: Request,
|
|
admin: Dict = Depends(get_current_admin),
|
|
):
|
|
"""Update user (role, is_active, full_name, email)."""
|
|
try:
|
|
data = await request.json()
|
|
svc = _get_admin_service()
|
|
success = svc.update_user(user_id, data)
|
|
if success:
|
|
db = get_database()
|
|
db.log_action(admin["id"], "admin_update_user", f"Updated user {user_id}: {data}")
|
|
return {"success": True}
|
|
return JSONResponse({"error": "No changes applied"}, status_code=400)
|
|
except Exception as e:
|
|
return JSONResponse({"error": str(e)}, status_code=500)
|
|
|
|
|
|
@router.get("/audit")
|
|
async def get_audit_log(
|
|
user_id: int = None,
|
|
action: str = None,
|
|
limit: int = 100,
|
|
offset: int = 0,
|
|
admin: Dict = Depends(get_current_admin),
|
|
):
|
|
"""Get audit log with optional filters."""
|
|
svc = _get_admin_service()
|
|
entries = svc.get_audit_log(user_id=user_id, action=action, limit=limit, offset=offset)
|
|
return {"success": True, "entries": entries, "count": len(entries)}
|
|
|
|
|
|
@router.get("/ai-usage")
|
|
async def get_ai_usage(admin: Dict = Depends(get_current_admin)):
|
|
"""Get AI usage statistics."""
|
|
svc = _get_admin_service()
|
|
stats = svc.get_ai_usage_stats()
|
|
by_user = svc.get_ai_usage_by_user()
|
|
return {"success": True, "stats": stats, "by_user": by_user}
|