diff --git a/backend/app/api/auth.py b/backend/app/api/auth.py index bb0fc42..88bd4dc 100644 --- a/backend/app/api/auth.py +++ b/backend/app/api/auth.py @@ -16,7 +16,7 @@ from app.schemas.auth import ( RegisterRequest, TokenResponse, ) -from app.schemas.user import UserResponse +from app.schemas.user import ProfileUpdate, UserResponse from app.services.auth_service import ( activate_mfa, authenticate_user, @@ -26,6 +26,7 @@ from app.services.auth_service import ( register_user, revoke_refresh_token, setup_mfa, + update_profile, ) router = APIRouter() @@ -150,3 +151,15 @@ def change_password_endpoint( def me(current_user: User = Depends(get_current_user)): """Return the currently authenticated user's profile.""" return UserResponse.model_validate(current_user) + + +@router.put("/profile", response_model=UserResponse) +def update_profile_endpoint( + data: ProfileUpdate, + db: Session = Depends(get_db), + current_user: User = Depends(get_current_user), +): + """Update the authenticated user's profile.""" + update_data = data.model_dump(exclude_unset=True) + user = update_profile(db, current_user, **update_data) + return UserResponse.model_validate(user) diff --git a/backend/app/services/auth_service.py b/backend/app/services/auth_service.py index 4ae017a..84db42d 100644 --- a/backend/app/services/auth_service.py +++ b/backend/app/services/auth_service.py @@ -275,3 +275,47 @@ def change_password( user.password_hash = hash_password(new_password) user.must_change_password = False db.commit() + + +# --------------------------------------------------------------------------- +# Profile update +# --------------------------------------------------------------------------- + +def update_profile( + db: Session, + user: User, + username: str | None = None, + email: str | None = None, + first_name: str | None = None, + last_name: str | None = None, + display_name: str | None = None, +) -> User: + """Update the authenticated user's own profile fields.""" + if username and username != user.username: + existing = db.query(User).filter(User.username == username, User.id != user.id).first() + if existing: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail="Username already taken", + ) + user.username = username + + if email and email != user.email: + existing = db.query(User).filter(User.email == email, User.id != user.id).first() + if existing: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail="Email already taken", + ) + user.email = email + + if first_name is not None: + user.first_name = first_name + if last_name is not None: + user.last_name = last_name + if display_name is not None: + user.display_name = display_name + + db.commit() + db.refresh(user) + return user