# app/modules/tenancy/services/user_account_service.py """ Self-service account management for logged-in users. Allows users to view/update their own profile and change password. Used by admin, store, and merchant frontends. """ import logging from sqlalchemy.orm import Session from app.modules.tenancy.exceptions import ( InvalidCredentialsException, UserAlreadyExistsException, UserNotFoundException, ) from app.modules.tenancy.models import User from middleware.auth import AuthManager logger = logging.getLogger(__name__) class UserAccountService: """Service for self-service account operations.""" def __init__(self): self.auth_manager = AuthManager() def get_account(self, db: Session, user_id: int) -> User: """Get the logged-in user's account info.""" user = db.query(User).filter(User.id == user_id).first() if not user: raise UserNotFoundException(str(user_id)) return user def update_account(self, db: Session, user_id: int, update_data: dict) -> User: """Update the logged-in user's account info.""" user = db.query(User).filter(User.id == user_id).first() if not user: raise UserNotFoundException(str(user_id)) # Check email uniqueness if email is being changed new_email = update_data.get("email") if new_email and new_email != user.email: existing = ( db.query(User) .filter(User.email == new_email, User.id != user_id) .first() ) if existing: raise UserAlreadyExistsException( "Email already registered", field="email" ) # Apply updates (only provided fields) for field, value in update_data.items(): if value is not None: setattr(user, field, value) db.flush() db.refresh(user) logger.info(f"User {user.username} updated account: {list(update_data.keys())}") return user def change_password(self, db: Session, user_id: int, password_data: dict) -> None: """Change the logged-in user's password.""" user = db.query(User).filter(User.id == user_id).first() if not user: raise UserNotFoundException(str(user_id)) current_password = password_data["current_password"] new_password = password_data["new_password"] confirm_password = password_data["confirm_password"] # Verify current password if not self.auth_manager.verify_password(current_password, user.hashed_password): raise InvalidCredentialsException("Current password is incorrect") # Validate new != current if self.auth_manager.verify_password(new_password, user.hashed_password): raise InvalidCredentialsException( "New password must be different from current password" ) # Validate confirmation match if new_password != confirm_password: raise InvalidCredentialsException( "New password and confirmation do not match" ) # Hash and save user.hashed_password = self.auth_manager.hash_password(new_password) db.flush() logger.info(f"User {user.username} changed password") # noqa: SEC021 user_account_service = UserAccountService()