"""Tree membership management endpoints (owner-managed; members can list).""" import uuid from fastapi import APIRouter, status from app.api.deps import CurrentUser, SessionDep from app.schemas.membership import MemberAdd, MemberRoleUpdate, MembershipRead from app.services import membership_service, tree_service router = APIRouter(prefix="/trees", tags=["members"]) @router.get("/{tree_id}/members", response_model=list[MembershipRead]) async def list_members( tree_id: uuid.UUID, session: SessionDep, current: CurrentUser ) -> list[MembershipRead]: tree = await tree_service.get_tree(session, viewer_id=current.id, tree_id=tree_id) rows = await membership_service.list_members(session, viewer_id=current.id, tree=tree) return [MembershipRead(**r) for r in rows] @router.post( "/{tree_id}/members", response_model=MembershipRead, status_code=status.HTTP_201_CREATED ) async def add_member( tree_id: uuid.UUID, data: MemberAdd, session: SessionDep, current: CurrentUser ) -> MembershipRead: tree = await tree_service.get_tree(session, viewer_id=current.id, tree_id=tree_id) row = await membership_service.add_member( session, actor=current, tree=tree, email=data.email, role=data.role ) return MembershipRead(**row) @router.patch("/{tree_id}/members/{membership_id}", response_model=MembershipRead) async def update_member( tree_id: uuid.UUID, membership_id: uuid.UUID, data: MemberRoleUpdate, session: SessionDep, current: CurrentUser, ) -> MembershipRead: tree = await tree_service.get_tree(session, viewer_id=current.id, tree_id=tree_id) row = await membership_service.update_member_role( session, actor=current, tree=tree, membership_id=membership_id, role=data.role ) return MembershipRead(**row) @router.delete("/{tree_id}/members/{membership_id}", status_code=status.HTTP_204_NO_CONTENT) async def remove_member( tree_id: uuid.UUID, membership_id: uuid.UUID, session: SessionDep, current: CurrentUser, ) -> None: tree = await tree_service.get_tree(session, viewer_id=current.id, tree_id=tree_id) await membership_service.remove_member( session, actor=current, tree=tree, membership_id=membership_id )