"""Service layer for user profile management. All user database operations go through this service. Routes should never query the users table directly. """ from typing import Optional import structlog from sqlmodel import Session, select from app.models.user import User logger = structlog.get_logger(__name__) class UserService: """Handles CRUD operations for User records. Args: session: An active SQLModel Session. """ def __init__(self, session: Session) -> None: self._session = session def create_user( self, username: str, password_hash: str, display_name: str, height: Optional[str] = None, weight: Optional[str] = None, goals: Optional[str] = None, is_admin: bool = False, ) -> User: """Create a new user profile. Args: username: Unique login identifier. password_hash: Pre-hashed password string. display_name: Human-readable name. height: User height as string. weight: User weight as string. goals: Free-text goals. is_admin: Whether user has admin privileges. Returns: The newly created User record. """ user = User( username=username, password_hash=password_hash, display_name=display_name, height=height, weight=weight, goals=goals, is_admin=is_admin, ) self._session.add(user) self._session.commit() self._session.refresh(user) logger.info("user_created", username=username, is_admin=is_admin) return user def get_user_by_id(self, user_id: int) -> Optional[User]: """Retrieve a user by primary key. Args: user_id: The user's ID. Returns: The User record, or None if not found. """ return self._session.get(User, user_id) def get_user_by_username(self, username: str) -> Optional[User]: """Retrieve a user by username. Args: username: The username to look up. Returns: The User record, or None if not found. """ statement = select(User).where(User.username == username) return self._session.exec(statement).first() def list_users(self, exclude_admin: bool = False) -> list[User]: """List all user profiles. Args: exclude_admin: If True, omit admin users from the result. Returns: List of User records. """ statement = select(User) if exclude_admin: statement = statement.where(User.is_admin == False) # noqa: E712 return list(self._session.exec(statement).all()) def update_user(self, user_id: int, **kwargs) -> User: """Update fields on an existing user. Args: user_id: The user's ID. **kwargs: Field names and new values to update. Returns: The updated User record. Raises: ValueError: If the user is not found. """ user = self.get_user_by_id(user_id) if user is None: raise ValueError(f"User with id {user_id} not found") for key, value in kwargs.items(): if hasattr(user, key): setattr(user, key, value) self._session.add(user) self._session.commit() self._session.refresh(user) logger.info("user_updated", user_id=user_id, fields=list(kwargs.keys())) return user