124 lines
3.9 KiB
Python
124 lines
3.9 KiB
Python
import json
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, status
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.deps import get_current_user, require_role
|
|
from app.db.database import get_db
|
|
from app.db.models import User
|
|
from app.schemas.user import UserOut, UserUpdate, UserListItem, UserStatusUpdate
|
|
from app.schemas.common import PageResponse
|
|
from app.services.user_service import (
|
|
update_profile,
|
|
update_user_status,
|
|
list_users,
|
|
get_user_by_id,
|
|
)
|
|
from app.services.cos_service import upload_image
|
|
from app.services.email_service import send_approval_notification
|
|
|
|
router = APIRouter(prefix="/api/users", tags=["users"])
|
|
|
|
|
|
@router.get("/me", response_model=UserOut)
|
|
async def get_my_profile(user: User = Depends(get_current_user)):
|
|
return UserOut.model_validate(user)
|
|
|
|
|
|
@router.put("/me", response_model=UserOut)
|
|
async def update_my_profile(
|
|
data: UserUpdate,
|
|
user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
try:
|
|
updated = await update_profile(db, user, data)
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
return UserOut.model_validate(updated)
|
|
|
|
|
|
@router.post("/me/avatar")
|
|
async def upload_avatar(
|
|
file: UploadFile = File(...),
|
|
user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
contents = await file.read()
|
|
if len(contents) > 5 * 1024 * 1024: # 5MB limit
|
|
raise HTTPException(status_code=400, detail="File too large (max 5MB)")
|
|
|
|
if file.content_type not in {"image/jpeg", "image/png", "image/gif", "image/webp"}:
|
|
raise HTTPException(status_code=400, detail="Invalid file type")
|
|
|
|
url = upload_image(f"avatars/{user.id}", file.filename or "avatar.jpg", contents, file.content_type)
|
|
user.avatar_url = url
|
|
await db.commit()
|
|
return {"avatar_url": url}
|
|
|
|
|
|
@router.get("/", response_model=PageResponse[UserListItem])
|
|
async def list_all_users(
|
|
page: int = 1,
|
|
page_size: int = 20,
|
|
class_id: int | None = None,
|
|
status: str | None = None,
|
|
role: str | None = None,
|
|
admin: User = Depends(require_role("super_admin")),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
users, total = await list_users(db, page, page_size, class_id, status, role)
|
|
total_pages = (total + page_size - 1) // page_size
|
|
return PageResponse(
|
|
items=[UserListItem.model_validate(u) for u in users],
|
|
total=total,
|
|
page=page,
|
|
page_size=page_size,
|
|
total_pages=total_pages,
|
|
)
|
|
|
|
|
|
@router.put("/{user_id}/status")
|
|
async def change_user_status(
|
|
user_id: int,
|
|
data: UserStatusUpdate,
|
|
admin: User = Depends(require_role("super_admin", "class_admin")),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
target = await get_user_by_id(db, user_id)
|
|
if target is None:
|
|
raise HTTPException(status_code=404, detail="User not found")
|
|
|
|
# Class admin can only manage users in their own class
|
|
if admin.role == "class_admin" and target.class_id != admin.class_id:
|
|
raise HTTPException(
|
|
status_code=403, detail="Cannot manage users outside your class"
|
|
)
|
|
|
|
updated = await update_user_status(db, user_id, data.status, data.role)
|
|
|
|
# Send email notification
|
|
if data.status in ("approved", "rejected"):
|
|
await send_approval_notification(target.email, data.status == "approved")
|
|
|
|
return {"message": f"User status updated to {data.status}"}
|
|
|
|
|
|
@router.put("/{user_id}/role")
|
|
async def change_user_role(
|
|
user_id: int,
|
|
role: str,
|
|
admin: User = Depends(require_role("super_admin")),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
if role not in ("super_admin", "class_admin", "student"):
|
|
raise HTTPException(status_code=400, detail="Invalid role")
|
|
|
|
target = await get_user_by_id(db, user_id)
|
|
if target is None:
|
|
raise HTTPException(status_code=404, detail="User not found")
|
|
|
|
target.role = role
|
|
await db.commit()
|
|
return {"message": f"User role updated to {role}"}
|