"""登录、会话与个人账号接口。""" from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from database import get_db from dependencies import get_current_user from models import Role, User, UserRole from schemas import ( ChangePasswordIn, LoginIn, ProfileUpdate, RoleOut, TokenOut, UserOut, ) from services.security import create_access_token, hash_password, verify_password router = APIRouter(prefix="/auth", tags=["auth"]) def user_roles(db: Session, user: User) -> list[Role]: return ( db.query(Role) .join(UserRole, UserRole.role_id == Role.id) .filter(UserRole.user_id == user.id) .order_by(Role.id.asc()) .all() ) def user_to_out(db: Session, user: User) -> UserOut: roles = user_roles(db, user) return UserOut( id=user.id, username=user.username, display_name=user.display_name, active=user.active, roles=[RoleOut.model_validate(role) for role in roles], created_at=user.created_at, ) @router.post("/login", response_model=TokenOut) def login(payload: LoginIn, db: Session = Depends(get_db)): user = db.query(User).filter(User.username == payload.username).first() if user is None or not verify_password(payload.password, user.password_hash): raise HTTPException(status_code=401, detail="用户名或密码错误") if not user.active: raise HTTPException(status_code=403, detail="账号已被停用") roles = user_roles(db, user) if not roles: raise HTTPException(status_code=403, detail="账号未分配角色") return TokenOut( access_token=create_access_token(user.id), role=roles[0].code, display_name=user.display_name or user.username, ) @router.get("/me", response_model=UserOut) def me(user: User = Depends(get_current_user), db: Session = Depends(get_db)): return user_to_out(db, user) @router.post("/change-password") def change_password( payload: ChangePasswordIn, user: User = Depends(get_current_user), db: Session = Depends(get_db), ): if not verify_password(payload.old_password, user.password_hash): raise HTTPException(status_code=400, detail="原密码不正确") user.password_hash = hash_password(payload.new_password) db.commit() return {"ok": True, "message": "密码已更新,下次登录请使用新密码。"} @router.put("/profile", response_model=UserOut) def update_profile( payload: ProfileUpdate, user: User = Depends(get_current_user), db: Session = Depends(get_db), ): name = payload.display_name.strip() if not name: raise HTTPException(status_code=400, detail="昵称不能为空") user.display_name = name db.commit() return user_to_out(db, user)