diff --git a/qfieldcloud_sdk/cli.py b/qfieldcloud_sdk/cli.py index ff9f675..1207f8e 100755 --- a/qfieldcloud_sdk/cli.py +++ b/qfieldcloud_sdk/cli.py @@ -736,3 +736,144 @@ def members_patch( log( f'Member "{membership["member"]}" changed role in organization "{membership["organization"]}" to role "{membership["role"]}".' ) + + +@cli.command(short_help="Get a list of organization teams.") +@click.argument("organization") +@click.pass_context +def teams_list(ctx: Context, organization: str) -> None: + """Get a list of organization teams.""" + teams_list = ctx.obj["client"].get_organization_teams(organization) + + if ctx.obj["format_json"]: + print_json(teams_list) + else: + log(f'Teams members in organization "{organization}":') + for object_team in teams_list: + log(f'{object_team["team"]}') + + +@cli.command(name="teams-create", short_help="Create an organization team.") +@click.argument("organization") +@click.argument("team_name") +@click.pass_context +def teams_create(ctx: Context, organization: str, team_name: str) -> None: + """Create a new team named TEAM_NAME in ORGANIZATION.""" + object_team = ctx.obj["client"].create_organization_team(organization, team_name) + + if ctx.obj["format_json"]: + print_json(object_team) + else: + log(f'Team "{object_team["team"]}" created in organization "{organization}".') + + +@cli.command(name="teams-get", short_help="Get a list of teams on an organization.") +@click.argument("organization") +@click.argument("team_name") +@click.pass_context +def teams_get(ctx: Context, organization: str, team_name: str) -> None: + """Get details of team TEAM_NAME in ORGANIZATION.""" + object_team = ctx.obj["client"].get_organization_team(organization, team_name) + + if ctx.obj["format_json"]: + print_json(object_team) + else: + log( + f'Team "{object_team["team"]}" in organization "{object_team["organization"]}":' + ) + log(f' Members: {", ".join(object_team["members"])}') + + +@cli.command(name="teams-patch", short_help="Rename an organization team.") +@click.argument("organization") +@click.argument("team_name") +@click.option("--name", "new_team_name") +@click.pass_context +def teams_patch( + ctx: Context, organization: str, team_name: str, new_team_name: str +) -> None: + """Rename team TEAM_NAME to NEW_TEAM_NAME in ORGANIZATION.""" + object_team = ctx.obj["client"].patch_organization_team( + organization, team_name, new_team_name + ) + + if ctx.obj["format_json"]: + print_json(object_team) + else: + log( + f'Team "{team_name}" in organization "{organization}" was renamed to "{object_team["team"]}".' + ) + + +@cli.command(name="teams-delete", short_help="Delete an organization team.") +@click.argument("organization") +@click.argument("team_name") +@click.pass_context +def teams_delete(ctx: Context, organization: str, team_name: str) -> None: + """Delete team TEAM_NAME from ORGANIZATION.""" + ctx.obj["client"].delete_organization_team(organization, team_name) + + if not ctx.obj["format_json"]: + log(f'Team "{team_name}" was deleted from organization "{organization}".') + + +@cli.command( + name="team-members-list", short_help="List members of an organization team." +) +@click.argument("organization") +@click.argument("team_name") +@click.pass_context +def team_members_list(ctx: Context, organization: str, team_name: str) -> None: + """List members of team TEAM_NAME in ORGANIZATION""" + members = ctx.obj["client"].get_organization_team_members(organization, team_name) + if ctx.obj["format_json"]: + print_json(members) + else: + log(f'Members of team "{team_name}" in organization "{organization}":') + + for object_member in members: + log(object_member["member"]) + + +@cli.command( + name="team-members-add", short_help="Add a member to an organization team." +) +@click.argument("organization") +@click.argument("team_name") +@click.argument("member_username") +@click.pass_context +def team_members_add( + ctx: Context, organization: str, team_name: str, member_username: str +) -> None: + """Add member MEMBER_USERNAME to team TEAM_NAME in ORGANIZATION.""" + object_member = ctx.obj["client"].add_organization_team_member( + organization, team_name, member_username + ) + + if ctx.obj["format_json"]: + print_json(object_member) + else: + log( + f'Member "{object_member["member"]}" added to team "{team_name}" in organization "{organization}".' + ) + + +@cli.command( + name="team-members-remove", short_help="Remove a member from an organization team." +) +@click.argument("organization") +@click.argument("team_name") +@click.argument("member_username") +@click.pass_context +def team_members_remove( + ctx: Context, organization: str, team_name: str, member_username: str +) -> None: + """Remove member MEMBER_USERNAME from team TEAM_NAME in ORGANIZATION.""" + ctx.obj["client"].remove_organization_team_member( + organization, team_name, member_username + ) + + if not ctx.obj["format_json"]: + log( + f'Member "{member_username}" removed from team "{team_name}" in organization "{organization}".' + ) diff --git a/qfieldcloud_sdk/sdk.py b/qfieldcloud_sdk/sdk.py index 5cc7aa8..b7579bd 100644 --- a/qfieldcloud_sdk/sdk.py +++ b/qfieldcloud_sdk/sdk.py @@ -154,6 +154,30 @@ class OrganizationMemberModel(TypedDict): # updated_at: datetime.datetime +class TeamModel(TypedDict): + """Represents the structure of a team within an organization in the QFieldCloud system. + + Attributes: + team: The team's identifier. + organization: The associated organization identifier. + members: A list of member identifiers belonging to the team. + """ + + team: str + organization: str + members: List[str] + + +class TeamMemberModel(TypedDict): + """Represents the structure of a team member in the QFieldCloud system. + + Attributes: + member: The member's identifier. + """ + + member: str + + class Pagination: """The Pagination class allows for controlling and managing pagination of results within the QFieldCloud SDK. @@ -1395,6 +1419,195 @@ def patch_organization_members( return member + def get_organization_teams(self, organization: str) -> List[TeamModel]: + """Gets a list of organization teams. + + Args: + organization: The name of the organization. + + Returns: + A list of TeamModel objects representing the organization's teams. + + Example: + ```python + teams = client.get_organization_teams(organization="My_Organization_Clan") + for team in teams: + print(team.team) + ``` + """ + teams = cast( + List[TeamModel], + self._request_json("GET", f"organizations/{organization}/teams/"), + ) + return teams + + def create_organization_team(self, organization: str, team_name: str) -> TeamModel: + """Creates a new organization team. + + Args: + organization: The name of the organization. + team_name: The name of the new team. + + Returns: + A TeamModel object representing the newly created team. + + Example: + ```python + client.create_organization_team(organization="My_Organization_Clan", team_name="surveyors") + ``` + """ + team = cast( + TeamModel, + self._request_json( + "POST", + f"organizations/{organization}/teams/", + {"team": team_name}, + ), + ) + return team + + def get_organization_team(self, organization: str, team_name: str) -> TeamModel: + """Gets an organization team by name. + + Args: + organization: The name of the organization. + team_name: The name of the team to retrieve. + + Returns: + A TeamModel object representing the requested team. + + Example: + ```python + client.get_organization_team(organization="My_Organization_Clan", team_name="surveyors") + ``` + """ + team = cast( + TeamModel, + self._request_json( + "GET", f"organizations/{organization}/teams/{team_name}/" + ), + ) + return team + + def patch_organization_team( + self, organization: str, team_name: str, new_team_name: str + ) -> TeamModel: + """Patches an organization team. + + Args: + organization: The name of the organization. + team_name: The current name of the team to modify. + new_team_name: The new name for the team. + + Returns: + A TeamModel object representing the updated team. + + Example: + ```python + client.patch_organization_team(organization="My_Organization_Clan", team_name="surveyors", new_team_name="field_surveyors") + ``` + """ + team = cast( + TeamModel, + self._request_json( + "PUT", + f"organizations/{organization}/teams/{team_name}/", + { + "team": new_team_name, + }, + ), + ) + return team + + def delete_organization_team(self, organization: str, team_name: str) -> None: + """Deletes an organization team. + + Args: + organization: The name of the organization. + team_name: The name of the team to delete. + + Example: + ```python + client.delete_organization_team(organization="My_Organization_Clan", team_name="field_surveyors") + ``` + """ + self._request("DELETE", f"organizations/{organization}/teams/{team_name}/") + + def get_organization_team_members( + self, organization: str, team_name: str + ) -> List[TeamMemberModel]: + """Gets a list of organization team members. + + Args: + organization: The name of the organization. + team_name: The name of the team. + + Returns: + A list of TeamMemberModel objects representing the team's members. + + Example: + ```python + members = client.get_organization_team_members(organization="My_Organization_Clan", team_name="admins") + for member in members: + print(member.member) + ``` + """ + members = cast( + List[TeamMemberModel], + self._request_json( + "GET", f"organizations/{organization}/teams/{team_name}/members/" + ), + ) + return members + + def add_organization_team_member( + self, organization: str, team_name: str, member_username: str + ) -> TeamMemberModel: + """Adds an organization team member. + + Args: + organization: The name of the organization. + team_name: The name of the team. + member_username: The username of the member to add. + + Returns: + A TeamMemberModel object representing the newly added team member. + + Example: + ```python + client.add_organization_team_member(organization="My_Organization_Clan", team_name="surveyors", member_username="surveyor_007") + ``` + """ + member = cast( + TeamMemberModel, + self._request_json( + "POST", + f"organizations/{organization}/teams/{team_name}/members/", + {"member": member_username}, + ), + ) + return member + + def remove_organization_team_member( + self, organization: str, team_name: str, member_username: str + ) -> None: + """Removes an organization team member. + + Args: + organization: The name of the organization. + team_name: The name of the team. + member_username: The username of the member to remove. + + Example: + ```python + client.delete_organization_team_member(organization="My_Organization_Clan", team_name="surveyors", member_username="surveyor_007") + ``` + """ + self._request( + "DELETE", + f"organizations/{organization}/teams/{team_name}/members/{member_username}/", + ) + def _request_json( self, method: str,