114 lines
3.8 KiB
Python
114 lines
3.8 KiB
Python
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()
|