trymeapp/app/routers/admin.py

114 lines
3.8 KiB
Python
Raw Normal View History

2026-11-11 22:06:26 +00:00
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import func, select
from sqlalchemy.orm import Session, selectinload
from app.auth import get_active_user, get_admin_user
from app.config import Settings, get_settings
from app.database import get_db
from app.models import Quiz, User
from app.schemas import BanUserIn, QuizSummaryOut, UserOut
from app.services.quiz_service import enrich_quizzes
router = APIRouter(prefix="/api/v1/admin", tags=["Moderation"])
@router.post("/claim", response_model=UserOut)
def claim_initial_moderator(
db: Session = Depends(get_db),
user: User = Depends(get_active_user),
settings: Settings = Depends(get_settings),
) -> UserOut:
if settings.admin_user.strip():
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Moderator account is locked to the configured ADMIN_USER container setting.",
)
existing_admins = db.scalar(select(func.count(User.id)).where(User.is_admin.is_(True))) or 0
if existing_admins > 0:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="An initial moderator account has already been configured.",
)
user.is_admin = True
db.commit()
db.refresh(user)
return UserOut.model_validate(user)
@router.get("/users", response_model=list[UserOut])
def list_users_for_moderation(
db: Session = Depends(get_db),
_admin: User = Depends(get_admin_user),
) -> list[UserOut]:
users = db.scalars(select(User).order_by(User.created_at.desc(), User.id.desc())).all()
return [UserOut.model_validate(u) for u in users]
@router.post("/users/{user_id}/ban", response_model=UserOut)
def ban_user(
user_id: int,
payload: BanUserIn,
db: Session = Depends(get_db),
admin: User = Depends(get_admin_user),
) -> UserOut:
if user_id == admin.id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Moderators cannot ban their own account.",
)
target = db.get(User, user_id)
if target is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found.")
target.is_banned = True
target.ban_reason = payload.reason.strip() or "Suspended by moderator"
db.commit()
db.refresh(target)
return UserOut.model_validate(target)
@router.post("/users/{user_id}/unban", response_model=UserOut)
def unban_user(
user_id: int,
db: Session = Depends(get_db),
_admin: User = Depends(get_admin_user),
) -> UserOut:
target = db.get(User, user_id)
if target is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found.")
target.is_banned = False
target.ban_reason = None
db.commit()
db.refresh(target)
return UserOut.model_validate(target)
@router.get("/quizzes", response_model=list[QuizSummaryOut])
def list_quizzes_for_moderation(
db: Session = Depends(get_db),
admin: User = Depends(get_admin_user),
) -> list[QuizSummaryOut]:
quizzes = list(
db.scalars(
select(Quiz)
.options(selectinload(Quiz.author), selectinload(Quiz.questions))
.where(Quiz.is_removed.is_(False))
.order_by(Quiz.created_at.desc(), Quiz.id.desc())
).all()
)
return enrich_quizzes(db, quizzes, admin)
@router.delete("/quizzes/{quiz_id}", status_code=status.HTTP_204_NO_CONTENT)
def moderator_remove_quiz(
quiz_id: int,
db: Session = Depends(get_db),
_admin: User = Depends(get_admin_user),
) -> None:
quiz = db.get(Quiz, quiz_id)
if quiz is None or quiz.is_removed:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Quiz not found.")
quiz.is_removed = True
db.commit()