"""superadmin/routers/platform.py — Platform-wide operations. Routes: GET / GET /diag GET /health GET /audit POST /announce POST /backup """ from fastapi import APIRouter, Depends, Query from sqlalchemy.ext.asyncio import AsyncSession from app.dependencies import check_health, get_db, get_current_superadmin from app.repositories.audit_repo import AuditRepository from app.schemas.admin import AnnouncementRequest from app.superadmin.routers._utils import _err router = APIRouter() @router.get("/") async def superadmin_index(): """SuperAdmin API index — lists available endpoints.""" return { "message": "SuperAdmin API. Requires SuperAdmin JWT (Bearer token).", "endpoints": { "list_authors": "GET /api/super/authors", "create_author": "POST /api/super/authors", "suspend_author": "POST /api/super/authors/{id}/suspend", "grant_access": "POST /api/super/authors/{id}/grant", "revoke_access": "POST /api/super/grants/{id}/revoke", "audit_log": "GET /api/super/audit", "platform_health": "GET /api/super/health", }, "docs": "/docs", } @router.get("/diag") async def diagnostic(superadmin=Depends(get_current_superadmin)): """R-045: Diagnostic endpoint — requires SuperAdmin auth.""" return {"status": "ok", "router": "superadmin", "auth": "verified"} @router.get("/health") async def platform_health(superadmin=Depends(get_current_superadmin)): """Platform health: Redis, ChromaDB, DB status.""" try: return await check_health() except Exception as e: return _err(e, "platform_health") @router.get("/audit") async def get_audit_log( limit: int = Query(100, ge=1, le=500), cursor: str | None = Query(None), actor_id: str | None = Query(None), action: str | None = Query(None), superadmin=Depends(get_current_superadmin), db: AsyncSession = Depends(get_db), ): """Retrieve the immutable audit log. No DELETE allowed.""" try: audit_repo = AuditRepository(db) entries = await audit_repo.list_recent( limit=limit, cursor=cursor, actor_id=actor_id, action=action ) result = [ { "id": e.id, "timestamp": e.timestamp.isoformat() if e.timestamp else None, "actor_email": e.actor_email, "action": e.action, "target_type": e.target_type, "target_id": e.target_id, "details": e.details, } for e in entries ] return {"entries": result, "count": len(result)} except Exception as e: return _err(e, "get_audit_log") @router.post("/announce", status_code=201) async def broadcast_announcement( body: AnnouncementRequest, superadmin=Depends(get_current_superadmin), db: AsyncSession = Depends(get_db), ): """Broadcast an announcement visible to all authors. R-029: Validated via Pydantic schema.""" try: audit_repo = AuditRepository(db) await audit_repo.log( actor_id=superadmin.id, actor_email=superadmin.email, action="broadcast_announcement", target_type="platform", target_id=None, details={"message": body.message}, ) return {"message": "Announcement sent"} except Exception as e: return _err(e, "broadcast_announcement") @router.post("/backup", status_code=202) async def trigger_backup( superadmin=Depends(get_current_superadmin), db: AsyncSession = Depends(get_db), ): """Dispatch backup to Celery worker (R-080 — non-blocking).""" try: from app.tasks.backup_task import run_backup task = run_backup.delay() audit_repo = AuditRepository(db) await audit_repo.log( actor_id=superadmin.id, actor_email=superadmin.email, action="trigger_backup", target_type="platform", target_id=None, details={"task_id": task.id}, ) return {"message": "Backup dispatched", "task_id": task.id} except Exception as e: return _err(e, "trigger_backup")