530a8b70a1
首页卡片支持修改/删除;详情页设置 Tab 可维护学校、年级与头像;系统设置新增数据备份下载与恢复;备份默认存 /root/grade-archive-backups,详见 docs/BACKUP.md。 Co-authored-by: Cursor <cursoragent@cursor.com>
135 lines
4.4 KiB
Python
135 lines
4.4 KiB
Python
import uuid
|
|
from pathlib import Path
|
|
|
|
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
|
|
from fastapi.responses import FileResponse
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.config import settings
|
|
from app.core.database import get_db
|
|
from app.core.deps import get_current_user
|
|
from app.models.user import Student, User
|
|
from app.schemas import StudentCreate, StudentOut, StudentUpdate
|
|
from app.services.student_access import get_student_for_user
|
|
from app.services.student_avatar import delete_avatar_file, save_avatar
|
|
|
|
router = APIRouter(prefix="/students", tags=["students"])
|
|
|
|
|
|
def _to_out(student: Student) -> StudentOut:
|
|
return StudentOut.from_student(student)
|
|
|
|
|
|
@router.get("", response_model=list[StudentOut])
|
|
def list_students(
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
rows = (
|
|
db.query(Student)
|
|
.filter(Student.user_id == current_user.id)
|
|
.order_by(Student.created_at.desc())
|
|
.all()
|
|
)
|
|
return [_to_out(row) for row in rows]
|
|
|
|
|
|
@router.post("", response_model=StudentOut, status_code=status.HTTP_201_CREATED)
|
|
def create_student(
|
|
data: StudentCreate,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
student = Student(user_id=current_user.id, **data.model_dump())
|
|
db.add(student)
|
|
db.commit()
|
|
db.refresh(student)
|
|
return _to_out(student)
|
|
|
|
|
|
@router.get("/{student_id}", response_model=StudentOut)
|
|
def get_student(
|
|
student_id: uuid.UUID,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
student = get_student_for_user(db, student_id, current_user.id)
|
|
return _to_out(student)
|
|
|
|
|
|
@router.patch("/{student_id}", response_model=StudentOut)
|
|
def update_student(
|
|
student_id: uuid.UUID,
|
|
data: StudentUpdate,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
student = get_student_for_user(db, student_id, current_user.id)
|
|
for key, value in data.model_dump(exclude_unset=True).items():
|
|
setattr(student, key, value)
|
|
db.commit()
|
|
db.refresh(student)
|
|
return _to_out(student)
|
|
|
|
|
|
@router.delete("/{student_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
def delete_student(
|
|
student_id: uuid.UUID,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
student = get_student_for_user(db, student_id, current_user.id)
|
|
delete_avatar_file(student.avatar_path)
|
|
db.delete(student)
|
|
db.commit()
|
|
|
|
|
|
@router.post("/{student_id}/avatar", response_model=StudentOut)
|
|
async def upload_avatar(
|
|
student_id: uuid.UUID,
|
|
file: UploadFile = File(...),
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
student = get_student_for_user(db, student_id, current_user.id)
|
|
content = await file.read()
|
|
if len(content) > settings.MAX_UPLOAD_SIZE:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="文件超过10MB限制")
|
|
if not (file.content_type or "").startswith("image/"):
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="请上传图片文件")
|
|
|
|
delete_avatar_file(student.avatar_path)
|
|
student.avatar_path = save_avatar(str(current_user.id), str(student.id), content)
|
|
db.commit()
|
|
db.refresh(student)
|
|
return _to_out(student)
|
|
|
|
|
|
@router.delete("/{student_id}/avatar", response_model=StudentOut)
|
|
def remove_avatar(
|
|
student_id: uuid.UUID,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
student = get_student_for_user(db, student_id, current_user.id)
|
|
delete_avatar_file(student.avatar_path)
|
|
student.avatar_path = None
|
|
db.commit()
|
|
db.refresh(student)
|
|
return _to_out(student)
|
|
|
|
|
|
@router.get("/{student_id}/avatar")
|
|
def get_avatar(
|
|
student_id: uuid.UUID,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
student = get_student_for_user(db, student_id, current_user.id)
|
|
if not student.avatar_path:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="未设置头像")
|
|
path = Path(settings.UPLOAD_DIR) / student.avatar_path
|
|
if not path.is_file():
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="头像文件不存在")
|
|
return FileResponse(path, media_type="image/jpeg")
|