feat(09-01): implement GET /api/admin/overview endpoint — ADMIN-11

- New backend/api/admin/overview.py: aggregate stats (user_count,
  total_storage_bytes, doc_status) + last 10 audit entries in one payload
- Sub-router carries NO prefix (parent __init__.py carries /api/admin)
- Reuses _build_filtered_query_with_handles + _audit_to_dict_with_handles
  from api.audit — no new serializer or query logic
- Registered overview_router on admin parent router in __init__.py
- All 8 ADMIN-11 tests pass; existing admin and audit suites stay green
This commit is contained in:
curo1305
2026-06-12 15:50:38 +02:00
parent 4caeed2d22
commit 41d81c089f
3 changed files with 54 additions and 8 deletions
+2
View File
@@ -13,11 +13,13 @@ main.py continues to use:
app.include_router(admin_router)
"""
from fastapi import APIRouter
from api.admin.overview import router as overview_router
from api.admin.users import router as users_router
from api.admin.quotas import router as quotas_router
from api.admin.ai import router as ai_router
router = APIRouter(prefix="/api/admin", tags=["admin"])
router.include_router(overview_router)
router.include_router(users_router)
router.include_router(quotas_router)
router.include_router(ai_router)
+52
View File
@@ -0,0 +1,52 @@
from __future__ import annotations
from fastapi import APIRouter, Depends
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from db.models import AuditLog, Document, Quota, User
from deps.auth import get_current_admin
from deps.db import get_db
from api.audit import _build_filtered_query_with_handles
from api.audit import _audit_to_dict_with_handles
router = APIRouter() # NO prefix — parent __init__.py carries /api/admin (D-02)
@router.get("/overview")
async def get_overview(
session: AsyncSession = Depends(get_db),
_admin: User = Depends(get_current_admin),
) -> dict:
user_count = await session.scalar(
select(func.count(User.id)).where(User.role == "user")
) or 0
total_storage_bytes = await session.scalar(
select(func.sum(Quota.used_bytes))
) or 0
status_rows = (
await session.execute(
select(Document.status, func.count(Document.id)).group_by(Document.status)
)
).all()
doc_status = {row[0]: row[1] for row in status_rows}
audit_q = (
_build_filtered_query_with_handles(None, None, None, None)
.order_by(AuditLog.created_at.desc())
.limit(10)
)
audit_rows = (await session.execute(audit_q)).all()
recent_audit = [
_audit_to_dict_with_handles(row[0], row[1], row[2], row[3])
for row in audit_rows
]
return {
"user_count": user_count,
"total_storage_bytes": total_storage_bytes,
"doc_status": doc_status,
"recent_audit": recent_audit,
}