Improve user delete flow

This commit is contained in:
Urtzi Alfaro
2025-08-02 17:09:53 +02:00
parent 277e8bec73
commit 3681429e11
10 changed files with 1334 additions and 210 deletions

View File

@@ -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"
)