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), ): updated = await update_profile(db, user, data) 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}"}