Refactor authentication services to async password handling; optimize bulk operations and queries
- Updated `verify_password` and `get_password_hash` to their async counterparts to prevent event loop blocking. - Replaced N+1 query patterns in `admin.py` and `session_async.py` with optimized bulk operations for improved performance. - Enhanced `user_async.py` with bulk update and soft delete methods for efficient user management. - Added eager loading support in CRUD operations to prevent N+1 query issues. - Updated test cases with stronger password examples for better security representation.
This commit is contained in:
@@ -345,54 +345,50 @@ async def admin_bulk_user_action(
|
||||
db: AsyncSession = Depends(get_async_db)
|
||||
) -> Any:
|
||||
"""
|
||||
Perform bulk actions on multiple users.
|
||||
Perform bulk actions on multiple users using optimized bulk operations.
|
||||
|
||||
Uses single UPDATE query instead of N individual queries for efficiency.
|
||||
Supported actions: activate, deactivate, delete
|
||||
"""
|
||||
affected_count = 0
|
||||
failed_count = 0
|
||||
failed_ids = []
|
||||
|
||||
try:
|
||||
for user_id in bulk_action.user_ids:
|
||||
try:
|
||||
user = await user_crud.get(db, id=user_id)
|
||||
if not user:
|
||||
failed_count += 1
|
||||
failed_ids.append(user_id)
|
||||
continue
|
||||
# Use efficient bulk operations instead of loop
|
||||
if bulk_action.action == BulkAction.ACTIVATE:
|
||||
affected_count = await user_crud.bulk_update_status(
|
||||
db,
|
||||
user_ids=bulk_action.user_ids,
|
||||
is_active=True
|
||||
)
|
||||
elif bulk_action.action == BulkAction.DEACTIVATE:
|
||||
affected_count = await user_crud.bulk_update_status(
|
||||
db,
|
||||
user_ids=bulk_action.user_ids,
|
||||
is_active=False
|
||||
)
|
||||
elif bulk_action.action == BulkAction.DELETE:
|
||||
# bulk_soft_delete automatically excludes the admin user
|
||||
affected_count = await user_crud.bulk_soft_delete(
|
||||
db,
|
||||
user_ids=bulk_action.user_ids,
|
||||
exclude_user_id=admin.id
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unsupported bulk action: {bulk_action.action}")
|
||||
|
||||
# Prevent affecting yourself
|
||||
if user.id == admin.id:
|
||||
failed_count += 1
|
||||
failed_ids.append(user_id)
|
||||
continue
|
||||
|
||||
if bulk_action.action == BulkAction.ACTIVATE:
|
||||
await user_crud.update(db, db_obj=user, obj_in={"is_active": True})
|
||||
elif bulk_action.action == BulkAction.DEACTIVATE:
|
||||
await user_crud.update(db, db_obj=user, obj_in={"is_active": False})
|
||||
elif bulk_action.action == BulkAction.DELETE:
|
||||
await user_crud.soft_delete(db, id=user_id)
|
||||
|
||||
affected_count += 1
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing user {user_id} in bulk action: {str(e)}")
|
||||
failed_count += 1
|
||||
failed_ids.append(user_id)
|
||||
# Calculate failed count (requested - affected)
|
||||
requested_count = len(bulk_action.user_ids)
|
||||
failed_count = requested_count - affected_count
|
||||
|
||||
logger.info(
|
||||
f"Admin {admin.email} performed bulk {bulk_action.action.value} "
|
||||
f"on {affected_count} users ({failed_count} failed)"
|
||||
f"on {affected_count} users ({failed_count} skipped/failed)"
|
||||
)
|
||||
|
||||
return BulkActionResult(
|
||||
success=failed_count == 0,
|
||||
affected_count=affected_count,
|
||||
failed_count=failed_count,
|
||||
message=f"Bulk {bulk_action.action.value}: {affected_count} users affected, {failed_count} failed",
|
||||
failed_ids=failed_ids if failed_ids else None
|
||||
message=f"Bulk {bulk_action.action.value}: {affected_count} users affected, {failed_count} skipped",
|
||||
failed_ids=None # Bulk operations don't track individual failures
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -51,23 +51,20 @@ async def get_my_organizations(
|
||||
Get all organizations the current user belongs to.
|
||||
|
||||
Returns organizations with member count for each.
|
||||
Uses optimized single query to avoid N+1 problem.
|
||||
"""
|
||||
try:
|
||||
orgs = await organization_crud.get_user_organizations(
|
||||
# Get all org data in single query with JOIN and subquery
|
||||
orgs_data = await organization_crud.get_user_organizations_with_details(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
is_active=is_active
|
||||
)
|
||||
|
||||
# Add member count and role to each organization
|
||||
# Transform to response objects
|
||||
orgs_with_data = []
|
||||
for org in orgs:
|
||||
role = await organization_crud.get_user_role_in_org(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
organization_id=org.id
|
||||
)
|
||||
|
||||
for item in orgs_data:
|
||||
org = item['organization']
|
||||
org_dict = {
|
||||
"id": org.id,
|
||||
"name": org.name,
|
||||
@@ -77,7 +74,7 @@ async def get_my_organizations(
|
||||
"settings": org.settings,
|
||||
"created_at": org.created_at,
|
||||
"updated_at": org.updated_at,
|
||||
"member_count": await organization_crud.get_member_count(db, organization_id=org.id)
|
||||
"member_count": item['member_count']
|
||||
}
|
||||
orgs_with_data.append(OrganizationResponse(**org_dict))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user