users.py 3.9 KB

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