# app/crud/group.py from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select from sqlalchemy.orm import selectinload # For eager loading members from sqlalchemy.exc import SQLAlchemyError, IntegrityError, OperationalError from typing import Optional, List from app.models import User as UserModel, Group as GroupModel, UserGroup as UserGroupModel from app.schemas.group import GroupCreate from app.models import UserRoleEnum # Import enum from app.core.exceptions import ( GroupOperationError, GroupNotFoundError, DatabaseConnectionError, DatabaseIntegrityError, DatabaseQueryError, DatabaseTransactionError ) # --- Group CRUD --- async def create_group(db: AsyncSession, group_in: GroupCreate, creator_id: int) -> GroupModel: """Creates a group and adds the creator as the owner.""" try: async with db.begin(): db_group = GroupModel(name=group_in.name, created_by_id=creator_id) db.add(db_group) await db.flush() db_user_group = UserGroupModel( user_id=creator_id, group_id=db_group.id, role=UserRoleEnum.owner ) db.add(db_user_group) await db.flush() await db.refresh(db_group) return db_group except IntegrityError as e: raise DatabaseIntegrityError(f"Failed to create group: {str(e)}") except OperationalError as e: raise DatabaseConnectionError(f"Database connection error: {str(e)}") except SQLAlchemyError as e: raise DatabaseTransactionError(f"Failed to create group: {str(e)}") async def get_user_groups(db: AsyncSession, user_id: int) -> List[GroupModel]: """Gets all groups a user is a member of.""" try: async with db.begin(): result = await db.execute( select(GroupModel) .join(UserGroupModel) .where(UserGroupModel.user_id == user_id) .options(selectinload(GroupModel.member_associations)) ) return result.scalars().all() except OperationalError as e: raise DatabaseConnectionError(f"Failed to connect to database: {str(e)}") except SQLAlchemyError as e: raise DatabaseQueryError(f"Failed to query user groups: {str(e)}") async def get_group_by_id(db: AsyncSession, group_id: int) -> Optional[GroupModel]: """Gets a single group by its ID, optionally loading members.""" try: async with db.begin(): result = await db.execute( select(GroupModel) .where(GroupModel.id == group_id) .options( selectinload(GroupModel.member_associations).selectinload(UserGroupModel.user) ) ) return result.scalars().first() except OperationalError as e: raise DatabaseConnectionError(f"Failed to connect to database: {str(e)}") except SQLAlchemyError as e: raise DatabaseQueryError(f"Failed to query group: {str(e)}") async def is_user_member(db: AsyncSession, group_id: int, user_id: int) -> bool: """Checks if a user is a member of a specific group.""" try: async with db.begin(): result = await db.execute( select(UserGroupModel.id) .where(UserGroupModel.group_id == group_id, UserGroupModel.user_id == user_id) .limit(1) ) return result.scalar_one_or_none() is not None except OperationalError as e: raise DatabaseConnectionError(f"Failed to connect to database: {str(e)}") except SQLAlchemyError as e: raise DatabaseQueryError(f"Failed to check group membership: {str(e)}") async def get_user_role_in_group(db: AsyncSession, group_id: int, user_id: int) -> Optional[UserRoleEnum]: """Gets the role of a user in a specific group.""" try: async with db.begin(): result = await db.execute( select(UserGroupModel.role) .where(UserGroupModel.group_id == group_id, UserGroupModel.user_id == user_id) ) return result.scalar_one_or_none() except OperationalError as e: raise DatabaseConnectionError(f"Failed to connect to database: {str(e)}") except SQLAlchemyError as e: raise DatabaseQueryError(f"Failed to query user role: {str(e)}") async def add_user_to_group(db: AsyncSession, group_id: int, user_id: int, role: UserRoleEnum = UserRoleEnum.member) -> Optional[UserGroupModel]: """Adds a user to a group if they aren't already a member.""" try: async with db.begin(): existing = await db.execute( select(UserGroupModel).where(UserGroupModel.group_id == group_id, UserGroupModel.user_id == user_id) ) if existing.scalar_one_or_none(): return None db_user_group = UserGroupModel(user_id=user_id, group_id=group_id, role=role) db.add(db_user_group) await db.flush() await db.refresh(db_user_group) return db_user_group except IntegrityError as e: raise DatabaseIntegrityError(f"Failed to add user to group: {str(e)}") except OperationalError as e: raise DatabaseConnectionError(f"Database connection error: {str(e)}") except SQLAlchemyError as e: raise DatabaseTransactionError(f"Failed to add user to group: {str(e)}") async def remove_user_from_group(db: AsyncSession, group_id: int, user_id: int) -> bool: """Removes a user from a group.""" try: async with db.begin(): result = await db.execute( delete(UserGroupModel) .where(UserGroupModel.group_id == group_id, UserGroupModel.user_id == user_id) .returning(UserGroupModel.id) ) return result.scalar_one_or_none() is not None except OperationalError as e: raise DatabaseConnectionError(f"Database connection error: {str(e)}") except SQLAlchemyError as e: raise DatabaseTransactionError(f"Failed to remove user from group: {str(e)}") async def get_group_member_count(db: AsyncSession, group_id: int) -> int: """Counts the number of members in a group.""" try: async with db.begin(): result = await db.execute( select(func.count(UserGroupModel.id)).where(UserGroupModel.group_id == group_id) ) return result.scalar_one() except OperationalError as e: raise DatabaseConnectionError(f"Failed to connect to database: {str(e)}") except SQLAlchemyError as e: raise DatabaseQueryError(f"Failed to count group members: {str(e)}")