Add pyproject.toml for consolidated project configuration and replace Black, isort, and Flake8 with Ruff

- Introduced `pyproject.toml` to centralize backend tool configurations (e.g., Ruff, mypy, coverage, pytest).
- Replaced Black, isort, and Flake8 with Ruff for linting, formatting, and import sorting.
- Updated `requirements.txt` to include Ruff and remove replaced tools.
- Added `Makefile` to streamline development workflows with commands for linting, formatting, type-checking, testing, and cleanup.
This commit is contained in:
2025-11-10 11:55:15 +01:00
parent a5c671c133
commit c589b565f0
86 changed files with 4572 additions and 3956 deletions

View File

@@ -5,9 +5,10 @@ Admin-specific endpoints for managing users and organizations.
These endpoints require superuser privileges and provide CMS-like functionality
for managing the application.
"""
import logging
from enum import Enum
from typing import Any, List, Optional
from typing import Any
from uuid import UUID
from fastapi import APIRouter, Depends, Query, status
@@ -16,27 +17,32 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.api.dependencies.permissions import require_superuser
from app.core.database import get_db
from app.core.exceptions import NotFoundError, DuplicateError, AuthorizationError, ErrorCode
from app.core.exceptions import (
AuthorizationError,
DuplicateError,
ErrorCode,
NotFoundError,
)
from app.crud.organization import organization as organization_crud
from app.crud.user import user as user_crud
from app.crud.session import session as session_crud
from app.crud.user import user as user_crud
from app.models.user import User
from app.models.user_organization import OrganizationRole
from app.schemas.common import (
PaginationParams,
PaginatedResponse,
MessageResponse,
PaginatedResponse,
PaginationParams,
SortParams,
create_pagination_meta
create_pagination_meta,
)
from app.schemas.organizations import (
OrganizationResponse,
OrganizationCreate,
OrganizationMemberResponse,
OrganizationResponse,
OrganizationUpdate,
OrganizationMemberResponse
)
from app.schemas.users import UserResponse, UserCreate, UserUpdate
from app.schemas.sessions import AdminSessionResponse
from app.schemas.users import UserCreate, UserResponse, UserUpdate
logger = logging.getLogger(__name__)
@@ -46,6 +52,7 @@ router = APIRouter()
# Schemas for bulk operations
class BulkAction(str, Enum):
"""Supported bulk actions."""
ACTIVATE = "activate"
DEACTIVATE = "deactivate"
DELETE = "delete"
@@ -53,36 +60,41 @@ class BulkAction(str, Enum):
class BulkUserAction(BaseModel):
"""Schema for bulk user actions."""
action: BulkAction = Field(..., description="Action to perform on selected users")
user_ids: List[UUID] = Field(..., min_items=1, max_items=100, description="List of user IDs (max 100)")
user_ids: list[UUID] = Field(
..., min_items=1, max_items=100, description="List of user IDs (max 100)"
)
class BulkActionResult(BaseModel):
"""Result of a bulk action."""
success: bool
affected_count: int
failed_count: int
message: str
failed_ids: Optional[List[UUID]] = []
failed_ids: list[UUID] | None = []
# ===== User Management Endpoints =====
@router.get(
"/users",
response_model=PaginatedResponse[UserResponse],
summary="Admin: List All Users",
description="Get paginated list of all users with filtering and search (admin only)",
operation_id="admin_list_users"
operation_id="admin_list_users",
)
async def admin_list_users(
pagination: PaginationParams = Depends(),
sort: SortParams = Depends(),
is_active: Optional[bool] = Query(None, description="Filter by active status"),
is_superuser: Optional[bool] = Query(None, description="Filter by superuser status"),
search: Optional[str] = Query(None, description="Search by email, name"),
is_active: bool | None = Query(None, description="Filter by active status"),
is_superuser: bool | None = Query(None, description="Filter by superuser status"),
search: str | None = Query(None, description="Search by email, name"),
admin: User = Depends(require_superuser),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
) -> Any:
"""
List all users with comprehensive filtering and search.
@@ -105,20 +117,20 @@ async def admin_list_users(
sort_by=sort.sort_by or "created_at",
sort_order=sort.sort_order.value if sort.sort_order else "desc",
filters=filters if filters else None,
search=search
search=search,
)
pagination_meta = create_pagination_meta(
total=total,
page=pagination.page,
limit=pagination.limit,
items_count=len(users)
items_count=len(users),
)
return PaginatedResponse(data=users, pagination=pagination_meta)
except Exception as e:
logger.error(f"Error listing users (admin): {str(e)}", exc_info=True)
logger.error(f"Error listing users (admin): {e!s}", exc_info=True)
raise
@@ -128,12 +140,12 @@ async def admin_list_users(
status_code=status.HTTP_201_CREATED,
summary="Admin: Create User",
description="Create a new user (admin only)",
operation_id="admin_create_user"
operation_id="admin_create_user",
)
async def admin_create_user(
user_in: UserCreate,
admin: User = Depends(require_superuser),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
) -> Any:
"""
Create a new user with admin privileges.
@@ -145,13 +157,10 @@ async def admin_create_user(
logger.info(f"Admin {admin.email} created user {user.email}")
return user
except ValueError as e:
logger.warning(f"Failed to create user: {str(e)}")
raise NotFoundError(
message=str(e),
error_code=ErrorCode.USER_ALREADY_EXISTS
)
logger.warning(f"Failed to create user: {e!s}")
raise NotFoundError(message=str(e), error_code=ErrorCode.USER_ALREADY_EXISTS)
except Exception as e:
logger.error(f"Error creating user (admin): {str(e)}", exc_info=True)
logger.error(f"Error creating user (admin): {e!s}", exc_info=True)
raise
@@ -160,19 +169,18 @@ async def admin_create_user(
response_model=UserResponse,
summary="Admin: Get User Details",
description="Get detailed user information (admin only)",
operation_id="admin_get_user"
operation_id="admin_get_user",
)
async def admin_get_user(
user_id: UUID,
admin: User = Depends(require_superuser),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
) -> Any:
"""Get detailed information about a specific user."""
user = await user_crud.get(db, id=user_id)
if not user:
raise NotFoundError(
message=f"User {user_id} not found",
error_code=ErrorCode.USER_NOT_FOUND
message=f"User {user_id} not found", error_code=ErrorCode.USER_NOT_FOUND
)
return user
@@ -182,21 +190,20 @@ async def admin_get_user(
response_model=UserResponse,
summary="Admin: Update User",
description="Update user information (admin only)",
operation_id="admin_update_user"
operation_id="admin_update_user",
)
async def admin_update_user(
user_id: UUID,
user_in: UserUpdate,
admin: User = Depends(require_superuser),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
) -> Any:
"""Update user information with admin privileges."""
try:
user = await user_crud.get(db, id=user_id)
if not user:
raise NotFoundError(
message=f"User {user_id} not found",
error_code=ErrorCode.USER_NOT_FOUND
message=f"User {user_id} not found", error_code=ErrorCode.USER_NOT_FOUND
)
updated_user = await user_crud.update(db, db_obj=user, obj_in=user_in)
@@ -206,7 +213,7 @@ async def admin_update_user(
except NotFoundError:
raise
except Exception as e:
logger.error(f"Error updating user (admin): {str(e)}", exc_info=True)
logger.error(f"Error updating user (admin): {e!s}", exc_info=True)
raise
@@ -215,20 +222,19 @@ async def admin_update_user(
response_model=MessageResponse,
summary="Admin: Delete User",
description="Soft delete a user (admin only)",
operation_id="admin_delete_user"
operation_id="admin_delete_user",
)
async def admin_delete_user(
user_id: UUID,
admin: User = Depends(require_superuser),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
) -> Any:
"""Soft delete a user (sets deleted_at timestamp)."""
try:
user = await user_crud.get(db, id=user_id)
if not user:
raise NotFoundError(
message=f"User {user_id} not found",
error_code=ErrorCode.USER_NOT_FOUND
message=f"User {user_id} not found", error_code=ErrorCode.USER_NOT_FOUND
)
# Prevent deleting yourself
@@ -236,21 +242,20 @@ async def admin_delete_user(
# Use AuthorizationError for permission/operation restrictions
raise AuthorizationError(
message="Cannot delete your own account",
error_code=ErrorCode.OPERATION_FORBIDDEN
error_code=ErrorCode.OPERATION_FORBIDDEN,
)
await user_crud.soft_delete(db, id=user_id)
logger.info(f"Admin {admin.email} deleted user {user.email}")
return MessageResponse(
success=True,
message=f"User {user.email} has been deleted"
success=True, message=f"User {user.email} has been deleted"
)
except NotFoundError:
raise
except Exception as e:
logger.error(f"Error deleting user (admin): {str(e)}", exc_info=True)
logger.error(f"Error deleting user (admin): {e!s}", exc_info=True)
raise
@@ -259,34 +264,32 @@ async def admin_delete_user(
response_model=MessageResponse,
summary="Admin: Activate User",
description="Activate a user account (admin only)",
operation_id="admin_activate_user"
operation_id="admin_activate_user",
)
async def admin_activate_user(
user_id: UUID,
admin: User = Depends(require_superuser),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
) -> Any:
"""Activate a user account."""
try:
user = await user_crud.get(db, id=user_id)
if not user:
raise NotFoundError(
message=f"User {user_id} not found",
error_code=ErrorCode.USER_NOT_FOUND
message=f"User {user_id} not found", error_code=ErrorCode.USER_NOT_FOUND
)
await user_crud.update(db, db_obj=user, obj_in={"is_active": True})
logger.info(f"Admin {admin.email} activated user {user.email}")
return MessageResponse(
success=True,
message=f"User {user.email} has been activated"
success=True, message=f"User {user.email} has been activated"
)
except NotFoundError:
raise
except Exception as e:
logger.error(f"Error activating user (admin): {str(e)}", exc_info=True)
logger.error(f"Error activating user (admin): {e!s}", exc_info=True)
raise
@@ -295,20 +298,19 @@ async def admin_activate_user(
response_model=MessageResponse,
summary="Admin: Deactivate User",
description="Deactivate a user account (admin only)",
operation_id="admin_deactivate_user"
operation_id="admin_deactivate_user",
)
async def admin_deactivate_user(
user_id: UUID,
admin: User = Depends(require_superuser),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
) -> Any:
"""Deactivate a user account."""
try:
user = await user_crud.get(db, id=user_id)
if not user:
raise NotFoundError(
message=f"User {user_id} not found",
error_code=ErrorCode.USER_NOT_FOUND
message=f"User {user_id} not found", error_code=ErrorCode.USER_NOT_FOUND
)
# Prevent deactivating yourself
@@ -316,21 +318,20 @@ async def admin_deactivate_user(
# Use AuthorizationError for permission/operation restrictions
raise AuthorizationError(
message="Cannot deactivate your own account",
error_code=ErrorCode.OPERATION_FORBIDDEN
error_code=ErrorCode.OPERATION_FORBIDDEN,
)
await user_crud.update(db, db_obj=user, obj_in={"is_active": False})
logger.info(f"Admin {admin.email} deactivated user {user.email}")
return MessageResponse(
success=True,
message=f"User {user.email} has been deactivated"
success=True, message=f"User {user.email} has been deactivated"
)
except NotFoundError:
raise
except Exception as e:
logger.error(f"Error deactivating user (admin): {str(e)}", exc_info=True)
logger.error(f"Error deactivating user (admin): {e!s}", exc_info=True)
raise
@@ -339,12 +340,12 @@ async def admin_deactivate_user(
response_model=BulkActionResult,
summary="Admin: Bulk User Action",
description="Perform bulk actions on multiple users (admin only)",
operation_id="admin_bulk_user_action"
operation_id="admin_bulk_user_action",
)
async def admin_bulk_user_action(
bulk_action: BulkUserAction,
admin: User = Depends(require_superuser),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
) -> Any:
"""
Perform bulk actions on multiple users using optimized bulk operations.
@@ -356,22 +357,16 @@ async def admin_bulk_user_action(
# 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
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
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
db, user_ids=bulk_action.user_ids, exclude_user_id=admin.id
)
else:
raise ValueError(f"Unsupported bulk action: {bulk_action.action}")
@@ -390,29 +385,30 @@ async def admin_bulk_user_action(
affected_count=affected_count,
failed_count=failed_count,
message=f"Bulk {bulk_action.action.value}: {affected_count} users affected, {failed_count} skipped",
failed_ids=None # Bulk operations don't track individual failures
failed_ids=None, # Bulk operations don't track individual failures
)
except Exception as e:
logger.error(f"Error in bulk user action: {str(e)}", exc_info=True)
logger.error(f"Error in bulk user action: {e!s}", exc_info=True)
raise
# ===== Organization Management Endpoints =====
@router.get(
"/organizations",
response_model=PaginatedResponse[OrganizationResponse],
summary="Admin: List Organizations",
description="Get paginated list of all organizations (admin only)",
operation_id="admin_list_organizations"
operation_id="admin_list_organizations",
)
async def admin_list_organizations(
pagination: PaginationParams = Depends(),
is_active: Optional[bool] = Query(None, description="Filter by active status"),
search: Optional[str] = Query(None, description="Search by name, slug, description"),
is_active: bool | None = Query(None, description="Filter by active status"),
search: str | None = Query(None, description="Search by name, slug, description"),
admin: User = Depends(require_superuser),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
) -> Any:
"""List all organizations with filtering and search."""
try:
@@ -422,14 +418,14 @@ async def admin_list_organizations(
skip=pagination.offset,
limit=pagination.limit,
is_active=is_active,
search=search
search=search,
)
# Build response objects from optimized query results
orgs_with_count = []
for item in orgs_with_data:
org = item['organization']
member_count = item['member_count']
org = item["organization"]
member_count = item["member_count"]
org_dict = {
"id": org.id,
@@ -440,7 +436,7 @@ async def admin_list_organizations(
"settings": org.settings,
"created_at": org.created_at,
"updated_at": org.updated_at,
"member_count": member_count
"member_count": member_count,
}
orgs_with_count.append(OrganizationResponse(**org_dict))
@@ -448,13 +444,13 @@ async def admin_list_organizations(
total=total,
page=pagination.page,
limit=pagination.limit,
items_count=len(orgs_with_count)
items_count=len(orgs_with_count),
)
return PaginatedResponse(data=orgs_with_count, pagination=pagination_meta)
except Exception as e:
logger.error(f"Error listing organizations (admin): {str(e)}", exc_info=True)
logger.error(f"Error listing organizations (admin): {e!s}", exc_info=True)
raise
@@ -464,12 +460,12 @@ async def admin_list_organizations(
status_code=status.HTTP_201_CREATED,
summary="Admin: Create Organization",
description="Create a new organization (admin only)",
operation_id="admin_create_organization"
operation_id="admin_create_organization",
)
async def admin_create_organization(
org_in: OrganizationCreate,
admin: User = Depends(require_superuser),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
) -> Any:
"""Create a new organization."""
try:
@@ -486,18 +482,15 @@ async def admin_create_organization(
"settings": org.settings,
"created_at": org.created_at,
"updated_at": org.updated_at,
"member_count": 0
"member_count": 0,
}
return OrganizationResponse(**org_dict)
except ValueError as e:
logger.warning(f"Failed to create organization: {str(e)}")
raise NotFoundError(
message=str(e),
error_code=ErrorCode.ALREADY_EXISTS
)
logger.warning(f"Failed to create organization: {e!s}")
raise NotFoundError(message=str(e), error_code=ErrorCode.ALREADY_EXISTS)
except Exception as e:
logger.error(f"Error creating organization (admin): {str(e)}", exc_info=True)
logger.error(f"Error creating organization (admin): {e!s}", exc_info=True)
raise
@@ -506,19 +499,18 @@ async def admin_create_organization(
response_model=OrganizationResponse,
summary="Admin: Get Organization Details",
description="Get detailed organization information (admin only)",
operation_id="admin_get_organization"
operation_id="admin_get_organization",
)
async def admin_get_organization(
org_id: UUID,
admin: User = Depends(require_superuser),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
) -> Any:
"""Get detailed information about a specific organization."""
org = await organization_crud.get(db, id=org_id)
if not org:
raise NotFoundError(
message=f"Organization {org_id} not found",
error_code=ErrorCode.NOT_FOUND
message=f"Organization {org_id} not found", error_code=ErrorCode.NOT_FOUND
)
org_dict = {
@@ -530,7 +522,9 @@ async def admin_get_organization(
"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": await organization_crud.get_member_count(
db, organization_id=org.id
),
}
return OrganizationResponse(**org_dict)
@@ -540,13 +534,13 @@ async def admin_get_organization(
response_model=OrganizationResponse,
summary="Admin: Update Organization",
description="Update organization information (admin only)",
operation_id="admin_update_organization"
operation_id="admin_update_organization",
)
async def admin_update_organization(
org_id: UUID,
org_in: OrganizationUpdate,
admin: User = Depends(require_superuser),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
) -> Any:
"""Update organization information."""
try:
@@ -554,7 +548,7 @@ async def admin_update_organization(
if not org:
raise NotFoundError(
message=f"Organization {org_id} not found",
error_code=ErrorCode.NOT_FOUND
error_code=ErrorCode.NOT_FOUND,
)
updated_org = await organization_crud.update(db, db_obj=org, obj_in=org_in)
@@ -569,14 +563,16 @@ async def admin_update_organization(
"settings": updated_org.settings,
"created_at": updated_org.created_at,
"updated_at": updated_org.updated_at,
"member_count": await organization_crud.get_member_count(db, organization_id=updated_org.id)
"member_count": await organization_crud.get_member_count(
db, organization_id=updated_org.id
),
}
return OrganizationResponse(**org_dict)
except NotFoundError:
raise
except Exception as e:
logger.error(f"Error updating organization (admin): {str(e)}", exc_info=True)
logger.error(f"Error updating organization (admin): {e!s}", exc_info=True)
raise
@@ -585,12 +581,12 @@ async def admin_update_organization(
response_model=MessageResponse,
summary="Admin: Delete Organization",
description="Delete an organization (admin only)",
operation_id="admin_delete_organization"
operation_id="admin_delete_organization",
)
async def admin_delete_organization(
org_id: UUID,
admin: User = Depends(require_superuser),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
) -> Any:
"""Delete an organization and all its relationships."""
try:
@@ -598,21 +594,20 @@ async def admin_delete_organization(
if not org:
raise NotFoundError(
message=f"Organization {org_id} not found",
error_code=ErrorCode.NOT_FOUND
error_code=ErrorCode.NOT_FOUND,
)
await organization_crud.remove(db, id=org_id)
logger.info(f"Admin {admin.email} deleted organization {org.name}")
return MessageResponse(
success=True,
message=f"Organization {org.name} has been deleted"
success=True, message=f"Organization {org.name} has been deleted"
)
except NotFoundError:
raise
except Exception as e:
logger.error(f"Error deleting organization (admin): {str(e)}", exc_info=True)
logger.error(f"Error deleting organization (admin): {e!s}", exc_info=True)
raise
@@ -621,14 +616,14 @@ async def admin_delete_organization(
response_model=PaginatedResponse[OrganizationMemberResponse],
summary="Admin: List Organization Members",
description="Get all members of an organization (admin only)",
operation_id="admin_list_organization_members"
operation_id="admin_list_organization_members",
)
async def admin_list_organization_members(
org_id: UUID,
pagination: PaginationParams = Depends(),
is_active: Optional[bool] = Query(True, description="Filter by active status"),
is_active: bool | None = Query(True, description="Filter by active status"),
admin: User = Depends(require_superuser),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
) -> Any:
"""List all members of an organization."""
try:
@@ -636,7 +631,7 @@ async def admin_list_organization_members(
if not org:
raise NotFoundError(
message=f"Organization {org_id} not found",
error_code=ErrorCode.NOT_FOUND
error_code=ErrorCode.NOT_FOUND,
)
members, total = await organization_crud.get_organization_members(
@@ -644,7 +639,7 @@ async def admin_list_organization_members(
organization_id=org_id,
skip=pagination.offset,
limit=pagination.limit,
is_active=is_active
is_active=is_active,
)
# Convert to response models
@@ -654,7 +649,7 @@ async def admin_list_organization_members(
total=total,
page=pagination.page,
limit=pagination.limit,
items_count=len(member_responses)
items_count=len(member_responses),
)
return PaginatedResponse(data=member_responses, pagination=pagination_meta)
@@ -662,14 +657,19 @@ async def admin_list_organization_members(
except NotFoundError:
raise
except Exception as e:
logger.error(f"Error listing organization members (admin): {str(e)}", exc_info=True)
logger.error(
f"Error listing organization members (admin): {e!s}", exc_info=True
)
raise
class AddMemberRequest(BaseModel):
"""Request to add a member to an organization."""
user_id: UUID = Field(..., description="User ID to add")
role: OrganizationRole = Field(OrganizationRole.MEMBER, description="Role in organization")
role: OrganizationRole = Field(
OrganizationRole.MEMBER, description="Role in organization"
)
@router.post(
@@ -677,13 +677,13 @@ class AddMemberRequest(BaseModel):
response_model=MessageResponse,
summary="Admin: Add Member to Organization",
description="Add a user to an organization (admin only)",
operation_id="admin_add_organization_member"
operation_id="admin_add_organization_member",
)
async def admin_add_organization_member(
org_id: UUID,
request: AddMemberRequest,
admin: User = Depends(require_superuser),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
) -> Any:
"""Add a user to an organization."""
try:
@@ -691,21 +691,18 @@ async def admin_add_organization_member(
if not org:
raise NotFoundError(
message=f"Organization {org_id} not found",
error_code=ErrorCode.NOT_FOUND
error_code=ErrorCode.NOT_FOUND,
)
user = await user_crud.get(db, id=request.user_id)
if not user:
raise NotFoundError(
message=f"User {request.user_id} not found",
error_code=ErrorCode.USER_NOT_FOUND
error_code=ErrorCode.USER_NOT_FOUND,
)
await organization_crud.add_user(
db,
organization_id=org_id,
user_id=request.user_id,
role=request.role
db, organization_id=org_id, user_id=request.user_id, role=request.role
)
logger.info(
@@ -714,22 +711,21 @@ async def admin_add_organization_member(
)
return MessageResponse(
success=True,
message=f"User {user.email} added to organization {org.name}"
success=True, message=f"User {user.email} added to organization {org.name}"
)
except ValueError as e:
logger.warning(f"Failed to add user to organization: {str(e)}")
logger.warning(f"Failed to add user to organization: {e!s}")
# Use DuplicateError for "already exists" scenarios
raise DuplicateError(
message=str(e),
error_code=ErrorCode.USER_ALREADY_EXISTS,
field="user_id"
message=str(e), error_code=ErrorCode.USER_ALREADY_EXISTS, field="user_id"
)
except NotFoundError:
raise
except Exception as e:
logger.error(f"Error adding member to organization (admin): {str(e)}", exc_info=True)
logger.error(
f"Error adding member to organization (admin): {e!s}", exc_info=True
)
raise
@@ -738,13 +734,13 @@ async def admin_add_organization_member(
response_model=MessageResponse,
summary="Admin: Remove Member from Organization",
description="Remove a user from an organization (admin only)",
operation_id="admin_remove_organization_member"
operation_id="admin_remove_organization_member",
)
async def admin_remove_organization_member(
org_id: UUID,
user_id: UUID,
admin: User = Depends(require_superuser),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
) -> Any:
"""Remove a user from an organization."""
try:
@@ -752,39 +748,40 @@ async def admin_remove_organization_member(
if not org:
raise NotFoundError(
message=f"Organization {org_id} not found",
error_code=ErrorCode.NOT_FOUND
error_code=ErrorCode.NOT_FOUND,
)
user = await user_crud.get(db, id=user_id)
if not user:
raise NotFoundError(
message=f"User {user_id} not found",
error_code=ErrorCode.USER_NOT_FOUND
message=f"User {user_id} not found", error_code=ErrorCode.USER_NOT_FOUND
)
success = await organization_crud.remove_user(
db,
organization_id=org_id,
user_id=user_id
db, organization_id=org_id, user_id=user_id
)
if not success:
raise NotFoundError(
message="User is not a member of this organization",
error_code=ErrorCode.NOT_FOUND
error_code=ErrorCode.NOT_FOUND,
)
logger.info(f"Admin {admin.email} removed user {user.email} from organization {org.name}")
logger.info(
f"Admin {admin.email} removed user {user.email} from organization {org.name}"
)
return MessageResponse(
success=True,
message=f"User {user.email} removed from organization {org.name}"
message=f"User {user.email} removed from organization {org.name}",
)
except NotFoundError:
raise
except Exception as e:
logger.error(f"Error removing member from organization (admin): {str(e)}", exc_info=True)
logger.error(
f"Error removing member from organization (admin): {e!s}", exc_info=True
)
raise
@@ -792,6 +789,7 @@ async def admin_remove_organization_member(
# Session Management Endpoints
# ============================================================================
@router.get(
"/sessions",
response_model=PaginatedResponse[AdminSessionResponse],
@@ -802,13 +800,13 @@ async def admin_remove_organization_member(
Returns paginated list of sessions with user information.
Useful for admin dashboard statistics and session monitoring.
""",
operation_id="admin_list_sessions"
operation_id="admin_list_sessions",
)
async def admin_list_sessions(
pagination: PaginationParams = Depends(),
is_active: Optional[bool] = Query(None, description="Filter by active status"),
is_active: bool | None = Query(None, description="Filter by active status"),
admin: User = Depends(require_superuser),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
) -> Any:
"""List all sessions across all users with filtering and pagination."""
try:
@@ -818,7 +816,7 @@ async def admin_list_sessions(
skip=pagination.offset,
limit=pagination.limit,
active_only=is_active if is_active is not None else True,
with_user=True
with_user=True,
)
# Build response objects with user information
@@ -847,21 +845,23 @@ async def admin_list_sessions(
last_used_at=session.last_used_at,
created_at=session.created_at,
expires_at=session.expires_at,
is_active=session.is_active
is_active=session.is_active,
)
session_responses.append(session_response)
logger.info(f"Admin {admin.email} listed {len(session_responses)} sessions (total: {total})")
logger.info(
f"Admin {admin.email} listed {len(session_responses)} sessions (total: {total})"
)
pagination_meta = create_pagination_meta(
total=total,
page=pagination.page,
limit=pagination.limit,
items_count=len(session_responses)
items_count=len(session_responses),
)
return PaginatedResponse(data=session_responses, pagination=pagination_meta)
except Exception as e:
logger.error(f"Error listing sessions (admin): {str(e)}", exc_info=True)
logger.error(f"Error listing sessions (admin): {e!s}", exc_info=True)
raise