oliver-metadata-tool/app/routers/admin.py
SamoilenkoVadym 3deaa5ef40 Initial commit: Oliver Metadata Tool (FastAPI)
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>
2026-02-09 21:23:42 +00:00

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}