107 lines
3.6 KiB
Python
107 lines
3.6 KiB
Python
"""Organization management API routes (users only)."""
|
|
|
|
import uuid
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.security import get_current_admin, get_current_user
|
|
from app.database import get_db
|
|
from app.models.user import User, Identity
|
|
from app.schemas.schemas import UserOut, UserUpdate
|
|
|
|
from sqlalchemy.orm import selectinload
|
|
|
|
router = APIRouter(prefix="/org", tags=["organization"])
|
|
|
|
|
|
# ─── Users Management ──────────────────────────────────
|
|
|
|
@router.get("/users", response_model=list[UserOut])
|
|
async def list_users(
|
|
tenant_id: uuid.UUID | None = None,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""List users, optionally filtered by tenant."""
|
|
query = (
|
|
select(User)
|
|
.options(selectinload(User.identity))
|
|
.where(User.is_active == True)
|
|
)
|
|
|
|
target_tenant_id = current_user.tenant_id
|
|
if current_user.role in ("platform_admin", "org_admin") and tenant_id:
|
|
target_tenant_id = tenant_id
|
|
if target_tenant_id:
|
|
query = query.where(User.tenant_id == target_tenant_id)
|
|
|
|
query = query.order_by(User.display_name)
|
|
result = await db.execute(query)
|
|
return [UserOut.model_validate(u) for u in result.scalars().all()]
|
|
|
|
|
|
@router.patch("/users/{user_id}", response_model=UserOut)
|
|
async def admin_update_user(
|
|
user_id: uuid.UUID,
|
|
data: UserUpdate,
|
|
current_user: User = Depends(get_current_admin),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Admin update user profile."""
|
|
result = await db.execute(
|
|
select(User)
|
|
.options(selectinload(User.identity))
|
|
.where(User.id == user_id)
|
|
)
|
|
user = result.scalar_one_or_none()
|
|
if not user:
|
|
raise HTTPException(status_code=404, detail="User not found")
|
|
|
|
update_data = data.model_dump(exclude_unset=True)
|
|
|
|
# Validate email uniqueness within tenant if changing
|
|
if "email" in update_data and update_data["email"] != user.email:
|
|
existing = await db.execute(
|
|
select(User)
|
|
.join(Identity, User.identity_id == Identity.id)
|
|
.where(
|
|
Identity.email == update_data["email"],
|
|
User.tenant_id == user.tenant_id,
|
|
User.id != user.id,
|
|
)
|
|
)
|
|
if existing.scalar_one_or_none():
|
|
raise HTTPException(status_code=409, detail="Email already registered")
|
|
|
|
# Validate mobile uniqueness within tenant if changing
|
|
if "primary_mobile" in update_data and update_data["primary_mobile"] != user.primary_mobile:
|
|
existing = await db.execute(
|
|
select(User)
|
|
.join(Identity, User.identity_id == Identity.id)
|
|
.where(
|
|
Identity.phone == update_data["primary_mobile"],
|
|
User.tenant_id == user.tenant_id,
|
|
User.id != user.id,
|
|
)
|
|
)
|
|
if existing.scalar_one_or_none():
|
|
raise HTTPException(status_code=409, detail="Mobile already registered")
|
|
|
|
for field, value in update_data.items():
|
|
setattr(user, field, value)
|
|
await db.flush()
|
|
|
|
# Sync email/phone to OrgMember if changed
|
|
if "email" in update_data or "primary_mobile" in update_data:
|
|
from app.services.registration_service import registration_service
|
|
await registration_service.sync_org_member_contact_from_user(
|
|
db,
|
|
user,
|
|
sync_email="email" in update_data,
|
|
sync_phone="primary_mobile" in update_data,
|
|
)
|
|
|
|
return UserOut.model_validate(user)
|