Files
SneakySwole/app/services/user_service.py
Phillip Tarrant 576d3bbb68 feat: replace admin auth with cookie-based profile picker
Remove all authentication (login, sessions, bcrypt, itsdangerous) since
the app runs on a private homelab LAN. Replace with a profile picker
landing page and cookie-based profile selection (1-year expiry).

- Add Alembic migration to drop password_hash/is_admin columns
- Delete auth service, auth routes, login template, and auth tests
- Rewrite app/utils/auth.py with NoProfileSelectedError and
  require_active_profile dependency
- Add profile creation flow (GET/POST /profiles/create)
- Rewrite home page as profile picker with card layout
- Update all route files to use profile dependency instead of admin auth
- Remove bcrypt and itsdangerous from requirements
- Remove admin_username/admin_password from config
- Update all tests for new profile-based access model

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 12:40:54 -05:00

100 lines
2.7 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,
display_name: str,
height: Optional[str] = None,
weight: Optional[str] = None,
goals: Optional[str] = None,
) -> User:
"""Create a new user profile.
Args:
username: Unique identifier.
display_name: Human-readable name.
height: User height as string.
weight: User weight as string.
goals: Free-text goals.
Returns:
The newly created User record.
"""
user = User(
username=username,
display_name=display_name,
height=height,
weight=weight,
goals=goals,
)
self._session.add(user)
self._session.commit()
self._session.refresh(user)
logger.info("user_created", username=username)
return user
def get_user_by_id(self, user_id: int) -> Optional[User]:
"""Retrieve a user by primary key."""
return self._session.get(User, user_id)
def get_user_by_username(self, username: str) -> Optional[User]:
"""Retrieve a user by username."""
statement = select(User).where(User.username == username)
return self._session.exec(statement).first()
def list_users(self) -> list[User]:
"""List all user profiles."""
statement = select(User)
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