129 lines
3.5 KiB
Python
129 lines
3.5 KiB
Python
"""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
|