Improve user delete flow
This commit is contained in:
@@ -311,4 +311,257 @@ async def delete_tenant_complete(
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to delete tenant: {str(e)}"
|
||||
)
|
||||
|
||||
@router.get("/users/{user_id}/tenants")
|
||||
async def get_user_tenants(
|
||||
user_id: str,
|
||||
current_user = Depends(get_current_user_dep),
|
||||
_admin_check = Depends(require_admin_role),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Get all tenant memberships for a user (admin only)"""
|
||||
try:
|
||||
user_uuid = uuid.UUID(user_id)
|
||||
except ValueError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid user ID format"
|
||||
)
|
||||
|
||||
try:
|
||||
from app.models.tenants import TenantMember, Tenant
|
||||
|
||||
# Get all memberships for the user
|
||||
membership_query = select(TenantMember, Tenant).join(
|
||||
Tenant, TenantMember.tenant_id == Tenant.id
|
||||
).where(TenantMember.user_id == user_uuid)
|
||||
|
||||
result = await db.execute(membership_query)
|
||||
memberships_data = result.all()
|
||||
|
||||
memberships = []
|
||||
for membership, tenant in memberships_data:
|
||||
memberships.append({
|
||||
"user_id": str(membership.user_id),
|
||||
"tenant_id": str(membership.tenant_id),
|
||||
"tenant_name": tenant.name,
|
||||
"role": membership.role,
|
||||
"joined_at": membership.created_at.isoformat() if membership.created_at else None
|
||||
})
|
||||
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"total_tenants": len(memberships),
|
||||
"memberships": memberships
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get user tenants", user_id=user_id, error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to get user tenants"
|
||||
)
|
||||
|
||||
@router.get("/tenants/{tenant_id}/check-other-admins/{user_id}")
|
||||
async def check_tenant_has_other_admins(
|
||||
tenant_id: str,
|
||||
user_id: str,
|
||||
current_user = Depends(get_current_user_dep),
|
||||
_admin_check = Depends(require_admin_role),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Check if tenant has other admin users besides the specified user"""
|
||||
try:
|
||||
tenant_uuid = uuid.UUID(tenant_id)
|
||||
user_uuid = uuid.UUID(user_id)
|
||||
except ValueError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid UUID format"
|
||||
)
|
||||
|
||||
try:
|
||||
from app.models.tenants import TenantMember
|
||||
|
||||
# Count admin/owner members excluding the specified user
|
||||
admin_count_query = select(func.count(TenantMember.id)).where(
|
||||
TenantMember.tenant_id == tenant_uuid,
|
||||
TenantMember.role.in_(['admin', 'owner']),
|
||||
TenantMember.user_id != user_uuid
|
||||
)
|
||||
|
||||
result = await db.execute(admin_count_query)
|
||||
admin_count = result.scalar()
|
||||
|
||||
return {
|
||||
"tenant_id": tenant_id,
|
||||
"excluded_user_id": user_id,
|
||||
"has_other_admins": admin_count > 0,
|
||||
"other_admin_count": admin_count
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to check tenant admins",
|
||||
tenant_id=tenant_id,
|
||||
user_id=user_id,
|
||||
error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to check tenant admins"
|
||||
)
|
||||
|
||||
@router.post("/tenants/{tenant_id}/transfer-ownership")
|
||||
async def transfer_tenant_ownership(
|
||||
tenant_id: str,
|
||||
transfer_data: dict, # {"current_owner_id": str, "new_owner_id": str}
|
||||
current_user = Depends(get_current_user_dep),
|
||||
_admin_check = Depends(require_admin_role),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Transfer tenant ownership from one user to another (admin only)"""
|
||||
try:
|
||||
tenant_uuid = uuid.UUID(tenant_id)
|
||||
current_owner_id = uuid.UUID(transfer_data.get("current_owner_id"))
|
||||
new_owner_id = uuid.UUID(transfer_data.get("new_owner_id"))
|
||||
except (ValueError, TypeError):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid UUID format in request data"
|
||||
)
|
||||
|
||||
try:
|
||||
from app.models.tenants import TenantMember, Tenant
|
||||
|
||||
# Verify tenant exists
|
||||
tenant_query = select(Tenant).where(Tenant.id == tenant_uuid)
|
||||
tenant_result = await db.execute(tenant_query)
|
||||
tenant = tenant_result.scalar_one_or_none()
|
||||
|
||||
if not tenant:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Tenant {tenant_id} not found"
|
||||
)
|
||||
|
||||
# Get current owner membership
|
||||
current_owner_query = select(TenantMember).where(
|
||||
TenantMember.tenant_id == tenant_uuid,
|
||||
TenantMember.user_id == current_owner_id,
|
||||
TenantMember.role == 'owner'
|
||||
)
|
||||
current_owner_result = await db.execute(current_owner_query)
|
||||
current_owner_membership = current_owner_result.scalar_one_or_none()
|
||||
|
||||
if not current_owner_membership:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Current owner membership not found"
|
||||
)
|
||||
|
||||
# Get new owner membership (should be admin)
|
||||
new_owner_query = select(TenantMember).where(
|
||||
TenantMember.tenant_id == tenant_uuid,
|
||||
TenantMember.user_id == new_owner_id
|
||||
)
|
||||
new_owner_result = await db.execute(new_owner_query)
|
||||
new_owner_membership = new_owner_result.scalar_one_or_none()
|
||||
|
||||
if not new_owner_membership:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="New owner must be a member of the tenant"
|
||||
)
|
||||
|
||||
if new_owner_membership.role not in ['admin', 'owner']:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="New owner must have admin or owner role"
|
||||
)
|
||||
|
||||
# Perform the transfer
|
||||
current_owner_membership.role = 'admin' # Demote current owner to admin
|
||||
new_owner_membership.role = 'owner' # Promote new owner
|
||||
|
||||
current_owner_membership.updated_at = datetime.utcnow()
|
||||
new_owner_membership.updated_at = datetime.utcnow()
|
||||
|
||||
await db.commit()
|
||||
|
||||
logger.info("Tenant ownership transferred",
|
||||
tenant_id=tenant_id,
|
||||
from_user=str(current_owner_id),
|
||||
to_user=str(new_owner_id))
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Ownership transferred successfully",
|
||||
"tenant_id": tenant_id,
|
||||
"previous_owner": str(current_owner_id),
|
||||
"new_owner": str(new_owner_id),
|
||||
"transferred_at": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error("Failed to transfer tenant ownership",
|
||||
tenant_id=tenant_id,
|
||||
error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to transfer tenant ownership"
|
||||
)
|
||||
|
||||
@router.delete("/users/{user_id}/memberships")
|
||||
async def delete_user_memberships(
|
||||
user_id: str,
|
||||
current_user = Depends(get_current_user_dep),
|
||||
_admin_check = Depends(require_admin_role),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Delete all tenant memberships for a user (admin only)"""
|
||||
try:
|
||||
user_uuid = uuid.UUID(user_id)
|
||||
except ValueError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid user ID format"
|
||||
)
|
||||
|
||||
try:
|
||||
from app.models.tenants import TenantMember
|
||||
|
||||
# Count memberships before deletion
|
||||
count_query = select(func.count(TenantMember.id)).where(
|
||||
TenantMember.user_id == user_uuid
|
||||
)
|
||||
count_result = await db.execute(count_query)
|
||||
membership_count = count_result.scalar()
|
||||
|
||||
# Delete all memberships
|
||||
delete_query = delete(TenantMember).where(TenantMember.user_id == user_uuid)
|
||||
delete_result = await db.execute(delete_query)
|
||||
|
||||
await db.commit()
|
||||
|
||||
logger.info("Deleted user memberships",
|
||||
user_id=user_id,
|
||||
memberships_deleted=delete_result.rowcount)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"user_id": user_id,
|
||||
"memberships_deleted": delete_result.rowcount,
|
||||
"expected_count": membership_count,
|
||||
"deleted_at": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error("Failed to delete user memberships", user_id=user_id, error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to delete user memberships"
|
||||
)
|
||||
Reference in New Issue
Block a user