from typing import Optional from fastapi import APIRouter, Body, Depends, HTTPException, Path, Query from pydantic import BaseModel from .agents.clients import get_redis_client from .auth import _service_client, invalidate_status_cache, verify_token router = APIRouter(prefix="/admin", tags=["Admin"]) # Prefix used by the multi-agent response cache (_cache_key in # agents_orchestration.py builds keys as "mac:::"). _PROMPT_CACHE_PREFIX = "mac:" _ALLOWED_STATUSES = {"pending", "approved", "rejected"} def require_admin(token_user_id: str = Depends(verify_token)) -> str: """Only users whose profiles.role is 'admin' may manage approvals.""" try: res = _service_client().table("profiles").select("role").eq("id", token_user_id).execute() except Exception as e: raise HTTPException(status_code=500, detail=str(e)) role = res.data[0].get("role") if res.data else None if role != "admin": raise HTTPException(status_code=403, detail="Admin access required") return token_user_id @router.get("/users") async def list_users( status: Optional[str] = Query(default=None), _admin_id: str = Depends(require_admin), ): if status is not None and status not in _ALLOWED_STATUSES: raise HTTPException(status_code=422, detail=f"status must be one of {sorted(_ALLOWED_STATUSES)}") try: query = ( _service_client() .table("profiles") .select("id, email, full_name, role, status, created_at") .order("created_at", desc=True) ) if status is not None: query = query.eq("status", status) res = query.execute() except Exception as e: raise HTTPException(status_code=500, detail=str(e)) return {"data": res.data or []} class StatusUpdate(BaseModel): status: str @router.post("/users/{user_id}/status") async def set_user_status( user_id: str = Path(...), payload: StatusUpdate = Body(...), admin_id: str = Depends(require_admin), ): if payload.status not in ("approved", "rejected"): raise HTTPException(status_code=422, detail="status must be 'approved' or 'rejected'") if user_id == admin_id: raise HTTPException(status_code=400, detail="Admins cannot change their own status") try: res = ( _service_client() .table("profiles") .update({"status": payload.status}) .eq("id", user_id) .execute() ) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) if not res.data: raise HTTPException(status_code=404, detail="User not found") invalidate_status_cache(user_id) return {"data": res.data[0]} @router.post("/cache/clear-prompts") async def clear_prompt_cache(_admin_id: str = Depends(require_admin)): """Delete every cached multi-agent response (the "mac:*" keys in Redis). These are the prompt/response cache entries built by the chat pipeline; they are regenerated on demand, so clearing them just forces fresh answers. """ try: redis = get_redis_client() keys = [key async for key in redis.scan_iter(match=f"{_PROMPT_CACHE_PREFIX}*", count=500)] deleted = await redis.delete(*keys) if keys else 0 except Exception as e: raise HTTPException(status_code=502, detail=f"Failed to clear cache: {e}") return {"deleted": deleted}