refactor(logging): switch to parameterized logging for improved performance and clarity
- Replaced f-strings with parameterized logging calls across routes, services, and repositories to optimize log message evaluation. - Improved exception handling by using `logger.exception` where appropriate for automatic traceback logging.
This commit is contained in:
@@ -243,7 +243,7 @@ async def admin_get_stats(
|
||||
|
||||
# 4. User Status - Active vs Inactive
|
||||
logger.info(
|
||||
f"User status counts - Active: {active_count}, Inactive: {inactive_count}"
|
||||
"User status counts - Active: %s, Inactive: %s", active_count, inactive_count
|
||||
)
|
||||
|
||||
user_status = [
|
||||
@@ -312,7 +312,7 @@ async def admin_list_users(
|
||||
return PaginatedResponse(data=users, pagination=pagination_meta)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error listing users (admin): {e!s}", exc_info=True)
|
||||
logger.exception("Error listing users (admin): %s", e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -336,13 +336,13 @@ async def admin_create_user(
|
||||
"""
|
||||
try:
|
||||
user = await user_service.create_user(db, user_in)
|
||||
logger.info(f"Admin {admin.email} created user {user.email}")
|
||||
logger.info("Admin %s created user %s", admin.email, user.email)
|
||||
return user
|
||||
except DuplicateEntryError as e:
|
||||
logger.warning(f"Failed to create user: {e!s}")
|
||||
logger.warning("Failed to create user: %s", e)
|
||||
raise DuplicateError(message=str(e), error_code=ErrorCode.USER_ALREADY_EXISTS)
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating user (admin): {e!s}", exc_info=True)
|
||||
logger.exception("Error creating user (admin): %s", e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -380,11 +380,11 @@ async def admin_update_user(
|
||||
try:
|
||||
user = await user_service.get_user(db, str(user_id))
|
||||
updated_user = await user_service.update_user(db, user=user, obj_in=user_in)
|
||||
logger.info(f"Admin {admin.email} updated user {updated_user.email}")
|
||||
logger.info("Admin %s updated user %s", admin.email, updated_user.email)
|
||||
return updated_user
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating user (admin): {e!s}", exc_info=True)
|
||||
logger.exception("Error updating user (admin): %s", e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -413,14 +413,14 @@ async def admin_delete_user(
|
||||
)
|
||||
|
||||
await user_service.soft_delete_user(db, str(user_id))
|
||||
logger.info(f"Admin {admin.email} deleted user {user.email}")
|
||||
logger.info("Admin %s deleted user %s", admin.email, user.email)
|
||||
|
||||
return MessageResponse(
|
||||
success=True, message=f"User {user.email} has been deleted"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting user (admin): {e!s}", exc_info=True)
|
||||
logger.exception("Error deleting user (admin): %s", e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -440,14 +440,14 @@ async def admin_activate_user(
|
||||
try:
|
||||
user = await user_service.get_user(db, str(user_id))
|
||||
await user_service.update_user(db, user=user, obj_in={"is_active": True})
|
||||
logger.info(f"Admin {admin.email} activated user {user.email}")
|
||||
logger.info("Admin %s activated user %s", admin.email, user.email)
|
||||
|
||||
return MessageResponse(
|
||||
success=True, message=f"User {user.email} has been activated"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error activating user (admin): {e!s}", exc_info=True)
|
||||
logger.exception("Error activating user (admin): %s", e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -476,14 +476,14 @@ async def admin_deactivate_user(
|
||||
)
|
||||
|
||||
await user_service.update_user(db, user=user, obj_in={"is_active": False})
|
||||
logger.info(f"Admin {admin.email} deactivated user {user.email}")
|
||||
logger.info("Admin %s deactivated user %s", admin.email, user.email)
|
||||
|
||||
return MessageResponse(
|
||||
success=True, message=f"User {user.email} has been deactivated"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error deactivating user (admin): {e!s}", exc_info=True)
|
||||
logger.exception("Error deactivating user (admin): %s", e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -528,8 +528,11 @@ async def admin_bulk_user_action(
|
||||
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} skipped/failed)"
|
||||
"Admin %s performed bulk %s on %s users (%s skipped/failed)",
|
||||
admin.email,
|
||||
bulk_action.action.value,
|
||||
affected_count,
|
||||
failed_count,
|
||||
)
|
||||
|
||||
return BulkActionResult(
|
||||
@@ -541,7 +544,7 @@ async def admin_bulk_user_action(
|
||||
)
|
||||
|
||||
except Exception as e: # pragma: no cover
|
||||
logger.error(f"Error in bulk user action: {e!s}", exc_info=True)
|
||||
logger.exception("Error in bulk user action: %s", e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -602,7 +605,7 @@ async def admin_list_organizations(
|
||||
return PaginatedResponse(data=orgs_with_count, pagination=pagination_meta)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error listing organizations (admin): {e!s}", exc_info=True)
|
||||
logger.exception("Error listing organizations (admin): %s", e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -622,7 +625,7 @@ async def admin_create_organization(
|
||||
"""Create a new organization."""
|
||||
try:
|
||||
org = await organization_service.create_organization(db, obj_in=org_in)
|
||||
logger.info(f"Admin {admin.email} created organization {org.name}")
|
||||
logger.info("Admin %s created organization %s", admin.email, org.name)
|
||||
|
||||
# Add member count
|
||||
org_dict = {
|
||||
@@ -639,10 +642,10 @@ async def admin_create_organization(
|
||||
return OrganizationResponse(**org_dict)
|
||||
|
||||
except DuplicateEntryError as e:
|
||||
logger.warning(f"Failed to create organization: {e!s}")
|
||||
logger.warning("Failed to create organization: %s", e)
|
||||
raise DuplicateError(message=str(e), error_code=ErrorCode.ALREADY_EXISTS)
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating organization (admin): {e!s}", exc_info=True)
|
||||
logger.exception("Error creating organization (admin): %s", e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -695,7 +698,7 @@ async def admin_update_organization(
|
||||
updated_org = await organization_service.update_organization(
|
||||
db, org=org, obj_in=org_in
|
||||
)
|
||||
logger.info(f"Admin {admin.email} updated organization {updated_org.name}")
|
||||
logger.info("Admin %s updated organization %s", admin.email, updated_org.name)
|
||||
|
||||
org_dict = {
|
||||
"id": updated_org.id,
|
||||
@@ -713,7 +716,7 @@ async def admin_update_organization(
|
||||
return OrganizationResponse(**org_dict)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating organization (admin): {e!s}", exc_info=True)
|
||||
logger.exception("Error updating organization (admin): %s", e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -733,14 +736,14 @@ async def admin_delete_organization(
|
||||
try:
|
||||
org = await organization_service.get_organization(db, str(org_id))
|
||||
await organization_service.remove_organization(db, str(org_id))
|
||||
logger.info(f"Admin {admin.email} deleted organization {org.name}")
|
||||
logger.info("Admin %s deleted organization %s", admin.email, org.name)
|
||||
|
||||
return MessageResponse(
|
||||
success=True, message=f"Organization {org.name} has been deleted"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting organization (admin): {e!s}", exc_info=True)
|
||||
logger.exception("Error deleting organization (admin): %s", e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -784,9 +787,7 @@ async def admin_list_organization_members(
|
||||
except NotFoundError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error listing organization members (admin): {e!s}", exc_info=True
|
||||
)
|
||||
logger.exception("Error listing organization members (admin): %s", e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -822,8 +823,11 @@ async def admin_add_organization_member(
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Admin {admin.email} added user {user.email} to organization {org.name} "
|
||||
f"with role {request.role.value}"
|
||||
"Admin %s added user %s to organization %s with role %s",
|
||||
admin.email,
|
||||
user.email,
|
||||
org.name,
|
||||
request.role.value,
|
||||
)
|
||||
|
||||
return MessageResponse(
|
||||
@@ -831,14 +835,12 @@ async def admin_add_organization_member(
|
||||
)
|
||||
|
||||
except DuplicateEntryError as e:
|
||||
logger.warning(f"Failed to add user to organization: {e!s}")
|
||||
logger.warning("Failed to add user to organization: %s", e)
|
||||
raise DuplicateError(
|
||||
message=str(e), error_code=ErrorCode.USER_ALREADY_EXISTS, field="user_id"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error adding member to organization (admin): {e!s}", exc_info=True
|
||||
)
|
||||
logger.exception("Error adding member to organization (admin): %s", e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -871,7 +873,10 @@ async def admin_remove_organization_member(
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Admin {admin.email} removed user {user.email} from organization {org.name}"
|
||||
"Admin %s removed user %s from organization %s",
|
||||
admin.email,
|
||||
user.email,
|
||||
org.name,
|
||||
)
|
||||
|
||||
return MessageResponse(
|
||||
@@ -882,9 +887,7 @@ async def admin_remove_organization_member(
|
||||
except NotFoundError:
|
||||
raise
|
||||
except Exception as e: # pragma: no cover
|
||||
logger.error(
|
||||
f"Error removing member from organization (admin): {e!s}", exc_info=True
|
||||
)
|
||||
logger.exception("Error removing member from organization (admin): %s", e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -953,7 +956,10 @@ async def admin_list_sessions(
|
||||
session_responses.append(session_response)
|
||||
|
||||
logger.info(
|
||||
f"Admin {admin.email} listed {len(session_responses)} sessions (total: {total})"
|
||||
"Admin %s listed %s sessions (total: %s)",
|
||||
admin.email,
|
||||
len(session_responses),
|
||||
total,
|
||||
)
|
||||
|
||||
pagination_meta = create_pagination_meta(
|
||||
@@ -966,5 +972,5 @@ async def admin_list_sessions(
|
||||
return PaginatedResponse(data=session_responses, pagination=pagination_meta)
|
||||
|
||||
except Exception as e: # pragma: no cover
|
||||
logger.error(f"Error listing sessions (admin): {e!s}", exc_info=True)
|
||||
logger.exception("Error listing sessions (admin): %s", e)
|
||||
raise
|
||||
|
||||
@@ -94,14 +94,15 @@ async def _create_login_session(
|
||||
await session_service.create_session(db, obj_in=session_data)
|
||||
|
||||
logger.info(
|
||||
f"{login_type.capitalize()} successful: {user.email} from {device_info.device_name} "
|
||||
f"(IP: {device_info.ip_address})"
|
||||
"%s successful: %s from %s (IP: %s)",
|
||||
login_type.capitalize(),
|
||||
user.email,
|
||||
device_info.device_name,
|
||||
device_info.ip_address,
|
||||
)
|
||||
except Exception as session_err:
|
||||
# Log but don't fail login if session creation fails
|
||||
logger.error(
|
||||
f"Failed to create session for {user.email}: {session_err!s}", exc_info=True
|
||||
)
|
||||
logger.exception("Failed to create session for %s: %s", user.email, session_err)
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -125,19 +126,19 @@ async def register_user(
|
||||
return user
|
||||
except DuplicateError:
|
||||
# SECURITY: Don't reveal if email exists - generic error message
|
||||
logger.warning(f"Registration failed: duplicate email {user_data.email}")
|
||||
logger.warning("Registration failed: duplicate email %s", user_data.email)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Registration failed. Please check your information and try again.",
|
||||
)
|
||||
except AuthError as e:
|
||||
logger.warning(f"Registration failed: {e!s}")
|
||||
logger.warning("Registration failed: %s", e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Registration failed. Please check your information and try again.",
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error during registration: {e!s}", exc_info=True)
|
||||
logger.exception("Unexpected error during registration: %s", e)
|
||||
raise DatabaseError(
|
||||
message="An unexpected error occurred. Please try again later.",
|
||||
error_code=ErrorCode.INTERNAL_ERROR,
|
||||
@@ -165,7 +166,7 @@ async def login(
|
||||
|
||||
# Explicitly check for None result and raise correct exception
|
||||
if user is None:
|
||||
logger.warning(f"Invalid login attempt for: {login_data.email}")
|
||||
logger.warning("Invalid login attempt for: %s", login_data.email)
|
||||
raise AuthError(
|
||||
message="Invalid email or password",
|
||||
error_code=ErrorCode.INVALID_CREDENTIALS,
|
||||
@@ -181,11 +182,11 @@ async def login(
|
||||
|
||||
except AuthenticationError as e:
|
||||
# Handle specific authentication errors like inactive accounts
|
||||
logger.warning(f"Authentication failed: {e!s}")
|
||||
logger.warning("Authentication failed: %s", e)
|
||||
raise AuthError(message=str(e), error_code=ErrorCode.INVALID_CREDENTIALS)
|
||||
except Exception as e:
|
||||
# Handle unexpected errors
|
||||
logger.error(f"Unexpected error during login: {e!s}", exc_info=True)
|
||||
logger.exception("Unexpected error during login: %s", e)
|
||||
raise DatabaseError(
|
||||
message="An unexpected error occurred. Please try again later.",
|
||||
error_code=ErrorCode.INTERNAL_ERROR,
|
||||
@@ -227,10 +228,10 @@ async def login_oauth(
|
||||
# Return full token response with user data
|
||||
return tokens
|
||||
except AuthenticationError as e:
|
||||
logger.warning(f"OAuth authentication failed: {e!s}")
|
||||
logger.warning("OAuth authentication failed: %s", e)
|
||||
raise AuthError(message=str(e), error_code=ErrorCode.INVALID_CREDENTIALS)
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error during OAuth login: {e!s}", exc_info=True)
|
||||
logger.exception("Unexpected error during OAuth login: %s", e)
|
||||
raise DatabaseError(
|
||||
message="An unexpected error occurred. Please try again later.",
|
||||
error_code=ErrorCode.INTERNAL_ERROR,
|
||||
@@ -263,7 +264,8 @@ async def refresh_token(
|
||||
|
||||
if not session:
|
||||
logger.warning(
|
||||
f"Refresh token used for inactive or non-existent session: {refresh_payload.jti}"
|
||||
"Refresh token used for inactive or non-existent session: %s",
|
||||
refresh_payload.jti,
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
@@ -286,9 +288,7 @@ async def refresh_token(
|
||||
new_expires_at=datetime.fromtimestamp(new_refresh_payload.exp, tz=UTC),
|
||||
)
|
||||
except Exception as session_err:
|
||||
logger.error(
|
||||
f"Failed to update session {session.id}: {session_err!s}", exc_info=True
|
||||
)
|
||||
logger.exception("Failed to update session %s: %s", session.id, session_err)
|
||||
# Continue anyway - tokens are already issued
|
||||
|
||||
return tokens
|
||||
@@ -311,7 +311,7 @@ async def refresh_token(
|
||||
# Re-raise HTTP exceptions (like session revoked)
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error during token refresh: {e!s}")
|
||||
logger.error("Unexpected error during token refresh: %s", e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="An unexpected error occurred. Please try again later.",
|
||||
@@ -358,11 +358,12 @@ async def request_password_reset(
|
||||
await email_service.send_password_reset_email(
|
||||
to_email=user.email, reset_token=reset_token, user_name=user.first_name
|
||||
)
|
||||
logger.info(f"Password reset requested for {user.email}")
|
||||
logger.info("Password reset requested for %s", user.email)
|
||||
else:
|
||||
# Log attempt but don't reveal if email exists
|
||||
logger.warning(
|
||||
f"Password reset requested for non-existent or inactive email: {reset_request.email}"
|
||||
"Password reset requested for non-existent or inactive email: %s",
|
||||
reset_request.email,
|
||||
)
|
||||
|
||||
# Always return success to prevent email enumeration
|
||||
@@ -371,7 +372,7 @@ async def request_password_reset(
|
||||
message="If your email is registered, you will receive a password reset link shortly",
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing password reset request: {e!s}", exc_info=True)
|
||||
logger.exception("Error processing password reset request: %s", e)
|
||||
# Still return success to prevent information leakage
|
||||
return MessageResponse(
|
||||
success=True,
|
||||
@@ -432,12 +433,14 @@ async def confirm_password_reset(
|
||||
db, user_id=str(user.id)
|
||||
)
|
||||
logger.info(
|
||||
f"Password reset successful for {user.email}, invalidated {deactivated_count} sessions"
|
||||
"Password reset successful for %s, invalidated %s sessions",
|
||||
user.email,
|
||||
deactivated_count,
|
||||
)
|
||||
except Exception as session_error:
|
||||
# Log but don't fail password reset if session invalidation fails
|
||||
logger.error(
|
||||
f"Failed to invalidate sessions after password reset: {session_error!s}"
|
||||
"Failed to invalidate sessions after password reset: %s", session_error
|
||||
)
|
||||
|
||||
return MessageResponse(
|
||||
@@ -448,7 +451,7 @@ async def confirm_password_reset(
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error confirming password reset: {e!s}", exc_info=True)
|
||||
logger.exception("Error confirming password reset: %s", e)
|
||||
await db.rollback()
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -498,7 +501,7 @@ async def logout(
|
||||
)
|
||||
except (TokenExpiredError, TokenInvalidError) as e:
|
||||
# Even if token is expired/invalid, try to deactivate session
|
||||
logger.warning(f"Logout with invalid/expired token: {e!s}")
|
||||
logger.warning("Logout with invalid/expired token: %s", e)
|
||||
# Don't fail - return success anyway
|
||||
return MessageResponse(success=True, message="Logged out successfully")
|
||||
|
||||
@@ -509,8 +512,10 @@ async def logout(
|
||||
# Verify session belongs to current user (security check)
|
||||
if str(session.user_id) != str(current_user.id):
|
||||
logger.warning(
|
||||
f"User {current_user.id} attempted to logout session {session.id} "
|
||||
f"belonging to user {session.user_id}"
|
||||
"User %s attempted to logout session %s belonging to user %s",
|
||||
current_user.id,
|
||||
session.id,
|
||||
session.user_id,
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
@@ -521,14 +526,17 @@ async def logout(
|
||||
await session_service.deactivate(db, session_id=str(session.id))
|
||||
|
||||
logger.info(
|
||||
f"User {current_user.id} logged out from {session.device_name} "
|
||||
f"(session {session.id})"
|
||||
"User %s logged out from %s (session %s)",
|
||||
current_user.id,
|
||||
session.device_name,
|
||||
session.id,
|
||||
)
|
||||
else:
|
||||
# Session not found - maybe already deleted or never existed
|
||||
# Return success anyway (idempotent)
|
||||
logger.info(
|
||||
f"Logout requested for non-existent session (JTI: {refresh_payload.jti})"
|
||||
"Logout requested for non-existent session (JTI: %s)",
|
||||
refresh_payload.jti,
|
||||
)
|
||||
|
||||
return MessageResponse(success=True, message="Logged out successfully")
|
||||
@@ -536,9 +544,7 @@ async def logout(
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error during logout for user {current_user.id}: {e!s}", exc_info=True
|
||||
)
|
||||
logger.exception("Error during logout for user %s: %s", current_user.id, e)
|
||||
# Don't expose error details
|
||||
return MessageResponse(success=True, message="Logged out successfully")
|
||||
|
||||
@@ -581,7 +587,7 @@ async def logout_all(
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"User {current_user.id} logged out from all devices ({count} sessions)"
|
||||
"User %s logged out from all devices (%s sessions)", current_user.id, count
|
||||
)
|
||||
|
||||
return MessageResponse(
|
||||
@@ -590,9 +596,7 @@ async def logout_all(
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error during logout-all for user {current_user.id}: {e!s}", exc_info=True
|
||||
)
|
||||
logger.exception("Error during logout-all for user %s: %s", current_user.id, e)
|
||||
await db.rollback()
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
|
||||
@@ -84,14 +84,16 @@ async def _create_oauth_login_session(
|
||||
await session_service.create_session(db, obj_in=session_data)
|
||||
|
||||
logger.info(
|
||||
f"OAuth login successful: {user.email} via {provider} "
|
||||
f"from {device_info.device_name} (IP: {device_info.ip_address})"
|
||||
"OAuth login successful: %s via %s from %s (IP: %s)",
|
||||
user.email,
|
||||
provider,
|
||||
device_info.device_name,
|
||||
device_info.ip_address,
|
||||
)
|
||||
except Exception as session_err:
|
||||
# Log but don't fail login if session creation fails
|
||||
logger.error(
|
||||
f"Failed to create session for OAuth login {user.email}: {session_err!s}",
|
||||
exc_info=True,
|
||||
logger.exception(
|
||||
"Failed to create session for OAuth login %s: %s", user.email, session_err
|
||||
)
|
||||
|
||||
|
||||
@@ -176,13 +178,13 @@ async def get_authorization_url(
|
||||
}
|
||||
|
||||
except AuthError as e:
|
||||
logger.warning(f"OAuth authorization failed: {e!s}")
|
||||
logger.warning("OAuth authorization failed: %s", e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=str(e),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"OAuth authorization error: {e!s}", exc_info=True)
|
||||
logger.exception("OAuth authorization error: %s", e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to create authorization URL",
|
||||
@@ -250,13 +252,13 @@ async def handle_callback(
|
||||
return result
|
||||
|
||||
except AuthError as e:
|
||||
logger.warning(f"OAuth callback failed: {e!s}")
|
||||
logger.warning("OAuth callback failed: %s", e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=str(e),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"OAuth callback error: {e!s}", exc_info=True)
|
||||
logger.exception("OAuth callback error: %s", e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="OAuth authentication failed",
|
||||
@@ -337,13 +339,13 @@ async def unlink_account(
|
||||
)
|
||||
|
||||
except AuthError as e:
|
||||
logger.warning(f"OAuth unlink failed for {current_user.email}: {e!s}")
|
||||
logger.warning("OAuth unlink failed for %s: %s", current_user.email, e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=str(e),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"OAuth unlink error: {e!s}", exc_info=True)
|
||||
logger.exception("OAuth unlink error: %s", e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to unlink OAuth account",
|
||||
@@ -419,13 +421,13 @@ async def start_link(
|
||||
}
|
||||
|
||||
except AuthError as e:
|
||||
logger.warning(f"OAuth link authorization failed: {e!s}")
|
||||
logger.warning("OAuth link authorization failed: %s", e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=str(e),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"OAuth link error: {e!s}", exc_info=True)
|
||||
logger.exception("OAuth link error: %s", e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to create authorization URL",
|
||||
|
||||
@@ -452,7 +452,7 @@ async def token(
|
||||
except Exception as e:
|
||||
# Log malformed Basic auth for security monitoring
|
||||
logger.warning(
|
||||
f"Malformed Basic auth header in token request: {type(e).__name__}"
|
||||
"Malformed Basic auth header in token request: %s", type(e).__name__
|
||||
)
|
||||
# Fall back to form body
|
||||
|
||||
@@ -563,7 +563,8 @@ async def revoke(
|
||||
except Exception as e:
|
||||
# Log malformed Basic auth for security monitoring
|
||||
logger.warning(
|
||||
f"Malformed Basic auth header in revoke request: {type(e).__name__}"
|
||||
"Malformed Basic auth header in revoke request: %s",
|
||||
type(e).__name__,
|
||||
)
|
||||
# Fall back to form body
|
||||
|
||||
@@ -585,7 +586,7 @@ async def revoke(
|
||||
)
|
||||
except Exception as e:
|
||||
# Log but don't expose errors per RFC 7009
|
||||
logger.warning(f"Token revocation error: {e}")
|
||||
logger.warning("Token revocation error: %s", e)
|
||||
|
||||
# Always return 200 OK per RFC 7009
|
||||
return {"status": "ok"}
|
||||
@@ -634,7 +635,8 @@ async def introspect(
|
||||
except Exception as e:
|
||||
# Log malformed Basic auth for security monitoring
|
||||
logger.warning(
|
||||
f"Malformed Basic auth header in introspect request: {type(e).__name__}"
|
||||
"Malformed Basic auth header in introspect request: %s",
|
||||
type(e).__name__,
|
||||
)
|
||||
# Fall back to form body
|
||||
|
||||
@@ -654,7 +656,7 @@ async def introspect(
|
||||
headers={"WWW-Authenticate": "Basic"},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Token introspection error: {e}")
|
||||
logger.warning("Token introspection error: %s", e)
|
||||
return OAuthTokenIntrospectionResponse(active=False) # pyright: ignore[reportCallIssue]
|
||||
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ async def get_my_organizations(
|
||||
return orgs_with_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting user organizations: {e!s}", exc_info=True)
|
||||
logger.exception("Error getting user organizations: %s", e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ async def get_organization(
|
||||
return OrganizationResponse(**org_dict)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting organization: {e!s}", exc_info=True)
|
||||
logger.exception("Error getting organization: %s", e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ async def get_organization_members(
|
||||
return PaginatedResponse(data=member_responses, pagination=pagination_meta)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting organization members: {e!s}", exc_info=True)
|
||||
logger.exception("Error getting organization members: %s", e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@ async def update_organization(
|
||||
db, org=org, obj_in=org_in
|
||||
)
|
||||
logger.info(
|
||||
f"User {current_user.email} updated organization {updated_org.name}"
|
||||
"User %s updated organization %s", current_user.email, updated_org.name
|
||||
)
|
||||
|
||||
org_dict = {
|
||||
@@ -207,5 +207,5 @@ async def update_organization(
|
||||
return OrganizationResponse(**org_dict)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating organization: {e!s}", exc_info=True)
|
||||
logger.exception("Error updating organization: %s", e)
|
||||
raise
|
||||
|
||||
@@ -74,9 +74,7 @@ async def list_my_sessions(
|
||||
# For now, we'll mark current based on most recent activity
|
||||
except Exception as e:
|
||||
# Optional token parsing - silently ignore failures
|
||||
logger.debug(
|
||||
f"Failed to decode access token for session marking: {e!s}"
|
||||
)
|
||||
logger.debug("Failed to decode access token for session marking: %s", e)
|
||||
|
||||
# Convert to response format
|
||||
session_responses = []
|
||||
@@ -98,7 +96,7 @@ async def list_my_sessions(
|
||||
session_responses.append(session_response)
|
||||
|
||||
logger.info(
|
||||
f"User {current_user.id} listed {len(session_responses)} active sessions"
|
||||
"User %s listed %s active sessions", current_user.id, len(session_responses)
|
||||
)
|
||||
|
||||
return SessionListResponse(
|
||||
@@ -106,9 +104,7 @@ async def list_my_sessions(
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error listing sessions for user {current_user.id}: {e!s}", exc_info=True
|
||||
)
|
||||
logger.exception("Error listing sessions for user %s: %s", current_user.id, e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve sessions",
|
||||
@@ -161,8 +157,10 @@ async def revoke_session(
|
||||
# Verify session belongs to current user
|
||||
if str(session.user_id) != str(current_user.id):
|
||||
logger.warning(
|
||||
f"User {current_user.id} attempted to revoke session {session_id} "
|
||||
f"belonging to user {session.user_id}"
|
||||
"User %s attempted to revoke session %s belonging to user %s",
|
||||
current_user.id,
|
||||
session_id,
|
||||
session.user_id,
|
||||
)
|
||||
raise AuthorizationError(
|
||||
message="You can only revoke your own sessions",
|
||||
@@ -173,8 +171,10 @@ async def revoke_session(
|
||||
await session_service.deactivate(db, session_id=str(session_id))
|
||||
|
||||
logger.info(
|
||||
f"User {current_user.id} revoked session {session_id} "
|
||||
f"({session.device_name})"
|
||||
"User %s revoked session %s (%s)",
|
||||
current_user.id,
|
||||
session_id,
|
||||
session.device_name,
|
||||
)
|
||||
|
||||
return MessageResponse(
|
||||
@@ -185,7 +185,7 @@ async def revoke_session(
|
||||
except (NotFoundError, AuthorizationError):
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error revoking session {session_id}: {e!s}", exc_info=True)
|
||||
logger.exception("Error revoking session %s: %s", session_id, e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to revoke session",
|
||||
@@ -229,7 +229,7 @@ async def cleanup_expired_sessions(
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"User {current_user.id} cleaned up {deleted_count} expired sessions"
|
||||
"User %s cleaned up %s expired sessions", current_user.id, deleted_count
|
||||
)
|
||||
|
||||
return MessageResponse(
|
||||
@@ -237,9 +237,8 @@ async def cleanup_expired_sessions(
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error cleaning up sessions for user {current_user.id}: {e!s}",
|
||||
exc_info=True,
|
||||
logger.exception(
|
||||
"Error cleaning up sessions for user %s: %s", current_user.id, e
|
||||
)
|
||||
await db.rollback()
|
||||
raise HTTPException(
|
||||
|
||||
@@ -90,7 +90,7 @@ async def list_users(
|
||||
|
||||
return PaginatedResponse(data=users, pagination=pagination_meta)
|
||||
except Exception as e:
|
||||
logger.error(f"Error listing users: {e!s}", exc_info=True)
|
||||
logger.exception("Error listing users: %s", e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -143,15 +143,13 @@ async def update_current_user(
|
||||
updated_user = await user_service.update_user(
|
||||
db, user=current_user, obj_in=user_update
|
||||
)
|
||||
logger.info(f"User {current_user.id} updated their profile")
|
||||
logger.info("User %s updated their profile", current_user.id)
|
||||
return updated_user
|
||||
except ValueError as e:
|
||||
logger.error(f"Error updating user {current_user.id}: {e!s}")
|
||||
logger.error("Error updating user %s: %s", current_user.id, e)
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Unexpected error updating user {current_user.id}: {e!s}", exc_info=True
|
||||
)
|
||||
logger.exception("Unexpected error updating user %s: %s", current_user.id, e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -184,7 +182,9 @@ async def get_user_by_id(
|
||||
# Check permissions
|
||||
if str(user_id) != str(current_user.id) and not current_user.is_superuser:
|
||||
logger.warning(
|
||||
f"User {current_user.id} attempted to access user {user_id} without permission"
|
||||
"User %s attempted to access user %s without permission",
|
||||
current_user.id,
|
||||
user_id,
|
||||
)
|
||||
raise AuthorizationError(
|
||||
message="Not enough permissions to view this user",
|
||||
@@ -229,7 +229,9 @@ async def update_user(
|
||||
|
||||
if not is_own_profile and not current_user.is_superuser:
|
||||
logger.warning(
|
||||
f"User {current_user.id} attempted to update user {user_id} without permission"
|
||||
"User %s attempted to update user %s without permission",
|
||||
current_user.id,
|
||||
user_id,
|
||||
)
|
||||
raise AuthorizationError(
|
||||
message="Not enough permissions to update this user",
|
||||
@@ -241,13 +243,13 @@ async def update_user(
|
||||
|
||||
try:
|
||||
updated_user = await user_service.update_user(db, user=user, obj_in=user_update)
|
||||
logger.info(f"User {user_id} updated by {current_user.id}")
|
||||
logger.info("User %s updated by %s", user_id, current_user.id)
|
||||
return updated_user
|
||||
except ValueError as e:
|
||||
logger.error(f"Error updating user {user_id}: {e!s}")
|
||||
logger.error("Error updating user %s: %s", user_id, e)
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error updating user {user_id}: {e!s}", exc_info=True)
|
||||
logger.exception("Unexpected error updating user %s: %s", user_id, e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -287,19 +289,19 @@ async def change_current_user_password(
|
||||
)
|
||||
|
||||
if success:
|
||||
logger.info(f"User {current_user.id} changed their password")
|
||||
logger.info("User %s changed their password", current_user.id)
|
||||
return MessageResponse(
|
||||
success=True, message="Password changed successfully"
|
||||
)
|
||||
except AuthenticationError as e:
|
||||
logger.warning(
|
||||
f"Failed password change attempt for user {current_user.id}: {e!s}"
|
||||
"Failed password change attempt for user %s: %s", current_user.id, e
|
||||
)
|
||||
raise AuthorizationError(
|
||||
message=str(e), error_code=ErrorCode.INVALID_CREDENTIALS
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error changing password for user {current_user.id}: {e!s}")
|
||||
logger.error("Error changing password for user %s: %s", current_user.id, e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -343,13 +345,13 @@ async def delete_user(
|
||||
try:
|
||||
# Use soft delete instead of hard delete
|
||||
await user_service.soft_delete_user(db, str(user_id))
|
||||
logger.info(f"User {user_id} soft-deleted by {current_user.id}")
|
||||
logger.info("User %s soft-deleted by %s", user_id, current_user.id)
|
||||
return MessageResponse(
|
||||
success=True, message=f"User {user_id} deleted successfully"
|
||||
)
|
||||
except ValueError as e:
|
||||
logger.error(f"Error deleting user {user_id}: {e!s}")
|
||||
logger.error("Error deleting user %s: %s", user_id, e)
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error deleting user {user_id}: {e!s}", exc_info=True)
|
||||
logger.exception("Unexpected error deleting user %s: %s", user_id, e)
|
||||
raise
|
||||
|
||||
@@ -139,7 +139,7 @@ async def async_transaction_scope() -> AsyncGenerator[AsyncSession, None]:
|
||||
logger.debug("Async transaction committed successfully")
|
||||
except Exception as e:
|
||||
await session.rollback()
|
||||
logger.error(f"Async transaction failed, rolling back: {e!s}")
|
||||
logger.error("Async transaction failed, rolling back: %s", e)
|
||||
raise
|
||||
finally:
|
||||
await session.close()
|
||||
@@ -155,7 +155,7 @@ async def check_async_database_health() -> bool:
|
||||
await db.execute(text("SELECT 1"))
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Async database health check failed: {e!s}")
|
||||
logger.error("Async database health check failed: %s", e)
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@@ -143,8 +143,11 @@ async def api_exception_handler(request: Request, exc: APIException) -> JSONResp
|
||||
Returns a standardized error response with error code and message.
|
||||
"""
|
||||
logger.warning(
|
||||
f"API exception: {exc.error_code} - {exc.message} "
|
||||
f"(status: {exc.status_code}, path: {request.url.path})"
|
||||
"API exception: %s - %s (status: %s, path: %s)",
|
||||
exc.error_code,
|
||||
exc.message,
|
||||
exc.status_code,
|
||||
request.url.path,
|
||||
)
|
||||
|
||||
error_response = ErrorResponse(
|
||||
@@ -186,7 +189,9 @@ async def validation_exception_handler(
|
||||
)
|
||||
)
|
||||
|
||||
logger.warning(f"Validation error: {len(errors)} errors (path: {request.url.path})")
|
||||
logger.warning(
|
||||
"Validation error: %s errors (path: %s)", len(errors), request.url.path
|
||||
)
|
||||
|
||||
error_response = ErrorResponse(errors=errors)
|
||||
|
||||
@@ -218,7 +223,10 @@ async def http_exception_handler(request: Request, exc: HTTPException) -> JSONRe
|
||||
)
|
||||
|
||||
logger.warning(
|
||||
f"HTTP exception: {exc.status_code} - {exc.detail} (path: {request.url.path})"
|
||||
"HTTP exception: %s - %s (path: %s)",
|
||||
exc.status_code,
|
||||
exc.detail,
|
||||
request.url.path,
|
||||
)
|
||||
|
||||
error_response = ErrorResponse(
|
||||
@@ -239,10 +247,11 @@ async def unhandled_exception_handler(request: Request, exc: Exception) -> JSONR
|
||||
Logs the full exception and returns a generic error response to avoid
|
||||
leaking sensitive information in production.
|
||||
"""
|
||||
logger.error(
|
||||
f"Unhandled exception: {type(exc).__name__} - {exc!s} "
|
||||
f"(path: {request.url.path})",
|
||||
exc_info=True,
|
||||
logger.exception(
|
||||
"Unhandled exception: %s - %s (path: %s)",
|
||||
type(exc).__name__,
|
||||
exc,
|
||||
request.url.path,
|
||||
)
|
||||
|
||||
# In production, don't expose internal error details
|
||||
|
||||
@@ -44,7 +44,8 @@ async def init_db() -> User | None:
|
||||
if not settings.FIRST_SUPERUSER_EMAIL or not settings.FIRST_SUPERUSER_PASSWORD:
|
||||
logger.warning(
|
||||
"First superuser credentials not configured in settings. "
|
||||
f"Using defaults: {superuser_email}"
|
||||
"Using defaults: %s",
|
||||
superuser_email,
|
||||
)
|
||||
|
||||
async with SessionLocal() as session:
|
||||
@@ -53,7 +54,7 @@ async def init_db() -> User | None:
|
||||
existing_user = await user_crud.get_by_email(session, email=superuser_email)
|
||||
|
||||
if existing_user:
|
||||
logger.info(f"Superuser already exists: {existing_user.email}")
|
||||
logger.info("Superuser already exists: %s", existing_user.email)
|
||||
return existing_user
|
||||
|
||||
# Create superuser if doesn't exist
|
||||
@@ -69,7 +70,7 @@ async def init_db() -> User | None:
|
||||
await session.commit()
|
||||
await session.refresh(user)
|
||||
|
||||
logger.info(f"Created first superuser: {user.email}")
|
||||
logger.info("Created first superuser: %s", user.email)
|
||||
|
||||
# Create demo data if in demo mode
|
||||
if settings.DEMO_MODE:
|
||||
@@ -79,7 +80,7 @@ async def init_db() -> User | None:
|
||||
|
||||
except Exception as e:
|
||||
await session.rollback()
|
||||
logger.error(f"Error initializing database: {e}")
|
||||
logger.error("Error initializing database: %s", e)
|
||||
raise
|
||||
|
||||
|
||||
@@ -92,7 +93,7 @@ async def load_demo_data(session):
|
||||
"""Load demo data from JSON file."""
|
||||
demo_data_path = Path(__file__).parent / "core" / "demo_data.json"
|
||||
if not demo_data_path.exists():
|
||||
logger.warning(f"Demo data file not found: {demo_data_path}")
|
||||
logger.warning("Demo data file not found: %s", demo_data_path)
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -119,7 +120,7 @@ async def load_demo_data(session):
|
||||
session.add(org)
|
||||
await session.flush() # Flush to get ID
|
||||
org_map[org.slug] = org
|
||||
logger.info(f"Created demo organization: {org.name}")
|
||||
logger.info("Created demo organization: %s", org.name)
|
||||
else:
|
||||
# We can't easily get the ORM object from raw SQL result for map without querying again or mapping
|
||||
# So let's just query it properly if we need it for relationships
|
||||
@@ -174,7 +175,10 @@ async def load_demo_data(session):
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Created demo user: {user.email} (created {days_ago} days ago, active={user_data.get('is_active', True)})"
|
||||
"Created demo user: %s (created %s days ago, active=%s)",
|
||||
user.email,
|
||||
days_ago,
|
||||
user_data.get("is_active", True),
|
||||
)
|
||||
|
||||
# Add to organization if specified
|
||||
@@ -187,15 +191,15 @@ async def load_demo_data(session):
|
||||
user_id=user.id, organization_id=org.id, role=role
|
||||
)
|
||||
session.add(member)
|
||||
logger.info(f"Added {user.email} to {org.name} as {role}")
|
||||
logger.info("Added %s to %s as %s", user.email, org.name, role)
|
||||
else:
|
||||
logger.info(f"Demo user already exists: {existing_user.email}")
|
||||
logger.info("Demo user already exists: %s", existing_user.email)
|
||||
|
||||
await session.commit()
|
||||
logger.info("Demo data loaded successfully")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading demo data: {e}")
|
||||
logger.error("Error loading demo data: %s", e)
|
||||
raise
|
||||
|
||||
|
||||
|
||||
@@ -320,7 +320,7 @@ async def health_check() -> JSONResponse:
|
||||
"message": f"Database connection failed: {e!s}",
|
||||
}
|
||||
response_status = status.HTTP_503_SERVICE_UNAVAILABLE
|
||||
logger.error(f"Health check failed - database error: {e}")
|
||||
logger.error("Health check failed - database error: %s", e)
|
||||
|
||||
return JSONResponse(status_code=response_status, content=health_status)
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ class BaseRepository[
|
||||
else:
|
||||
uuid_obj = uuid.UUID(str(id))
|
||||
except (ValueError, AttributeError, TypeError) as e:
|
||||
logger.warning(f"Invalid UUID format: {id} - {e!s}")
|
||||
logger.warning("Invalid UUID format: %s - %s", id, e)
|
||||
return None
|
||||
|
||||
try:
|
||||
@@ -81,7 +81,9 @@ class BaseRepository[
|
||||
result = await db.execute(query)
|
||||
return result.scalar_one_or_none()
|
||||
except Exception as e:
|
||||
logger.error(f"Error retrieving {self.model.__name__} with id {id}: {e!s}")
|
||||
logger.error(
|
||||
"Error retrieving %s with id %s: %s", self.model.__name__, id, e
|
||||
)
|
||||
raise
|
||||
|
||||
async def get_multi(
|
||||
@@ -113,7 +115,7 @@ class BaseRepository[
|
||||
return list(result.scalars().all())
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error retrieving multiple {self.model.__name__} records: {e!s}"
|
||||
"Error retrieving multiple %s records: %s", self.model.__name__, e
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -138,22 +140,24 @@ class BaseRepository[
|
||||
error_msg = str(e.orig) if hasattr(e, "orig") else str(e)
|
||||
if "unique" in error_msg.lower() or "duplicate" in error_msg.lower():
|
||||
logger.warning(
|
||||
f"Duplicate entry attempted for {self.model.__name__}: {error_msg}"
|
||||
"Duplicate entry attempted for %s: %s",
|
||||
self.model.__name__,
|
||||
error_msg,
|
||||
)
|
||||
raise DuplicateEntryError(
|
||||
f"A {self.model.__name__} with this data already exists"
|
||||
)
|
||||
logger.error(f"Integrity error creating {self.model.__name__}: {error_msg}")
|
||||
logger.error(
|
||||
"Integrity error creating %s: %s", self.model.__name__, error_msg
|
||||
)
|
||||
raise IntegrityConstraintError(f"Database integrity error: {error_msg}")
|
||||
except (OperationalError, DataError) as e: # pragma: no cover
|
||||
await db.rollback()
|
||||
logger.error(f"Database error creating {self.model.__name__}: {e!s}")
|
||||
logger.error("Database error creating %s: %s", self.model.__name__, e)
|
||||
raise IntegrityConstraintError(f"Database operation failed: {e!s}")
|
||||
except Exception as e: # pragma: no cover
|
||||
await db.rollback()
|
||||
logger.error(
|
||||
f"Unexpected error creating {self.model.__name__}: {e!s}", exc_info=True
|
||||
)
|
||||
logger.exception("Unexpected error creating %s: %s", self.model.__name__, e)
|
||||
raise
|
||||
|
||||
async def update(
|
||||
@@ -184,22 +188,24 @@ class BaseRepository[
|
||||
error_msg = str(e.orig) if hasattr(e, "orig") else str(e)
|
||||
if "unique" in error_msg.lower() or "duplicate" in error_msg.lower():
|
||||
logger.warning(
|
||||
f"Duplicate entry attempted for {self.model.__name__}: {error_msg}"
|
||||
"Duplicate entry attempted for %s: %s",
|
||||
self.model.__name__,
|
||||
error_msg,
|
||||
)
|
||||
raise DuplicateEntryError(
|
||||
f"A {self.model.__name__} with this data already exists"
|
||||
)
|
||||
logger.error(f"Integrity error updating {self.model.__name__}: {error_msg}")
|
||||
logger.error(
|
||||
"Integrity error updating %s: %s", self.model.__name__, error_msg
|
||||
)
|
||||
raise IntegrityConstraintError(f"Database integrity error: {error_msg}")
|
||||
except (OperationalError, DataError) as e:
|
||||
await db.rollback()
|
||||
logger.error(f"Database error updating {self.model.__name__}: {e!s}")
|
||||
logger.error("Database error updating %s: %s", self.model.__name__, e)
|
||||
raise IntegrityConstraintError(f"Database operation failed: {e!s}")
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(
|
||||
f"Unexpected error updating {self.model.__name__}: {e!s}", exc_info=True
|
||||
)
|
||||
logger.exception("Unexpected error updating %s: %s", self.model.__name__, e)
|
||||
raise
|
||||
|
||||
async def remove(self, db: AsyncSession, *, id: str) -> ModelType | None:
|
||||
@@ -210,7 +216,7 @@ class BaseRepository[
|
||||
else:
|
||||
uuid_obj = uuid.UUID(str(id))
|
||||
except (ValueError, AttributeError, TypeError) as e:
|
||||
logger.warning(f"Invalid UUID format for deletion: {id} - {e!s}")
|
||||
logger.warning("Invalid UUID format for deletion: %s - %s", id, e)
|
||||
return None
|
||||
|
||||
try:
|
||||
@@ -221,7 +227,7 @@ class BaseRepository[
|
||||
|
||||
if obj is None:
|
||||
logger.warning(
|
||||
f"{self.model.__name__} with id {id} not found for deletion"
|
||||
"%s with id %s not found for deletion", self.model.__name__, id
|
||||
)
|
||||
return None
|
||||
|
||||
@@ -231,15 +237,16 @@ class BaseRepository[
|
||||
except IntegrityError as e:
|
||||
await db.rollback()
|
||||
error_msg = str(e.orig) if hasattr(e, "orig") else str(e)
|
||||
logger.error(f"Integrity error deleting {self.model.__name__}: {error_msg}")
|
||||
logger.error(
|
||||
"Integrity error deleting %s: %s", self.model.__name__, error_msg
|
||||
)
|
||||
raise IntegrityConstraintError(
|
||||
f"Cannot delete {self.model.__name__}: referenced by other records"
|
||||
)
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(
|
||||
f"Error deleting {self.model.__name__} with id {id}: {e!s}",
|
||||
exc_info=True,
|
||||
logger.exception(
|
||||
"Error deleting %s with id %s: %s", self.model.__name__, id, e
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -298,7 +305,7 @@ class BaseRepository[
|
||||
return items, total
|
||||
except Exception as e: # pragma: no cover
|
||||
logger.error(
|
||||
f"Error retrieving paginated {self.model.__name__} records: {e!s}"
|
||||
"Error retrieving paginated %s records: %s", self.model.__name__, e
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -308,7 +315,7 @@ class BaseRepository[
|
||||
result = await db.execute(select(func.count(self.model.id)))
|
||||
return result.scalar_one()
|
||||
except Exception as e:
|
||||
logger.error(f"Error counting {self.model.__name__} records: {e!s}")
|
||||
logger.error("Error counting %s records: %s", self.model.__name__, e)
|
||||
raise
|
||||
|
||||
async def exists(self, db: AsyncSession, id: str) -> bool:
|
||||
@@ -330,7 +337,7 @@ class BaseRepository[
|
||||
else:
|
||||
uuid_obj = uuid.UUID(str(id))
|
||||
except (ValueError, AttributeError, TypeError) as e:
|
||||
logger.warning(f"Invalid UUID format for soft deletion: {id} - {e!s}")
|
||||
logger.warning("Invalid UUID format for soft deletion: %s - %s", id, e)
|
||||
return None
|
||||
|
||||
try:
|
||||
@@ -341,12 +348,12 @@ class BaseRepository[
|
||||
|
||||
if obj is None:
|
||||
logger.warning(
|
||||
f"{self.model.__name__} with id {id} not found for soft deletion"
|
||||
"%s with id %s not found for soft deletion", self.model.__name__, id
|
||||
)
|
||||
return None
|
||||
|
||||
if not hasattr(self.model, "deleted_at"):
|
||||
logger.error(f"{self.model.__name__} does not support soft deletes")
|
||||
logger.error("%s does not support soft deletes", self.model.__name__)
|
||||
raise InvalidInputError(
|
||||
f"{self.model.__name__} does not have a deleted_at column"
|
||||
)
|
||||
@@ -358,9 +365,8 @@ class BaseRepository[
|
||||
return obj
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(
|
||||
f"Error soft deleting {self.model.__name__} with id {id}: {e!s}",
|
||||
exc_info=True,
|
||||
logger.exception(
|
||||
"Error soft deleting %s with id %s: %s", self.model.__name__, id, e
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -376,7 +382,7 @@ class BaseRepository[
|
||||
else:
|
||||
uuid_obj = uuid.UUID(str(id))
|
||||
except (ValueError, AttributeError, TypeError) as e:
|
||||
logger.warning(f"Invalid UUID format for restoration: {id} - {e!s}")
|
||||
logger.warning("Invalid UUID format for restoration: %s - %s", id, e)
|
||||
return None
|
||||
|
||||
try:
|
||||
@@ -388,14 +394,16 @@ class BaseRepository[
|
||||
)
|
||||
obj = result.scalar_one_or_none()
|
||||
else:
|
||||
logger.error(f"{self.model.__name__} does not support soft deletes")
|
||||
logger.error("%s does not support soft deletes", self.model.__name__)
|
||||
raise InvalidInputError(
|
||||
f"{self.model.__name__} does not have a deleted_at column"
|
||||
)
|
||||
|
||||
if obj is None:
|
||||
logger.warning(
|
||||
f"Soft-deleted {self.model.__name__} with id {id} not found for restoration"
|
||||
"Soft-deleted %s with id %s not found for restoration",
|
||||
self.model.__name__,
|
||||
id,
|
||||
)
|
||||
return None
|
||||
|
||||
@@ -406,8 +414,7 @@ class BaseRepository[
|
||||
return obj
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(
|
||||
f"Error restoring {self.model.__name__} with id {id}: {e!s}",
|
||||
exc_info=True,
|
||||
logger.exception(
|
||||
"Error restoring %s with id %s: %s", self.model.__name__, id, e
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -50,7 +50,10 @@ class OAuthAccountRepository(
|
||||
return result.scalar_one_or_none()
|
||||
except Exception as e: # pragma: no cover
|
||||
logger.error(
|
||||
f"Error getting OAuth account for {provider}:{provider_user_id}: {e!s}"
|
||||
"Error getting OAuth account for %s:%s: %s",
|
||||
provider,
|
||||
provider_user_id,
|
||||
e,
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -76,7 +79,7 @@ class OAuthAccountRepository(
|
||||
return result.scalar_one_or_none()
|
||||
except Exception as e: # pragma: no cover
|
||||
logger.error(
|
||||
f"Error getting OAuth account for {provider} email {email}: {e!s}"
|
||||
"Error getting OAuth account for %s email %s: %s", provider, email, e
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -97,7 +100,7 @@ class OAuthAccountRepository(
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
except Exception as e: # pragma: no cover
|
||||
logger.error(f"Error getting OAuth accounts for user {user_id}: {e!s}")
|
||||
logger.error("Error getting OAuth accounts for user %s: %s", user_id, e)
|
||||
raise
|
||||
|
||||
async def get_user_account_by_provider(
|
||||
@@ -122,7 +125,10 @@ class OAuthAccountRepository(
|
||||
return result.scalar_one_or_none()
|
||||
except Exception as e: # pragma: no cover
|
||||
logger.error(
|
||||
f"Error getting OAuth account for user {user_id}, provider {provider}: {e!s}"
|
||||
"Error getting OAuth account for user %s, provider %s: %s",
|
||||
user_id,
|
||||
provider,
|
||||
e,
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -145,7 +151,9 @@ class OAuthAccountRepository(
|
||||
await db.refresh(db_obj)
|
||||
|
||||
logger.info(
|
||||
f"OAuth account created: {obj_in.provider} linked to user {obj_in.user_id}"
|
||||
"OAuth account created: %s linked to user %s",
|
||||
obj_in.provider,
|
||||
obj_in.user_id,
|
||||
)
|
||||
return db_obj
|
||||
except IntegrityError as e: # pragma: no cover
|
||||
@@ -153,16 +161,18 @@ class OAuthAccountRepository(
|
||||
error_msg = str(e.orig) if hasattr(e, "orig") else str(e)
|
||||
if "uq_oauth_provider_user" in error_msg.lower():
|
||||
logger.warning(
|
||||
f"OAuth account already exists: {obj_in.provider}:{obj_in.provider_user_id}"
|
||||
"OAuth account already exists: %s:%s",
|
||||
obj_in.provider,
|
||||
obj_in.provider_user_id,
|
||||
)
|
||||
raise DuplicateEntryError(
|
||||
f"This {obj_in.provider} account is already linked to another user"
|
||||
)
|
||||
logger.error(f"Integrity error creating OAuth account: {error_msg}")
|
||||
logger.error("Integrity error creating OAuth account: %s", error_msg)
|
||||
raise DuplicateEntryError(f"Failed to create OAuth account: {error_msg}")
|
||||
except Exception as e: # pragma: no cover
|
||||
await db.rollback()
|
||||
logger.error(f"Error creating OAuth account: {e!s}", exc_info=True)
|
||||
logger.exception("Error creating OAuth account: %s", e)
|
||||
raise
|
||||
|
||||
async def delete_account(
|
||||
@@ -189,18 +199,20 @@ class OAuthAccountRepository(
|
||||
deleted = result.rowcount > 0
|
||||
if deleted:
|
||||
logger.info(
|
||||
f"OAuth account deleted: {provider} unlinked from user {user_id}"
|
||||
"OAuth account deleted: %s unlinked from user %s", provider, user_id
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
f"OAuth account not found for deletion: {provider} for user {user_id}"
|
||||
"OAuth account not found for deletion: %s for user %s",
|
||||
provider,
|
||||
user_id,
|
||||
)
|
||||
|
||||
return deleted
|
||||
except Exception as e: # pragma: no cover
|
||||
await db.rollback()
|
||||
logger.error(
|
||||
f"Error deleting OAuth account {provider} for user {user_id}: {e!s}"
|
||||
"Error deleting OAuth account %s for user %s: %s", provider, user_id, e
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -229,7 +241,7 @@ class OAuthAccountRepository(
|
||||
return account
|
||||
except Exception as e: # pragma: no cover
|
||||
await db.rollback()
|
||||
logger.error(f"Error updating OAuth tokens: {e!s}")
|
||||
logger.error("Error updating OAuth tokens: %s", e)
|
||||
raise
|
||||
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class OAuthClientRepository(
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
except Exception as e: # pragma: no cover
|
||||
logger.error(f"Error getting OAuth client {client_id}: {e!s}")
|
||||
logger.error("Error getting OAuth client %s: %s", client_id, e)
|
||||
raise
|
||||
|
||||
async def create_client(
|
||||
@@ -80,17 +80,17 @@ class OAuthClientRepository(
|
||||
await db.refresh(db_obj)
|
||||
|
||||
logger.info(
|
||||
f"OAuth client created: {obj_in.client_name} ({client_id[:8]}...)"
|
||||
"OAuth client created: %s (%s...)", obj_in.client_name, client_id[:8]
|
||||
)
|
||||
return db_obj, client_secret
|
||||
except IntegrityError as e: # pragma: no cover
|
||||
await db.rollback()
|
||||
error_msg = str(e.orig) if hasattr(e, "orig") else str(e)
|
||||
logger.error(f"Error creating OAuth client: {error_msg}")
|
||||
logger.error("Error creating OAuth client: %s", error_msg)
|
||||
raise DuplicateEntryError(f"Failed to create OAuth client: {error_msg}")
|
||||
except Exception as e: # pragma: no cover
|
||||
await db.rollback()
|
||||
logger.error(f"Error creating OAuth client: {e!s}", exc_info=True)
|
||||
logger.exception("Error creating OAuth client: %s", e)
|
||||
raise
|
||||
|
||||
async def deactivate_client(
|
||||
@@ -107,11 +107,11 @@ class OAuthClientRepository(
|
||||
await db.commit()
|
||||
await db.refresh(client)
|
||||
|
||||
logger.info(f"OAuth client deactivated: {client.client_name}")
|
||||
logger.info("OAuth client deactivated: %s", client.client_name)
|
||||
return client
|
||||
except Exception as e: # pragma: no cover
|
||||
await db.rollback()
|
||||
logger.error(f"Error deactivating OAuth client {client_id}: {e!s}")
|
||||
logger.error("Error deactivating OAuth client %s: %s", client_id, e)
|
||||
raise
|
||||
|
||||
async def validate_redirect_uri(
|
||||
@@ -125,7 +125,7 @@ class OAuthClientRepository(
|
||||
|
||||
return redirect_uri in (client.redirect_uris or [])
|
||||
except Exception as e: # pragma: no cover
|
||||
logger.error(f"Error validating redirect URI: {e!s}")
|
||||
logger.error("Error validating redirect URI: %s", e)
|
||||
return False
|
||||
|
||||
async def verify_client_secret(
|
||||
@@ -158,7 +158,7 @@ class OAuthClientRepository(
|
||||
secret_hash = hashlib.sha256(client_secret.encode()).hexdigest()
|
||||
return secrets.compare_digest(stored_hash, secret_hash)
|
||||
except Exception as e: # pragma: no cover
|
||||
logger.error(f"Error verifying client secret: {e!s}")
|
||||
logger.error("Error verifying client secret: %s", e)
|
||||
return False
|
||||
|
||||
async def get_all_clients(
|
||||
@@ -173,7 +173,7 @@ class OAuthClientRepository(
|
||||
result = await db.execute(query)
|
||||
return list(result.scalars().all())
|
||||
except Exception as e: # pragma: no cover
|
||||
logger.error(f"Error getting all OAuth clients: {e!s}")
|
||||
logger.error("Error getting all OAuth clients: %s", e)
|
||||
raise
|
||||
|
||||
async def delete_client(self, db: AsyncSession, *, client_id: str) -> bool:
|
||||
@@ -186,14 +186,14 @@ class OAuthClientRepository(
|
||||
|
||||
deleted = result.rowcount > 0
|
||||
if deleted:
|
||||
logger.info(f"OAuth client deleted: {client_id}")
|
||||
logger.info("OAuth client deleted: %s", client_id)
|
||||
else:
|
||||
logger.warning(f"OAuth client not found for deletion: {client_id}")
|
||||
logger.warning("OAuth client not found for deletion: %s", client_id)
|
||||
|
||||
return deleted
|
||||
except Exception as e: # pragma: no cover
|
||||
await db.rollback()
|
||||
logger.error(f"Error deleting OAuth client {client_id}: {e!s}")
|
||||
logger.error("Error deleting OAuth client %s: %s", client_id, e)
|
||||
raise
|
||||
|
||||
|
||||
|
||||
@@ -42,16 +42,16 @@ class OAuthStateRepository(BaseRepository[OAuthState, OAuthStateCreate, EmptySch
|
||||
await db.commit()
|
||||
await db.refresh(db_obj)
|
||||
|
||||
logger.debug(f"OAuth state created for {obj_in.provider}")
|
||||
logger.debug("OAuth state created for %s", obj_in.provider)
|
||||
return db_obj
|
||||
except IntegrityError as e: # pragma: no cover
|
||||
await db.rollback()
|
||||
error_msg = str(e.orig) if hasattr(e, "orig") else str(e)
|
||||
logger.error(f"OAuth state collision: {error_msg}")
|
||||
logger.error("OAuth state collision: %s", error_msg)
|
||||
raise DuplicateEntryError("Failed to create OAuth state, please retry")
|
||||
except Exception as e: # pragma: no cover
|
||||
await db.rollback()
|
||||
logger.error(f"Error creating OAuth state: {e!s}", exc_info=True)
|
||||
logger.exception("Error creating OAuth state: %s", e)
|
||||
raise
|
||||
|
||||
async def get_and_consume_state(
|
||||
@@ -65,7 +65,7 @@ class OAuthStateRepository(BaseRepository[OAuthState, OAuthStateCreate, EmptySch
|
||||
db_obj = result.scalar_one_or_none()
|
||||
|
||||
if db_obj is None:
|
||||
logger.warning(f"OAuth state not found: {state[:8]}...")
|
||||
logger.warning("OAuth state not found: %s...", state[:8])
|
||||
return None
|
||||
|
||||
now = datetime.now(UTC)
|
||||
@@ -74,7 +74,7 @@ class OAuthStateRepository(BaseRepository[OAuthState, OAuthStateCreate, EmptySch
|
||||
expires_at = expires_at.replace(tzinfo=UTC)
|
||||
|
||||
if expires_at < now:
|
||||
logger.warning(f"OAuth state expired: {state[:8]}...")
|
||||
logger.warning("OAuth state expired: %s...", state[:8])
|
||||
await db.delete(db_obj)
|
||||
await db.commit()
|
||||
return None
|
||||
@@ -82,11 +82,11 @@ class OAuthStateRepository(BaseRepository[OAuthState, OAuthStateCreate, EmptySch
|
||||
await db.delete(db_obj)
|
||||
await db.commit()
|
||||
|
||||
logger.debug(f"OAuth state consumed: {state[:8]}...")
|
||||
logger.debug("OAuth state consumed: %s...", state[:8])
|
||||
return db_obj
|
||||
except Exception as e: # pragma: no cover
|
||||
await db.rollback()
|
||||
logger.error(f"Error consuming OAuth state: {e!s}")
|
||||
logger.error("Error consuming OAuth state: %s", e)
|
||||
raise
|
||||
|
||||
async def cleanup_expired(self, db: AsyncSession) -> int:
|
||||
@@ -100,12 +100,12 @@ class OAuthStateRepository(BaseRepository[OAuthState, OAuthStateCreate, EmptySch
|
||||
|
||||
count = result.rowcount
|
||||
if count > 0:
|
||||
logger.info(f"Cleaned up {count} expired OAuth states")
|
||||
logger.info("Cleaned up %s expired OAuth states", count)
|
||||
|
||||
return count
|
||||
except Exception as e: # pragma: no cover
|
||||
await db.rollback()
|
||||
logger.error(f"Error cleaning up expired OAuth states: {e!s}")
|
||||
logger.error("Error cleaning up expired OAuth states: %s", e)
|
||||
raise
|
||||
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ class OrganizationRepository(
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting organization by slug {slug}: {e!s}")
|
||||
logger.error("Error getting organization by slug %s: %s", slug, e)
|
||||
raise
|
||||
|
||||
async def create(
|
||||
@@ -62,17 +62,15 @@ class OrganizationRepository(
|
||||
or "unique" in error_msg.lower()
|
||||
or "duplicate" in error_msg.lower()
|
||||
):
|
||||
logger.warning(f"Duplicate slug attempted: {obj_in.slug}")
|
||||
logger.warning("Duplicate slug attempted: %s", obj_in.slug)
|
||||
raise DuplicateEntryError(
|
||||
f"Organization with slug '{obj_in.slug}' already exists"
|
||||
)
|
||||
logger.error(f"Integrity error creating organization: {error_msg}")
|
||||
logger.error("Integrity error creating organization: %s", error_msg)
|
||||
raise IntegrityConstraintError(f"Database integrity error: {error_msg}")
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(
|
||||
f"Unexpected error creating organization: {e!s}", exc_info=True
|
||||
)
|
||||
logger.exception("Unexpected error creating organization: %s", e)
|
||||
raise
|
||||
|
||||
async def get_multi_with_filters(
|
||||
@@ -117,7 +115,7 @@ class OrganizationRepository(
|
||||
|
||||
return organizations, total
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting organizations with filters: {e!s}")
|
||||
logger.error("Error getting organizations with filters: %s", e)
|
||||
raise
|
||||
|
||||
async def get_member_count(self, db: AsyncSession, *, organization_id: UUID) -> int:
|
||||
@@ -134,7 +132,7 @@ class OrganizationRepository(
|
||||
return result.scalar_one() or 0
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error getting member count for organization {organization_id}: {e!s}"
|
||||
"Error getting member count for organization %s: %s", organization_id, e
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -207,9 +205,7 @@ class OrganizationRepository(
|
||||
return orgs_with_counts, total
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error getting organizations with member counts: {e!s}", exc_info=True
|
||||
)
|
||||
logger.exception("Error getting organizations with member counts: %s", e)
|
||||
raise
|
||||
|
||||
async def add_user(
|
||||
@@ -259,11 +255,11 @@ class OrganizationRepository(
|
||||
return user_org
|
||||
except IntegrityError as e:
|
||||
await db.rollback()
|
||||
logger.error(f"Integrity error adding user to organization: {e!s}")
|
||||
logger.error("Integrity error adding user to organization: %s", e)
|
||||
raise IntegrityConstraintError("Failed to add user to organization")
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(f"Error adding user to organization: {e!s}", exc_info=True)
|
||||
logger.exception("Error adding user to organization: %s", e)
|
||||
raise
|
||||
|
||||
async def remove_user(
|
||||
@@ -289,7 +285,7 @@ class OrganizationRepository(
|
||||
return True
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(f"Error removing user from organization: {e!s}", exc_info=True)
|
||||
logger.exception("Error removing user from organization: %s", e)
|
||||
raise
|
||||
|
||||
async def update_user_role(
|
||||
@@ -324,7 +320,7 @@ class OrganizationRepository(
|
||||
return user_org
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(f"Error updating user role: {e!s}", exc_info=True)
|
||||
logger.exception("Error updating user role: %s", e)
|
||||
raise
|
||||
|
||||
async def get_organization_members(
|
||||
@@ -384,7 +380,7 @@ class OrganizationRepository(
|
||||
|
||||
return members, total
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting organization members: {e!s}")
|
||||
logger.error("Error getting organization members: %s", e)
|
||||
raise
|
||||
|
||||
async def get_user_organizations(
|
||||
@@ -407,7 +403,7 @@ class OrganizationRepository(
|
||||
result = await db.execute(query)
|
||||
return list(result.scalars().all())
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting user organizations: {e!s}")
|
||||
logger.error("Error getting user organizations: %s", e)
|
||||
raise
|
||||
|
||||
async def get_user_organizations_with_details(
|
||||
@@ -456,9 +452,7 @@ class OrganizationRepository(
|
||||
]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error getting user organizations with details: {e!s}", exc_info=True
|
||||
)
|
||||
logger.exception("Error getting user organizations with details: %s", e)
|
||||
raise
|
||||
|
||||
async def get_user_role_in_org(
|
||||
@@ -479,7 +473,7 @@ class OrganizationRepository(
|
||||
|
||||
return user_org.role if user_org else None # pyright: ignore[reportReturnType]
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting user role in org: {e!s}")
|
||||
logger.error("Error getting user role in org: %s", e)
|
||||
raise
|
||||
|
||||
async def is_user_org_owner(
|
||||
|
||||
@@ -29,7 +29,7 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting session by JTI {jti}: {e!s}")
|
||||
logger.error("Error getting session by JTI %s: %s", jti, e)
|
||||
raise
|
||||
|
||||
async def get_active_by_jti(
|
||||
@@ -47,7 +47,7 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting active session by JTI {jti}: {e!s}")
|
||||
logger.error("Error getting active session by JTI %s: %s", jti, e)
|
||||
raise
|
||||
|
||||
async def get_user_sessions(
|
||||
@@ -74,7 +74,7 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate
|
||||
result = await db.execute(query)
|
||||
return list(result.scalars().all())
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting sessions for user {user_id}: {e!s}")
|
||||
logger.error("Error getting sessions for user %s: %s", user_id, e)
|
||||
raise
|
||||
|
||||
async def create_session(
|
||||
@@ -100,14 +100,16 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate
|
||||
await db.refresh(db_obj)
|
||||
|
||||
logger.info(
|
||||
f"Session created for user {obj_in.user_id} from {obj_in.device_name} "
|
||||
f"(IP: {obj_in.ip_address})"
|
||||
"Session created for user %s from %s (IP: %s)",
|
||||
obj_in.user_id,
|
||||
obj_in.device_name,
|
||||
obj_in.ip_address,
|
||||
)
|
||||
|
||||
return db_obj
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(f"Error creating session: {e!s}", exc_info=True)
|
||||
logger.exception("Error creating session: %s", e)
|
||||
raise IntegrityConstraintError(f"Failed to create session: {e!s}")
|
||||
|
||||
async def deactivate(
|
||||
@@ -117,7 +119,7 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate
|
||||
try:
|
||||
session = await self.get(db, id=session_id)
|
||||
if not session:
|
||||
logger.warning(f"Session {session_id} not found for deactivation")
|
||||
logger.warning("Session %s not found for deactivation", session_id)
|
||||
return None
|
||||
|
||||
session.is_active = False
|
||||
@@ -126,14 +128,16 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate
|
||||
await db.refresh(session)
|
||||
|
||||
logger.info(
|
||||
f"Session {session_id} deactivated for user {session.user_id} "
|
||||
f"({session.device_name})"
|
||||
"Session %s deactivated for user %s (%s)",
|
||||
session_id,
|
||||
session.user_id,
|
||||
session.device_name,
|
||||
)
|
||||
|
||||
return session
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(f"Error deactivating session {session_id}: {e!s}")
|
||||
logger.error("Error deactivating session %s: %s", session_id, e)
|
||||
raise
|
||||
|
||||
async def deactivate_all_user_sessions(
|
||||
@@ -154,12 +158,12 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate
|
||||
|
||||
count = result.rowcount
|
||||
|
||||
logger.info(f"Deactivated {count} sessions for user {user_id}")
|
||||
logger.info("Deactivated %s sessions for user %s", count, user_id)
|
||||
|
||||
return count
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(f"Error deactivating all sessions for user {user_id}: {e!s}")
|
||||
logger.error("Error deactivating all sessions for user %s: %s", user_id, e)
|
||||
raise
|
||||
|
||||
async def update_last_used(
|
||||
@@ -174,7 +178,7 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate
|
||||
return session
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(f"Error updating last_used for session {session.id}: {e!s}")
|
||||
logger.error("Error updating last_used for session %s: %s", session.id, e)
|
||||
raise
|
||||
|
||||
async def update_refresh_token(
|
||||
@@ -197,7 +201,7 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(
|
||||
f"Error updating refresh token for session {session.id}: {e!s}"
|
||||
"Error updating refresh token for session %s: %s", session.id, e
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -221,12 +225,12 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate
|
||||
count = result.rowcount
|
||||
|
||||
if count > 0:
|
||||
logger.info(f"Cleaned up {count} expired sessions using bulk DELETE")
|
||||
logger.info("Cleaned up %s expired sessions using bulk DELETE", count)
|
||||
|
||||
return count
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(f"Error cleaning up expired sessions: {e!s}")
|
||||
logger.error("Error cleaning up expired sessions: %s", e)
|
||||
raise
|
||||
|
||||
async def cleanup_expired_for_user(self, db: AsyncSession, *, user_id: str) -> int:
|
||||
@@ -235,7 +239,7 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate
|
||||
try:
|
||||
uuid_obj = uuid.UUID(user_id)
|
||||
except (ValueError, AttributeError):
|
||||
logger.error(f"Invalid UUID format: {user_id}")
|
||||
logger.error("Invalid UUID format: %s", user_id)
|
||||
raise InvalidInputError(f"Invalid user ID format: {user_id}")
|
||||
|
||||
now = datetime.now(UTC)
|
||||
@@ -255,14 +259,16 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate
|
||||
|
||||
if count > 0:
|
||||
logger.info(
|
||||
f"Cleaned up {count} expired sessions for user {user_id} using bulk DELETE"
|
||||
"Cleaned up %s expired sessions for user %s using bulk DELETE",
|
||||
count,
|
||||
user_id,
|
||||
)
|
||||
|
||||
return count
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(
|
||||
f"Error cleaning up expired sessions for user {user_id}: {e!s}"
|
||||
"Error cleaning up expired sessions for user %s: %s", user_id, e
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -278,7 +284,7 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate
|
||||
)
|
||||
return result.scalar_one()
|
||||
except Exception as e:
|
||||
logger.error(f"Error counting sessions for user {user_id}: {e!s}")
|
||||
logger.error("Error counting sessions for user %s: %s", user_id, e)
|
||||
raise
|
||||
|
||||
async def get_all_sessions(
|
||||
@@ -319,7 +325,7 @@ class SessionRepository(BaseRepository[UserSession, SessionCreate, SessionUpdate
|
||||
return sessions, total
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting all sessions: {e!s}", exc_info=True)
|
||||
logger.exception("Error getting all sessions: %s", e)
|
||||
raise
|
||||
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class UserRepository(BaseRepository[User, UserCreate, UserUpdate]):
|
||||
result = await db.execute(select(User).where(User.email == email))
|
||||
return result.scalar_one_or_none()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting user by email {email}: {e!s}")
|
||||
logger.error("Error getting user by email %s: %s", email, e)
|
||||
raise
|
||||
|
||||
async def create(self, db: AsyncSession, *, obj_in: UserCreate) -> User:
|
||||
@@ -57,15 +57,15 @@ class UserRepository(BaseRepository[User, UserCreate, UserUpdate]):
|
||||
await db.rollback()
|
||||
error_msg = str(e.orig) if hasattr(e, "orig") else str(e)
|
||||
if "email" in error_msg.lower():
|
||||
logger.warning(f"Duplicate email attempted: {obj_in.email}")
|
||||
logger.warning("Duplicate email attempted: %s", obj_in.email)
|
||||
raise DuplicateEntryError(
|
||||
f"User with email {obj_in.email} already exists"
|
||||
)
|
||||
logger.error(f"Integrity error creating user: {error_msg}")
|
||||
logger.error("Integrity error creating user: %s", error_msg)
|
||||
raise DuplicateEntryError(f"Database integrity error: {error_msg}")
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(f"Unexpected error creating user: {e!s}", exc_info=True)
|
||||
logger.exception("Unexpected error creating user: %s", e)
|
||||
raise
|
||||
|
||||
async def create_oauth_user(
|
||||
@@ -93,13 +93,13 @@ class UserRepository(BaseRepository[User, UserCreate, UserUpdate]):
|
||||
await db.rollback()
|
||||
error_msg = str(e.orig) if hasattr(e, "orig") else str(e)
|
||||
if "email" in error_msg.lower():
|
||||
logger.warning(f"Duplicate email attempted: {email}")
|
||||
logger.warning("Duplicate email attempted: %s", email)
|
||||
raise DuplicateEntryError(f"User with email {email} already exists")
|
||||
logger.error(f"Integrity error creating OAuth user: {error_msg}")
|
||||
logger.error("Integrity error creating OAuth user: %s", error_msg)
|
||||
raise DuplicateEntryError(f"Database integrity error: {error_msg}")
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(f"Unexpected error creating OAuth user: {e!s}", exc_info=True)
|
||||
logger.exception("Unexpected error creating OAuth user: %s", e)
|
||||
raise
|
||||
|
||||
async def update(
|
||||
@@ -184,7 +184,7 @@ class UserRepository(BaseRepository[User, UserCreate, UserUpdate]):
|
||||
return users, total
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error retrieving paginated users: {e!s}")
|
||||
logger.error("Error retrieving paginated users: %s", e)
|
||||
raise
|
||||
|
||||
async def bulk_update_status(
|
||||
@@ -206,12 +206,14 @@ class UserRepository(BaseRepository[User, UserCreate, UserUpdate]):
|
||||
await db.commit()
|
||||
|
||||
updated_count = result.rowcount
|
||||
logger.info(f"Bulk updated {updated_count} users to is_active={is_active}")
|
||||
logger.info(
|
||||
"Bulk updated %s users to is_active=%s", updated_count, is_active
|
||||
)
|
||||
return updated_count
|
||||
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(f"Error bulk updating user status: {e!s}", exc_info=True)
|
||||
logger.exception("Error bulk updating user status: %s", e)
|
||||
raise
|
||||
|
||||
async def bulk_soft_delete(
|
||||
@@ -246,12 +248,12 @@ class UserRepository(BaseRepository[User, UserCreate, UserUpdate]):
|
||||
await db.commit()
|
||||
|
||||
deleted_count = result.rowcount
|
||||
logger.info(f"Bulk soft deleted {deleted_count} users")
|
||||
logger.info("Bulk soft deleted %s users", deleted_count)
|
||||
return deleted_count
|
||||
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
logger.error(f"Error bulk deleting users: {e!s}", exc_info=True)
|
||||
logger.exception("Error bulk deleting users: %s", e)
|
||||
raise
|
||||
|
||||
def is_active(self, user: User) -> bool:
|
||||
|
||||
@@ -85,7 +85,7 @@ class AuthService:
|
||||
# Delegate creation (hashing + commit) to the repository
|
||||
user = await user_repo.create(db, obj_in=user_data)
|
||||
|
||||
logger.info(f"User created successfully: {user.email}")
|
||||
logger.info("User created successfully: %s", user.email)
|
||||
return user
|
||||
|
||||
except (AuthenticationError, DuplicateError):
|
||||
@@ -94,7 +94,7 @@ class AuthService:
|
||||
except DuplicateEntryError as e:
|
||||
raise DuplicateError(str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating user: {e!s}", exc_info=True)
|
||||
logger.exception("Error creating user: %s", e)
|
||||
raise AuthenticationError(f"Failed to create user: {e!s}")
|
||||
|
||||
@staticmethod
|
||||
@@ -166,7 +166,7 @@ class AuthService:
|
||||
return AuthService.create_tokens(user)
|
||||
|
||||
except (TokenExpiredError, TokenInvalidError) as e:
|
||||
logger.warning(f"Token refresh failed: {e!s}")
|
||||
logger.warning("Token refresh failed: %s", e)
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
@@ -201,7 +201,7 @@ class AuthService:
|
||||
new_hash = await get_password_hash_async(new_password)
|
||||
await user_repo.update_password(db, user=user, password_hash=new_hash)
|
||||
|
||||
logger.info(f"Password changed successfully for user {user_id}")
|
||||
logger.info("Password changed successfully for user %s", user_id)
|
||||
return True
|
||||
|
||||
except AuthenticationError:
|
||||
@@ -210,9 +210,7 @@ class AuthService:
|
||||
except Exception as e:
|
||||
# Rollback on any database errors
|
||||
await db.rollback()
|
||||
logger.error(
|
||||
f"Error changing password for user {user_id}: {e!s}", exc_info=True
|
||||
)
|
||||
logger.exception("Error changing password for user %s: %s", user_id, e)
|
||||
raise AuthenticationError(f"Failed to change password: {e!s}")
|
||||
|
||||
@staticmethod
|
||||
@@ -241,5 +239,5 @@ class AuthService:
|
||||
|
||||
new_hash = await get_password_hash_async(new_password)
|
||||
user = await user_repo.update_password(db, user=user, password_hash=new_hash)
|
||||
logger.info(f"Password reset successfully for {email}")
|
||||
logger.info("Password reset successfully for %s", email)
|
||||
return user
|
||||
|
||||
@@ -58,8 +58,8 @@ class ConsoleEmailBackend(EmailBackend):
|
||||
logger.info("=" * 80)
|
||||
logger.info("EMAIL SENT (Console Backend)")
|
||||
logger.info("=" * 80)
|
||||
logger.info(f"To: {', '.join(to)}")
|
||||
logger.info(f"Subject: {subject}")
|
||||
logger.info("To: %s", ", ".join(to))
|
||||
logger.info("Subject: %s", subject)
|
||||
logger.info("-" * 80)
|
||||
if text_content:
|
||||
logger.info("Plain Text Content:")
|
||||
@@ -199,7 +199,7 @@ The {settings.PROJECT_NAME} Team
|
||||
text_content=text_content,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send password reset email to {to_email}: {e!s}")
|
||||
logger.error("Failed to send password reset email to %s: %s", to_email, e)
|
||||
return False
|
||||
|
||||
async def send_email_verification(
|
||||
@@ -287,7 +287,7 @@ The {settings.PROJECT_NAME} Team
|
||||
text_content=text_content,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send verification email to {to_email}: {e!s}")
|
||||
logger.error("Failed to send verification email to %s: %s", to_email, e)
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ def verify_pkce(code_verifier: str, code_challenge: str, method: str) -> bool:
|
||||
if method != "S256":
|
||||
# SECURITY: Reject any method other than S256
|
||||
# 'plain' method provides no security against code interception attacks
|
||||
logger.warning(f"PKCE verification rejected for unsupported method: {method}")
|
||||
logger.warning("PKCE verification rejected for unsupported method: %s", method)
|
||||
return False
|
||||
|
||||
# SHA-256 hash, then base64url encode (RFC 7636 Section 4.2)
|
||||
@@ -257,7 +257,9 @@ def validate_scopes(client: OAuthClient, requested_scopes: list[str]) -> list[st
|
||||
# Warn if some scopes were filtered out
|
||||
invalid = requested - allowed
|
||||
if invalid:
|
||||
logger.warning(f"Client {client.client_id} requested invalid scopes: {invalid}")
|
||||
logger.warning(
|
||||
"Client %s requested invalid scopes: %s", client.client_id, invalid
|
||||
)
|
||||
|
||||
return list(valid)
|
||||
|
||||
@@ -320,7 +322,9 @@ async def create_authorization_code(
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Created authorization code for user {user.id} and client {client.client_id}"
|
||||
"Created authorization code for user %s and client %s",
|
||||
user.id,
|
||||
client.client_id,
|
||||
)
|
||||
return code
|
||||
|
||||
@@ -369,7 +373,8 @@ async def exchange_authorization_code(
|
||||
if existing_code and existing_code.used:
|
||||
# Code reuse is a security incident - revoke all tokens for this grant
|
||||
logger.warning(
|
||||
f"Authorization code reuse detected for client {existing_code.client_id}"
|
||||
"Authorization code reuse detected for client %s",
|
||||
existing_code.client_id,
|
||||
)
|
||||
await revoke_tokens_for_user_client(
|
||||
db, UUID(str(existing_code.user_id)), str(existing_code.client_id)
|
||||
@@ -527,7 +532,7 @@ async def create_tokens(
|
||||
ip_address=ip_address,
|
||||
)
|
||||
|
||||
logger.info(f"Issued tokens for user {user.id} to client {client.client_id}")
|
||||
logger.info("Issued tokens for user %s to client %s", user.id, client.client_id)
|
||||
|
||||
return {
|
||||
"access_token": access_token,
|
||||
@@ -580,7 +585,7 @@ async def refresh_tokens(
|
||||
if token_record.revoked:
|
||||
# Token reuse after revocation - security incident
|
||||
logger.warning(
|
||||
f"Revoked refresh token reuse detected for client {token_record.client_id}"
|
||||
"Revoked refresh token reuse detected for client %s", token_record.client_id
|
||||
)
|
||||
raise InvalidGrantError("Refresh token has been revoked")
|
||||
|
||||
@@ -672,7 +677,7 @@ async def revoke_token(
|
||||
raise InvalidClientError("Token was not issued to this client")
|
||||
|
||||
await oauth_provider_token_repo.revoke(db, token=refresh_record)
|
||||
logger.info(f"Revoked refresh token {refresh_record.jti[:8]}...")
|
||||
logger.info("Revoked refresh token %s...", refresh_record.jti[:8])
|
||||
return True
|
||||
|
||||
# Try as access token (JWT)
|
||||
@@ -696,7 +701,7 @@ async def revoke_token(
|
||||
raise InvalidClientError("Token was not issued to this client")
|
||||
await oauth_provider_token_repo.revoke(db, token=refresh_record)
|
||||
logger.info(
|
||||
f"Revoked refresh token via access token JTI {jti[:8]}..."
|
||||
"Revoked refresh token via access token JTI %s...", jti[:8]
|
||||
)
|
||||
return True
|
||||
except JWTError:
|
||||
@@ -731,7 +736,7 @@ async def revoke_tokens_for_user_client(
|
||||
|
||||
if count > 0:
|
||||
logger.warning(
|
||||
f"Revoked {count} tokens for user {user_id} and client {client_id}"
|
||||
"Revoked %s tokens for user %s and client %s", count, user_id, client_id
|
||||
)
|
||||
|
||||
return count
|
||||
@@ -753,7 +758,7 @@ async def revoke_all_user_tokens(db: AsyncSession, user_id: UUID) -> int:
|
||||
count = await oauth_provider_token_repo.revoke_all_for_user(db, user_id=user_id)
|
||||
|
||||
if count > 0:
|
||||
logger.info(f"Revoked {count} OAuth provider tokens for user {user_id}")
|
||||
logger.info("Revoked %s OAuth provider tokens for user %s", count, user_id)
|
||||
|
||||
return count
|
||||
|
||||
|
||||
@@ -219,7 +219,7 @@ class OAuthService:
|
||||
**auth_params,
|
||||
)
|
||||
|
||||
logger.info(f"OAuth authorization URL created for {provider}")
|
||||
logger.info("OAuth authorization URL created for %s", provider)
|
||||
return url, state
|
||||
|
||||
@staticmethod
|
||||
@@ -254,8 +254,9 @@ class OAuthService:
|
||||
# This prevents authorization code injection attacks (RFC 6749 Section 10.6)
|
||||
if state_record.redirect_uri != redirect_uri:
|
||||
logger.warning(
|
||||
f"OAuth redirect_uri mismatch: expected {state_record.redirect_uri}, "
|
||||
f"got {redirect_uri}"
|
||||
"OAuth redirect_uri mismatch: expected %s, got %s",
|
||||
state_record.redirect_uri,
|
||||
redirect_uri,
|
||||
)
|
||||
raise AuthenticationError("Redirect URI mismatch")
|
||||
|
||||
@@ -299,7 +300,7 @@ class OAuthService:
|
||||
except AuthenticationError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"OAuth token exchange failed: {e!s}")
|
||||
logger.error("OAuth token exchange failed: %s", e)
|
||||
raise AuthenticationError("Failed to exchange authorization code")
|
||||
|
||||
# Get user info from provider
|
||||
@@ -312,7 +313,7 @@ class OAuthService:
|
||||
client, provider, config, access_token
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get user info: {e!s}")
|
||||
logger.error("Failed to get user info: %s", e)
|
||||
raise AuthenticationError(
|
||||
"Failed to get user information from provider"
|
||||
)
|
||||
@@ -353,7 +354,7 @@ class OAuthService:
|
||||
+ timedelta(seconds=token.get("expires_in", 3600)),
|
||||
)
|
||||
|
||||
logger.info(f"OAuth login successful for {user.email} via {provider}")
|
||||
logger.info("OAuth login successful for %s via %s", user.email, provider)
|
||||
|
||||
elif state_record.user_id:
|
||||
# Account linking flow (user is already logged in)
|
||||
@@ -387,7 +388,7 @@ class OAuthService:
|
||||
)
|
||||
await oauth_account.create_account(db, obj_in=oauth_create)
|
||||
|
||||
logger.info(f"OAuth account linked: {provider} -> {user.email}")
|
||||
logger.info("OAuth account linked: %s -> %s", provider, user.email)
|
||||
|
||||
else:
|
||||
# New OAuth login - check for existing user by email
|
||||
@@ -409,7 +410,9 @@ class OAuthService:
|
||||
if existing_provider:
|
||||
# This shouldn't happen if we got here, but safety check
|
||||
logger.warning(
|
||||
f"OAuth account already linked (race condition?): {provider} -> {user.email}"
|
||||
"OAuth account already linked (race condition?): %s -> %s",
|
||||
provider,
|
||||
user.email,
|
||||
)
|
||||
else:
|
||||
# Create OAuth account link
|
||||
@@ -427,7 +430,9 @@ class OAuthService:
|
||||
)
|
||||
await oauth_account.create_account(db, obj_in=oauth_create)
|
||||
|
||||
logger.info(f"OAuth auto-linked by email: {provider} -> {user.email}")
|
||||
logger.info(
|
||||
"OAuth auto-linked by email: %s -> %s", provider, user.email
|
||||
)
|
||||
|
||||
else:
|
||||
# Create new user
|
||||
@@ -447,7 +452,7 @@ class OAuthService:
|
||||
)
|
||||
is_new_user = True
|
||||
|
||||
logger.info(f"New user created via OAuth: {user.email} ({provider})")
|
||||
logger.info("New user created via OAuth: %s (%s)", user.email, provider)
|
||||
|
||||
# Generate JWT tokens
|
||||
claims = {
|
||||
@@ -583,8 +588,9 @@ class OAuthService:
|
||||
token_nonce = payload.get("nonce")
|
||||
if token_nonce != expected_nonce:
|
||||
logger.warning(
|
||||
f"OAuth ID token nonce mismatch: expected {expected_nonce}, "
|
||||
f"got {token_nonce}"
|
||||
"OAuth ID token nonce mismatch: expected %s, got %s",
|
||||
expected_nonce,
|
||||
token_nonce,
|
||||
)
|
||||
raise AuthenticationError("Invalid ID token nonce")
|
||||
|
||||
@@ -592,14 +598,14 @@ class OAuthService:
|
||||
return payload
|
||||
|
||||
except JWTError as e:
|
||||
logger.warning(f"Google ID token verification failed: {e}")
|
||||
logger.warning("Google ID token verification failed: %s", e)
|
||||
raise AuthenticationError("Invalid ID token signature")
|
||||
except httpx.HTTPError as e:
|
||||
logger.error(f"Failed to fetch Google JWKS: {e}")
|
||||
logger.error("Failed to fetch Google JWKS: %s", e)
|
||||
# If we can't verify the ID token, fail closed for security
|
||||
raise AuthenticationError("Failed to verify ID token")
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error verifying Google ID token: {e}")
|
||||
logger.error("Unexpected error verifying Google ID token: %s", e)
|
||||
raise AuthenticationError("ID token verification error")
|
||||
|
||||
@staticmethod
|
||||
@@ -701,7 +707,7 @@ class OAuthService:
|
||||
if not deleted:
|
||||
raise AuthenticationError(f"No {provider} account found to unlink")
|
||||
|
||||
logger.info(f"OAuth provider unlinked: {provider} from {user.email}")
|
||||
logger.info("OAuth provider unlinked: %s from %s", provider, user.email)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -35,12 +35,12 @@ async def cleanup_expired_sessions(keep_days: int = 30) -> int:
|
||||
# Use CRUD method to cleanup
|
||||
count = await session_crud.cleanup_expired(db, keep_days=keep_days)
|
||||
|
||||
logger.info(f"Session cleanup complete: {count} sessions deleted")
|
||||
logger.info("Session cleanup complete: %s sessions deleted", count)
|
||||
|
||||
return count
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during session cleanup: {e!s}", exc_info=True)
|
||||
logger.exception("Error during session cleanup: %s", e)
|
||||
return 0
|
||||
|
||||
|
||||
@@ -79,10 +79,10 @@ async def get_session_statistics() -> dict:
|
||||
"expired": expired_sessions,
|
||||
}
|
||||
|
||||
logger.info(f"Session statistics: {stats}")
|
||||
logger.info("Session statistics: %s", stats)
|
||||
|
||||
return stats
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting session statistics: {e!s}", exc_info=True)
|
||||
logger.exception("Error getting session statistics: %s", e)
|
||||
return {}
|
||||
|
||||
@@ -148,8 +148,6 @@ ignore = [
|
||||
"S607", # Starting a process with a partial path (safe usage)
|
||||
"B008", # FastAPI Depends() in function defaults (required by framework)
|
||||
"B904", # Exception chaining (overly strict for FastAPI error handlers)
|
||||
"G004", # f-string logging (TODO: migrate existing 300+ occurrences to lazy %)
|
||||
"G201", # .exception() vs .error(exc_info=True) (TODO: migrate existing 64 occurrences)
|
||||
]
|
||||
|
||||
# Allow autofix for all enabled rules
|
||||
|
||||
Reference in New Issue
Block a user