from fastapi import APIRouter, Depends, HTTPException from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.core.dependencies import get_current_admin from app.database import get_db from app.models import Follow from app.models.user import User from app.schemas.schemas import UserResponse router = APIRouter() # ── User Management ─────────────────────────────────────────────────────────── class UserWithStats(UserResponse): follow_count: int @router.get("/users", response_model=list[UserWithStats]) async def list_users( db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_admin), ): """List all users with their follow counts.""" users_result = await db.execute(select(User).order_by(User.created_at)) users = users_result.scalars().all() counts_result = await db.execute( select(Follow.user_id, func.count(Follow.id).label("cnt")) .group_by(Follow.user_id) ) counts = {row.user_id: row.cnt for row in counts_result} return [ UserWithStats( id=u.id, email=u.email, is_admin=u.is_admin, notification_prefs=u.notification_prefs or {}, created_at=u.created_at, follow_count=counts.get(u.id, 0), ) for u in users ] @router.delete("/users/{user_id}", status_code=204) async def delete_user( user_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_admin), ): """Delete a user account (cascades to their follows). Cannot delete yourself.""" if user_id == current_user.id: raise HTTPException(status_code=400, detail="Cannot delete your own account") user = await db.get(User, user_id) if not user: raise HTTPException(status_code=404, detail="User not found") await db.delete(user) await db.commit() @router.patch("/users/{user_id}/toggle-admin", response_model=UserResponse) async def toggle_admin( user_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_admin), ): """Promote or demote a user's admin status.""" if user_id == current_user.id: raise HTTPException(status_code=400, detail="Cannot change your own admin status") user = await db.get(User, user_id) if not user: raise HTTPException(status_code=404, detail="User not found") user.is_admin = not user.is_admin await db.commit() await db.refresh(user) return user # ── Celery Tasks ────────────────────────────────────────────────────────────── @router.post("/trigger-poll") async def trigger_poll(current_user: User = Depends(get_current_admin)): from app.workers.congress_poller import poll_congress_bills task = poll_congress_bills.delay() return {"task_id": task.id, "status": "queued"} @router.post("/trigger-member-sync") async def trigger_member_sync(current_user: User = Depends(get_current_admin)): from app.workers.congress_poller import sync_members task = sync_members.delay() return {"task_id": task.id, "status": "queued"} @router.post("/trigger-trend-scores") async def trigger_trend_scores(current_user: User = Depends(get_current_admin)): from app.workers.trend_scorer import calculate_all_trend_scores task = calculate_all_trend_scores.delay() return {"task_id": task.id, "status": "queued"} @router.get("/task-status/{task_id}") async def get_task_status(task_id: str, current_user: User = Depends(get_current_admin)): from app.workers.celery_app import celery_app result = celery_app.AsyncResult(task_id) return { "task_id": task_id, "status": result.status, "result": result.result if result.ready() else None, }