"""User service. Account creation here is a temporary, open dev bootstrap so we can create tree owners before the auth slice exists; the auth slice replaces it with the AuthProvider (password/OIDC/social) and proper verification. """ import uuid from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.models.person import Person from app.models.tree import Tree from app.models.user import User from app.repositories.base import BaseRepository from app.services import privacy from app.services.audit import record_audit from app.services.exceptions import Conflict, Forbidden, NotFound async def create_user( session: AsyncSession, *, email: str, display_name: str | None = None ) -> User: email = email.strip().lower() existing = ( await session.execute(select(User).where(User.email == email)) ).scalar_one_or_none() if existing is not None: raise Conflict("email already registered") user = User(email=email, display_name=display_name) session.add(user) await session.flush() # assign user.id record_audit( session, action="create", entity_type="User", entity_id=user.id, actor_user_id=user.id, after={"email": email}, ) await session.commit() await session.refresh(user) return user async def get_user(session: AsyncSession, user_id: uuid.UUID) -> User | None: return await BaseRepository(session, User).get(user_id) async def set_self_person( session: AsyncSession, *, user: User, person_id: uuid.UUID | None ) -> User: """Point a user's account at the Person record that *is* them ("home person"), or clear it with ``None``. The person must live in a tree the user can view.""" if person_id is not None: person = ( await session.execute( select(Person).where(Person.id == person_id, Person.deleted_at.is_(None)) ) ).scalar_one_or_none() if person is None: raise NotFound("person not found") tree = ( await session.execute(select(Tree).where(Tree.id == person.tree_id)) ).scalar_one_or_none() if tree is None or not await privacy.can_view_tree( session, user_id=user.id, tree=tree ): raise Forbidden("not permitted to link this person") user.self_person_id = person_id record_audit( session, action="update", entity_type="User", entity_id=user.id, actor_user_id=user.id, after={"self_person_id": str(person_id) if person_id else None}, ) await session.commit() await session.refresh(user) return user