""" Enhanced User Service Updated to use repository pattern with dependency injection and improved error handling """ from datetime import datetime, timezone from typing import Dict, Any, List, Optional from fastapi import HTTPException, status import structlog from app.repositories import UserRepository, TokenRepository from app.schemas.auth import UserResponse, UserUpdate from app.models.users import User from app.models.tokens import RefreshToken from app.core.security import SecurityManager from shared.database.unit_of_work import UnitOfWork from shared.database.transactions import transactional from shared.database.exceptions import DatabaseError, ValidationError logger = structlog.get_logger() class EnhancedUserService: """Enhanced user management service using repository pattern""" def __init__(self, database_manager): """Initialize service with database manager""" self.database_manager = database_manager async def get_user_by_id(self, user_id: str) -> Optional[UserResponse]: """Get user by ID using repository pattern""" try: async with self.database_manager.get_session() as session: user_repo = UserRepository(User, session) user = await user_repo.get_by_id(user_id) if not user: return None return UserResponse( id=str(user.id), email=user.email, full_name=user.full_name, is_active=user.is_active, is_verified=user.is_verified, created_at=user.created_at, role=user.role, phone=getattr(user, 'phone', None), language=getattr(user, 'language', None), timezone=getattr(user, 'timezone', None) ) except Exception as e: logger.error("Failed to get user by ID using repository pattern", user_id=user_id, error=str(e)) raise DatabaseError(f"Failed to get user: {str(e)}") async def get_user_by_email(self, email: str) -> Optional[UserResponse]: """Get user by email using repository pattern""" try: async with self.database_manager.get_session() as session: user_repo = UserRepository(User, session) user = await user_repo.get_by_email(email) if not user: return None return UserResponse( id=str(user.id), email=user.email, full_name=user.full_name, is_active=user.is_active, is_verified=user.is_verified, created_at=user.created_at, role=user.role, phone=getattr(user, 'phone', None), language=getattr(user, 'language', None), timezone=getattr(user, 'timezone', None) ) except Exception as e: logger.error("Failed to get user by email using repository pattern", email=email, error=str(e)) raise DatabaseError(f"Failed to get user: {str(e)}") async def get_users_list( self, skip: int = 0, limit: int = 100, active_only: bool = True, role: str = None ) -> List[UserResponse]: """Get paginated list of users using repository pattern""" try: async with self.database_manager.get_session() as session: user_repo = UserRepository(User, session) filters = {} if active_only: filters["is_active"] = True if role: filters["role"] = role users = await user_repo.get_multi( filters=filters, skip=skip, limit=limit, order_by="created_at", order_desc=True ) return [ UserResponse( id=str(user.id), email=user.email, full_name=user.full_name, is_active=user.is_active, is_verified=user.is_verified, created_at=user.created_at, role=user.role, phone=getattr(user, 'phone', None), language=getattr(user, 'language', None), timezone=getattr(user, 'timezone', None) ) for user in users ] except Exception as e: logger.error("Failed to get users list using repository pattern", error=str(e)) return [] @transactional async def update_user( self, user_id: str, user_data: UserUpdate, session=None ) -> Optional[UserResponse]: """Update user information using repository pattern""" try: async with self.database_manager.get_session() as db_session: user_repo = UserRepository(User, db_session) # Validate user exists existing_user = await user_repo.get_by_id(user_id) if not existing_user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found" ) # Prepare update data update_data = {} if user_data.full_name is not None: update_data["full_name"] = user_data.full_name if user_data.phone is not None: update_data["phone"] = user_data.phone if user_data.language is not None: update_data["language"] = user_data.language if user_data.timezone is not None: update_data["timezone"] = user_data.timezone if not update_data: # No updates to apply return UserResponse( id=str(existing_user.id), email=existing_user.email, full_name=existing_user.full_name, is_active=existing_user.is_active, is_verified=existing_user.is_verified, created_at=existing_user.created_at, role=existing_user.role ) # Update user using repository updated_user = await user_repo.update(user_id, update_data) if not updated_user: raise DatabaseError("Failed to update user") logger.info("User updated successfully using repository pattern", user_id=user_id, updated_fields=list(update_data.keys())) return UserResponse( id=str(updated_user.id), email=updated_user.email, full_name=updated_user.full_name, is_active=updated_user.is_active, is_verified=updated_user.is_verified, created_at=updated_user.created_at, role=updated_user.role, phone=getattr(updated_user, 'phone', None), language=getattr(updated_user, 'language', None), timezone=getattr(updated_user, 'timezone', None) ) except HTTPException: raise except Exception as e: logger.error("Failed to update user using repository pattern", user_id=user_id, error=str(e)) raise DatabaseError(f"Failed to update user: {str(e)}") @transactional async def change_password( self, user_id: str, current_password: str, new_password: str, session=None ) -> bool: """Change user password using repository pattern""" try: async with self.database_manager.get_session() as db_session: async with UnitOfWork(db_session) as uow: # Register repositories user_repo = uow.register_repository("users", UserRepository) token_repo = uow.register_repository("tokens", TokenRepository) # Get user and verify current password user = await user_repo.get_by_id(user_id) if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found" ) if not SecurityManager.verify_password(current_password, user.hashed_password): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Current password is incorrect" ) # Hash new password and update new_hashed_password = SecurityManager.hash_password(new_password) await user_repo.update(user_id, {"hashed_password": new_hashed_password}) # Revoke all existing tokens for security await token_repo.revoke_user_tokens(user_id) # Commit transaction await uow.commit() logger.info("Password changed successfully using repository pattern", user_id=user_id) return True except HTTPException: raise except Exception as e: logger.error("Failed to change password using repository pattern", user_id=user_id, error=str(e)) raise DatabaseError(f"Failed to change password: {str(e)}") @transactional async def deactivate_user(self, user_id: str, admin_user_id: str, session=None) -> bool: """Deactivate user account using repository pattern""" try: async with self.database_manager.get_session() as db_session: async with UnitOfWork(db_session) as uow: # Register repositories user_repo = uow.register_repository("users", UserRepository) token_repo = uow.register_repository("tokens", TokenRepository) # Verify user exists user = await user_repo.get_by_id(user_id) if not user: return False # Update user status (soft delete) updated_user = await user_repo.update(user_id, {"is_active": False}) if not updated_user: return False # Revoke all tokens await token_repo.revoke_user_tokens(user_id) # Commit transaction await uow.commit() logger.info("User deactivated successfully using repository pattern", user_id=user_id, admin_user_id=admin_user_id) return True except Exception as e: logger.error("Failed to deactivate user using repository pattern", user_id=user_id, error=str(e)) return False @transactional async def activate_user(self, user_id: str, admin_user_id: str, session=None) -> bool: """Activate user account using repository pattern""" try: async with self.database_manager.get_session() as db_session: user_repo = UserRepository(User, db_session) # Update user status updated_user = await user_repo.update(user_id, {"is_active": True}) if not updated_user: return False logger.info("User activated successfully using repository pattern", user_id=user_id, admin_user_id=admin_user_id) return True except Exception as e: logger.error("Failed to activate user using repository pattern", user_id=user_id, error=str(e)) return False async def verify_user_email(self, user_id: str, verification_token: str) -> bool: """Verify user email using repository pattern""" try: async with self.database_manager.get_session() as session: user_repo = UserRepository(User, session) # In a real implementation, you'd verify the verification_token # For now, just mark user as verified updated_user = await user_repo.update(user_id, {"is_verified": True}) if updated_user: logger.info("User email verified using repository pattern", user_id=user_id) return True return False except Exception as e: logger.error("Failed to verify email using repository pattern", user_id=user_id, error=str(e)) return False async def get_user_statistics(self) -> Dict[str, Any]: """Get user statistics using repository pattern""" try: async with self.database_manager.get_session() as session: user_repo = UserRepository(User, session) # Get basic user statistics statistics = await user_repo.get_user_statistics() return statistics except Exception as e: logger.error("Failed to get user statistics using repository pattern", error=str(e)) return { "total_users": 0, "active_users": 0, "verified_users": 0, "users_by_role": {}, "recent_registrations_7d": 0 } async def search_users( self, search_term: str, role: str = None, active_only: bool = True, skip: int = 0, limit: int = 50 ) -> List[UserResponse]: """Search users by email or name using repository pattern""" try: async with self.database_manager.get_session() as session: user_repo = UserRepository(User, session) users = await user_repo.search_users( search_term, role, active_only, skip, limit ) return [ UserResponse( id=str(user.id), email=user.email, full_name=user.full_name, is_active=user.is_active, is_verified=user.is_verified, created_at=user.created_at, role=user.role, phone=getattr(user, 'phone', None), language=getattr(user, 'language', None), timezone=getattr(user, 'timezone', None) ) for user in users ] except Exception as e: logger.error("Failed to search users using repository pattern", search_term=search_term, error=str(e)) return [] async def update_user_role( self, user_id: str, new_role: str, admin_user_id: str ) -> Optional[UserResponse]: """Update user role using repository pattern""" try: async with self.database_manager.get_session() as session: user_repo = UserRepository(User, session) # Validate role valid_roles = ["user", "admin", "manager", "super_admin"] if new_role not in valid_roles: raise ValidationError(f"Invalid role. Must be one of: {valid_roles}") # Update user role updated_user = await user_repo.update(user_id, {"role": new_role}) if not updated_user: return None logger.info("User role updated using repository pattern", user_id=user_id, new_role=new_role, admin_user_id=admin_user_id) return UserResponse( id=str(updated_user.id), email=updated_user.email, full_name=updated_user.full_name, is_active=updated_user.is_active, is_verified=updated_user.is_verified, created_at=updated_user.created_at, role=updated_user.role, phone=getattr(updated_user, 'phone', None), language=getattr(updated_user, 'language', None), timezone=getattr(updated_user, 'timezone', None) ) except ValidationError: raise except Exception as e: logger.error("Failed to update user role using repository pattern", user_id=user_id, new_role=new_role, error=str(e)) raise DatabaseError(f"Failed to update role: {str(e)}") async def get_user_activity(self, user_id: str) -> Dict[str, Any]: """Get user activity information using repository pattern""" try: async with self.database_manager.get_session() as session: user_repo = UserRepository(User, session) token_repo = TokenRepository(RefreshToken, session) # Get user user = await user_repo.get_by_id(user_id) if not user: return {"error": "User not found"} # Get token activity active_tokens = await token_repo.get_active_tokens_for_user(user_id) return { "user_id": user_id, "last_login": user.last_login.isoformat() if user.last_login else None, "account_created": user.created_at.isoformat(), "is_active": user.is_active, "is_verified": user.is_verified, "active_sessions": len(active_tokens), "last_activity": max([token.created_at for token in active_tokens]).isoformat() if active_tokens else None } except Exception as e: logger.error("Failed to get user activity using repository pattern", user_id=user_id, error=str(e)) return {"error": str(e)} # Legacy compatibility - alias EnhancedUserService as UserService UserService = EnhancedUserService