users.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. from fastapi import APIRouter, Depends, HTTPException
  2. from sqlalchemy import delete, select
  3. from sqlalchemy.orm import Session, selectinload
  4. from app.api import deps
  5. from app.core.security import get_password_hash
  6. from app.models import GlobalRole, User, UserOrganization
  7. from app.schemas.requests import UserCreateRequest, UserProfileUpdateRequest, UserUpdatePasswordRequest
  8. from app.schemas.responses import UserResponse
  9. router = APIRouter()
  10. @router.get("", response_model=list[UserResponse])
  11. async def list_users(
  12. current_user: User = Depends(deps.get_current_user),
  13. session: Session = Depends(deps.get_session),
  14. ):
  15. """Returns users. super_admin sees everyone; anyone else sees only users
  16. who share at least one organization with them -- enough to find teammates
  17. when adding commission members or org roles, without leaking the full
  18. cross-organization user directory."""
  19. if current_user.global_role == GlobalRole.SUPER_ADMIN:
  20. return session.execute(
  21. select(User).options(selectinload(User.organizations))
  22. ).scalars().all()
  23. org_ids = [m.organization_id for m in current_user.organizations]
  24. if not org_ids:
  25. return [current_user]
  26. result = session.execute(
  27. select(User)
  28. .join(UserOrganization, UserOrganization.user_id == User.id)
  29. .where(UserOrganization.organization_id.in_(org_ids))
  30. .options(selectinload(User.organizations))
  31. .distinct()
  32. )
  33. return result.scalars().all()
  34. @router.get("/me", response_model=UserResponse)
  35. async def read_current_user(
  36. current_user: User = Depends(deps.get_current_user),
  37. ):
  38. """Get current user"""
  39. return current_user
  40. @router.patch("/me", response_model=UserResponse)
  41. async def update_current_user_profile(
  42. payload: UserProfileUpdateRequest,
  43. current_user: User = Depends(deps.get_current_user),
  44. session: Session = Depends(deps.get_session),
  45. ):
  46. """Update the caller's own display name and/or contact phone number.
  47. This is the only place phone_number is ever written -- commission
  48. contact info (Commission.contacts) is derived from it, not stored
  49. separately."""
  50. if payload.name is not None:
  51. current_user.name = payload.name
  52. if payload.phone_number is not None:
  53. current_user.phone_number = payload.phone_number
  54. session.add(current_user)
  55. session.commit()
  56. session.refresh(current_user)
  57. return current_user
  58. @router.delete("/me", status_code=204)
  59. async def delete_current_user(
  60. current_user: User = Depends(deps.get_current_user),
  61. session: Session = Depends(deps.get_session),
  62. ):
  63. """Delete current user"""
  64. session.execute(delete(User).where(User.id == current_user.id))
  65. session.commit()
  66. @router.post("/update-password", response_model=UserResponse)
  67. async def reset_current_user_password(
  68. user_update_password: UserUpdatePasswordRequest,
  69. session: Session = Depends(deps.get_session),
  70. current_user: User = Depends(deps.get_current_user),
  71. ):
  72. """Update current user password"""
  73. current_user.hashed_password = get_password_hash(user_update_password.password)
  74. session.add(current_user)
  75. session.commit()
  76. return current_user
  77. @router.post("/register", response_model=UserResponse)
  78. async def register_new_user(
  79. new_user: UserCreateRequest,
  80. session: Session = Depends(deps.get_session),
  81. ):
  82. """Create new user. Does not attach the user to any organization --
  83. a super_admin must add them via POST /organizations/{id}/members
  84. afterward."""
  85. result = session.execute(select(User).where(User.email == new_user.email))
  86. if result.scalars().first() is not None:
  87. raise HTTPException(status_code=400, detail="Cannot use this email address")
  88. user = User(
  89. email=new_user.email,
  90. name=new_user.name,
  91. hashed_password=get_password_hash(new_user.password),
  92. )
  93. session.add(user)
  94. session.commit()
  95. session.refresh(user)
  96. return user