from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select from sqlalchemy.orm import selectinload from sqlalchemy.exc import SQLAlchemyError, IntegrityError, OperationalError from typing import Optional import logging from app.models import User as UserModel, UserGroup as UserGroupModel from app.schemas.user import UserCreate from app.core.security import hash_password from app.core.exceptions import ( UserCreationError, EmailAlreadyRegisteredError, DatabaseConnectionError, DatabaseIntegrityError, DatabaseQueryError, DatabaseTransactionError, UserOperationError ) logger = logging.getLogger(__name__) async def get_user_by_email(db: AsyncSession, email: str) -> Optional[UserModel]: """Fetches a user from the database by email, with common relationships.""" try: stmt = ( select(UserModel) .filter(UserModel.email == email) .options( selectinload(UserModel.group_associations).selectinload(UserGroupModel.group), ) ) result = await db.execute(stmt) return result.scalars().first() except OperationalError as e: logger.error(f"Database connection error while fetching user by email '{email}': {str(e)}", exc_info=True) raise DatabaseConnectionError(f"Failed to connect to database: {str(e)}") except SQLAlchemyError as e: logger.error(f"Unexpected SQLAlchemy error while fetching user by email '{email}': {str(e)}", exc_info=True) raise DatabaseQueryError(f"Failed to query user: {str(e)}") async def create_user(db: AsyncSession, user_in: UserCreate, is_guest: bool = False) -> UserModel: """Creates a new user record in the database with common relationships loaded.""" try: async with db.begin_nested() if db.in_transaction() else db.begin() as transaction: _hashed_password = hash_password(user_in.password) db_user = UserModel( email=user_in.email, hashed_password=_hashed_password, name=user_in.name, is_guest=is_guest ) db.add(db_user) await db.flush() stmt = ( select(UserModel) .where(UserModel.id == db_user.id) .options( selectinload(UserModel.group_associations).selectinload(UserGroupModel.group), selectinload(UserModel.created_groups) ) ) result = await db.execute(stmt) loaded_user = result.scalar_one_or_none() if loaded_user is None: raise UserOperationError("Failed to load user after creation.") return loaded_user except IntegrityError as e: logger.error(f"Database integrity error during user creation for email '{user_in.email}': {str(e)}", exc_info=True) if "unique constraint" in str(e).lower() and ("users_email_key" in str(e).lower() or "ix_users_email" in str(e).lower()): raise EmailAlreadyRegisteredError(email=user_in.email) raise DatabaseIntegrityError(f"Failed to create user due to integrity issue: {str(e)}") except OperationalError as e: logger.error(f"Database connection error during user creation for email '{user_in.email}': {str(e)}", exc_info=True) raise DatabaseConnectionError(f"Database connection error during user creation: {str(e)}") except SQLAlchemyError as e: logger.error(f"Unexpected SQLAlchemy error during user creation for email '{user_in.email}': {str(e)}", exc_info=True) raise DatabaseTransactionError(f"Failed to create user due to other DB error: {str(e)}")