"""Group management service.""" import logging from sqlalchemy import select from sqlalchemy.exc import IntegrityError from fabledassistant.models import async_session from fabledassistant.models.group import Group, GroupMembership from fabledassistant.models.user import User logger = logging.getLogger(__name__) async def create_group( user_id: int, name: str, description: str | None = None ) -> Group: async with async_session() as session: group = Group(name=name, description=description, created_by=user_id) session.add(group) await session.flush() session.add(GroupMembership(group_id=group.id, user_id=user_id, role="owner")) await session.commit() await session.refresh(group) return group async def list_groups(user_id: int) -> list[dict]: """All users see all groups with member count and their own membership status.""" async with async_session() as session: groups = (await session.execute(select(Group).order_by(Group.name))).scalars().all() user_group_ids = set( (await session.execute( select(GroupMembership.group_id).where(GroupMembership.user_id == user_id) )).scalars().all() ) result = [] for g in groups: count = len( (await session.execute( select(GroupMembership).where(GroupMembership.group_id == g.id) )).scalars().all() ) d = g.to_dict() d["member_count"] = count d["is_member"] = g.id in user_group_ids result.append(d) return result async def get_group(group_id: int) -> Group | None: async with async_session() as session: return await session.get(Group, group_id) async def _is_group_owner(session, acting_user_id: int, group_id: int) -> bool: m = (await session.execute( select(GroupMembership).where( GroupMembership.group_id == group_id, GroupMembership.user_id == acting_user_id, GroupMembership.role == "owner", ) )).scalar_one_or_none() return m is not None async def update_group( acting_user_id: int, group_id: int, is_site_admin: bool, **fields ) -> Group | None: async with async_session() as session: group = await session.get(Group, group_id) if not group: return None if not is_site_admin and not await _is_group_owner(session, acting_user_id, group_id): return None for k, v in fields.items(): if hasattr(group, k): setattr(group, k, v) await session.commit() await session.refresh(group) return group async def delete_group(acting_user_id: int, group_id: int, is_site_admin: bool) -> bool: async with async_session() as session: group = await session.get(Group, group_id) if not group: return False if not is_site_admin and group.created_by != acting_user_id: return False await session.delete(group) await session.commit() return True async def list_members(group_id: int) -> list[dict]: async with async_session() as session: rows = (await session.execute( select(GroupMembership, User) .join(User, User.id == GroupMembership.user_id) .where(GroupMembership.group_id == group_id) )).all() return [ {**m.to_dict(), "username": u.username, "email": u.email} for m, u in rows ] async def add_member( acting_user_id: int, group_id: int, target_user_id: int, role: str, is_site_admin: bool, ) -> GroupMembership | None: async with async_session() as session: if not is_site_admin and not await _is_group_owner(session, acting_user_id, group_id): return None membership = GroupMembership(group_id=group_id, user_id=target_user_id, role=role) session.add(membership) try: await session.commit() except IntegrityError: await session.rollback() return None await session.refresh(membership) return membership async def update_member_role( acting_user_id: int, group_id: int, target_user_id: int, role: str, is_site_admin: bool, ) -> GroupMembership | None: async with async_session() as session: if not is_site_admin and not await _is_group_owner(session, acting_user_id, group_id): return None m = (await session.execute( select(GroupMembership).where( GroupMembership.group_id == group_id, GroupMembership.user_id == target_user_id, ) )).scalar_one_or_none() if not m: return None m.role = role await session.commit() await session.refresh(m) return m async def remove_member( acting_user_id: int, group_id: int, target_user_id: int, is_site_admin: bool, ) -> bool: """Group owner, site admin, or self-removal are all permitted.""" async with async_session() as session: is_self = acting_user_id == target_user_id if not is_site_admin and not is_self: if not await _is_group_owner(session, acting_user_id, group_id): return False m = (await session.execute( select(GroupMembership).where( GroupMembership.group_id == group_id, GroupMembership.user_id == target_user_id, ) )).scalar_one_or_none() if not m: return False await session.delete(m) await session.commit() return True async def get_user_groups(user_id: int) -> list[Group]: async with async_session() as session: rows = (await session.execute( select(Group) .join(GroupMembership, GroupMembership.group_id == Group.id) .where(GroupMembership.user_id == user_id) )).scalars().all() return list(rows)