| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- from fastapi import APIRouter, Depends, HTTPException
- from sqlalchemy import delete, select
- from sqlalchemy.orm import Session, selectinload
- from app.api import deps
- from app.core.security import get_password_hash
- from app.models import GlobalRole, User, UserOrganization
- from app.schemas.requests import UserCreateRequest, UserProfileUpdateRequest, UserUpdatePasswordRequest
- from app.schemas.responses import UserResponse
- router = APIRouter()
- @router.get("", response_model=list[UserResponse])
- async def list_users(
- current_user: User = Depends(deps.get_current_user),
- session: Session = Depends(deps.get_session),
- ):
- """Returns users. super_admin sees everyone; anyone else sees only users
- who share at least one organization with them -- enough to find teammates
- when adding commission members or org roles, without leaking the full
- cross-organization user directory."""
- if current_user.global_role == GlobalRole.SUPER_ADMIN:
- return session.execute(
- select(User).options(selectinload(User.organizations))
- ).scalars().all()
- org_ids = [m.organization_id for m in current_user.organizations]
- if not org_ids:
- return [current_user]
- result = session.execute(
- select(User)
- .join(UserOrganization, UserOrganization.user_id == User.id)
- .where(UserOrganization.organization_id.in_(org_ids))
- .options(selectinload(User.organizations))
- .distinct()
- )
- return result.scalars().all()
- @router.get("/me", response_model=UserResponse)
- async def read_current_user(
- current_user: User = Depends(deps.get_current_user),
- ):
- """Get current user"""
- return current_user
- @router.patch("/me", response_model=UserResponse)
- async def update_current_user_profile(
- payload: UserProfileUpdateRequest,
- current_user: User = Depends(deps.get_current_user),
- session: Session = Depends(deps.get_session),
- ):
- """Update the caller's own display name and/or contact phone number.
- This is the only place phone_number is ever written -- commission
- contact info (Commission.contacts) is derived from it, not stored
- separately."""
- if payload.name is not None:
- current_user.name = payload.name
- if payload.phone_number is not None:
- current_user.phone_number = payload.phone_number
- session.add(current_user)
- session.commit()
- session.refresh(current_user)
- return current_user
- @router.delete("/me", status_code=204)
- async def delete_current_user(
- current_user: User = Depends(deps.get_current_user),
- session: Session = Depends(deps.get_session),
- ):
- """Delete current user"""
- session.execute(delete(User).where(User.id == current_user.id))
- session.commit()
- @router.post("/update-password", response_model=UserResponse)
- async def reset_current_user_password(
- user_update_password: UserUpdatePasswordRequest,
- session: Session = Depends(deps.get_session),
- current_user: User = Depends(deps.get_current_user),
- ):
- """Update current user password"""
- current_user.hashed_password = get_password_hash(user_update_password.password)
- session.add(current_user)
- session.commit()
- return current_user
- @router.post("/register", response_model=UserResponse)
- async def register_new_user(
- new_user: UserCreateRequest,
- session: Session = Depends(deps.get_session),
- ):
- """Create new user. Does not attach the user to any organization --
- a super_admin must add them via POST /organizations/{id}/members
- afterward."""
- result = session.execute(select(User).where(User.email == new_user.email))
- if result.scalars().first() is not None:
- raise HTTPException(status_code=400, detail="Cannot use this email address")
- user = User(
- email=new_user.email,
- name=new_user.name,
- hashed_password=get_password_hash(new_user.password),
- )
- session.add(user)
- session.commit()
- session.refresh(user)
- return user
|