""" Platform Admin Router — super_admin / platform_admin operations. GET /admin/schools — list all institutes with member + calendar counts GET /admin/stats — platform-level summary """ import os from typing import Any, Dict, List from fastapi import APIRouter, Depends, HTTPException from modules.logger_tool import initialise_logger from modules.auth.supabase_bearer import SupabaseBearer from modules.auth.platform_admin import require_platform_admin from modules.database.supabase.utils.client import SupabaseServiceRoleClient logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True) router = APIRouter() def _sb() -> SupabaseServiceRoleClient: return SupabaseServiceRoleClient() @router.get("/schools") async def list_all_schools( _: dict = Depends(require_platform_admin), ) -> Dict[str, Any]: """List every institute with basic counts. Platform admin only.""" sb = _sb() institutes = ( sb.supabase.table("institutes") .select("id,name,urn,website,status,created_at,neo4j_uuid_string") .order("name") .execute() .data or [] ) if not institutes: return {"status": "ok", "schools": [], "total": 0} inst_ids = [i["id"] for i in institutes] # Member counts per institute all_members = ( sb.supabase.table("institute_memberships") .select("institute_id,role") .in_("institute_id", inst_ids) .execute() .data or [] ) from collections import defaultdict member_counts: Dict[str, Dict[str, int]] = defaultdict(lambda: {"staff": 0, "students": 0}) staff_roles = {"teacher", "school_admin", "department_head"} for m in all_members: iid = m["institute_id"] if m["role"] in staff_roles: member_counts[iid]["staff"] += 1 elif m["role"] == "student": member_counts[iid]["students"] += 1 # Calendar presence per institute term_rows = ( sb.supabase.table("academic_terms") .select("institute_id") .in_("institute_id", inst_ids) .execute() .data or [] ) has_calendar = {r["institute_id"] for r in term_rows} # Pending invitations count inv_rows = ( sb.supabase.table("invitations") .select("institute_id") .eq("status", "pending") .in_("institute_id", inst_ids) .execute() .data or [] ) from collections import Counter inv_counts = Counter(r["institute_id"] for r in inv_rows) schools = [] for inst in institutes: iid = inst["id"] mc = member_counts.get(iid, {}) schools.append({ **inst, "staff_count": mc.get("staff", 0), "student_count": mc.get("students", 0), "has_calendar": iid in has_calendar, "pending_invitations": inv_counts.get(iid, 0), }) return {"status": "ok", "schools": schools, "total": len(schools)} @router.get("/stats") async def platform_stats( _: dict = Depends(require_platform_admin), ) -> Dict[str, Any]: """High-level platform counts. Platform admin only.""" sb = _sb() inst_count = len( sb.supabase.table("institutes").select("id").execute().data or [] ) profile_count = len( sb.supabase.table("profiles").select("id").execute().data or [] ) lesson_count = len( sb.supabase.table("taught_lessons").select("id").execute().data or [] ) inv_count = len( sb.supabase.table("invitations").select("id").eq("status", "pending").execute().data or [] ) return { "status": "ok", "schools": inst_count, "profiles": profile_count, "taught_lessons": lesson_count, "pending_invitations": inv_count, } @router.post("/reset") async def reset_environment( _: dict = Depends(require_platform_admin), ) -> Dict[str, Any]: """DESTRUCTIVE: wipe all test data. Neo4j + Supabase. Platform admin only.""" import asyncio from run.initialization.reset_environment import reset as _reset loop = asyncio.get_event_loop() result = await loop.run_in_executor(None, _reset) return {"status": "ok", **result} @router.post("/seed") async def seed_environment( _: dict = Depends(require_platform_admin), ) -> Dict[str, Any]: """Idempotent rebuild: both schools, global calendar, 20 test accounts. Platform admin only.""" import asyncio from run.initialization.seed_environment import seed as _seed loop = asyncio.get_event_loop() result = await loop.run_in_executor(None, _seed) return {"status": "ok", **result} @router.post("/seed-timetable") async def seed_greenfield_timetable( _: dict = Depends(require_platform_admin), ) -> Dict[str, Any]: """Seed full timetable + taught lessons for Greenfield Academy. Platform admin only.""" import asyncio from run.initialization.seed_greenfield_timetable import seed as _seed loop = asyncio.get_event_loop() result = await loop.run_in_executor(None, _seed) return {"status": "ok", **result}