from typing import List from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from app.core.database import get_db from app.core.deps import get_current_active_user, require_role from app.core.security import get_password_hash from app.models.user import User from app.schemas.user import UserRead, UserUpdate, UserCreate router = APIRouter() @router.get("/", response_model=List[UserRead]) async def list_users( skip: int = 0, limit: int = 100, current_user: User = Depends(require_role(["admin"])), db: Session = Depends(get_db) ): """ List all users (admin only, scoped to tenant) Admins can only see users within their own tenant unless they have cross-tenant access. """ # Scope to tenant query = db.query(User).filter(User.tenant_id == current_user.tenant_id) users = query.offset(skip).limit(limit).all() return users @router.get("/{user_id}", response_model=UserRead) async def get_user( user_id: int, current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db) ): """ Get user by ID Users can view their own profile or admins can view users in their tenant. """ user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found" ) # Check permissions: user can view themselves or admin can view users in their tenant if user.id != current_user.id and ( current_user.role != "admin" or user.tenant_id != current_user.tenant_id ): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized to view this user" ) return user @router.put("/{user_id}", response_model=UserRead) async def update_user( user_id: int, user_update: UserUpdate, current_user: User = Depends(require_role(["admin"])), db: Session = Depends(get_db) ): """ Update user (admin only) Admins can update users within their tenant. """ user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found" ) # Check tenant access if user.tenant_id != current_user.tenant_id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized to update this user" ) # Update fields if user_update.username is not None: # Check if new username is already taken existing_user = db.query(User).filter( User.username == user_update.username, User.id != user_id ).first() if existing_user: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Username already taken" ) user.username = user_update.username if user_update.password is not None: user.password_hash = get_password_hash(user_update.password) if user_update.role is not None: user.role = user_update.role if user_update.is_active is not None: user.is_active = user_update.is_active db.commit() db.refresh(user) return user @router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_user( user_id: int, current_user: User = Depends(require_role(["admin"])), db: Session = Depends(get_db) ): """ Deactivate user (admin only) Soft delete by setting is_active to False. """ user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found" ) # Check tenant access if user.tenant_id != current_user.tenant_id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized to delete this user" ) # Prevent self-deletion if user.id == current_user.id: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot delete your own account" ) # Soft delete user.is_active = False db.commit() return None