diff --git a/Octokit.Reactive/Clients/IObservableTeamsClient.cs b/Octokit.Reactive/Clients/IObservableTeamsClient.cs index 95d382a9bd..745aa620e3 100644 --- a/Octokit.Reactive/Clients/IObservableTeamsClient.cs +++ b/Octokit.Reactive/Clients/IObservableTeamsClient.cs @@ -124,16 +124,54 @@ public interface IObservableTeamsClient /// Newly created IObservable Create(string org, NewTeam team); + /// + /// Updates a team + /// To edit a team, the authenticated user must either be an organization owner or a team maintainer + /// + /// + /// See the API documentation + /// for more information. + /// + /// updated for the current org + IObservable Update(string org, string teamSlug, UpdateTeam team); + /// /// Returns updated for the current org. + /// This endpoint route is deprecated and will be removed from the Teams API. + /// We recommend migrating your existing code to use the new Update a team endpoint. + /// /// + /// + /// See the API documentation + /// for more information. + /// /// Thrown when a general API error occurs. /// Updated IObservable Update(int id, UpdateTeam team); /// - /// Delete a team - must have owner permissions to this + /// To delete a team, the authenticated user must be an organization owner or team maintainer. + /// If you are an organization owner, deleting a parent team will delete all of its child teams as well. /// + /// + /// See the API documentation + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// Thrown when a general API error occurs. + /// + IObservable Delete(string org, string teamSlug); + + /// + /// Delete a team - must have owner permissions to do this + /// This endpoint route is deprecated and will be removed from the Teams API. + /// We recommend migrating your existing code to use the new Delete a team endpoint. + /// . + /// + /// + /// See the API documentation + /// + /// The unique identifier of the team. /// Thrown when a general API error occurs. /// IObservable Delete(int id); @@ -257,5 +295,72 @@ public interface IObservableTeamsClient /// Options to change API behaviour. /// IObservable GetAllPendingInvitations(int id, ApiOptions options); + + /// + /// Checks whether a team has admin, push, maintain, triage, or pull permission for a repository. + /// Repositories inherited through a parent team will also be checked. + /// + /// + /// See the API Documentation + /// for more information. + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// The account owner of the repository. The name is not case sensitive. + /// The name of the repository. The name is not case sensitive. + /// + IObservable CheckTeamPermissionsForARepository(string org, string teamSlug, string owner, string repo); + + /// + /// Checks whether a team has admin, push, maintain, triage, or pull permission for a repository. + /// Repositories inherited through a parent team will also be checked. + /// + /// + /// See the API Documentation + /// for more information. + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// The account owner of the repository. The name is not case sensitive. + /// The name of the repository. The name is not case sensitive. + /// Thrown when a general API error occurs. + /// + IObservable CheckTeamPermissionsForARepositoryWithCustomAcceptHeader(string org, string teamSlug, string owner, string repo); + + /// + /// Add or update team repository permissions + /// + /// + /// See the API Documentation + /// for more information. + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// The account owner of the repository. The name is not case sensitive. + /// The name of the repository. The name is not case sensitive. + /// + /// The permission to grant the team on this repository. We accept the following permissions to be set: + /// pull, triage, push, maintain, admin and you can also specify a custom repository role name, if the + /// owning organization has defined any. If no permission is specified, the team's permission attribute + /// will be used to determine what permission to grant the team on this repository + /// + /// Thrown when a general API error occurs. + /// + IObservable AddOrUpdateTeamRepositoryPermissions(string org, string teamSlug, string owner, string repo, string permission); + + /// + /// Remove a repository from a team + /// + /// + /// See the API Documentation + /// for more information. + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// The account owner of the repository. The name is not case sensitive. + /// The name of the repository. The name is not case sensitive. + /// Thrown when a general API error occurs. + /// + IObservable RemoveRepositoryFromATeam(string org, string teamSlug, string owner, string repo); } } diff --git a/Octokit.Reactive/Clients/ObservableTeamsClient.cs b/Octokit.Reactive/Clients/ObservableTeamsClient.cs index 9fa0d3f7ba..f068d468bb 100644 --- a/Octokit.Reactive/Clients/ObservableTeamsClient.cs +++ b/Octokit.Reactive/Clients/ObservableTeamsClient.cs @@ -193,9 +193,34 @@ public IObservable Create(string org, NewTeam team) return _client.Create(org, team).ToObservable(); } + /// + /// Updates a team + /// To edit a team, the authenticated user must either be an organization owner or a team maintainer + /// + /// + /// See the API documentation + /// for more information. + /// + /// updated for the current org + public IObservable Update(string org, string teamSlug, UpdateTeam team) + { + Ensure.ArgumentNotNull(org, nameof(org)); + Ensure.ArgumentNotNull(teamSlug, nameof(teamSlug)); + Ensure.ArgumentNotNull(team, nameof(team)); + + return _client.Update(org, teamSlug, team).ToObservable(); + } + /// /// Returns updated for the current org. + /// This endpoint route is deprecated and will be removed from the Teams API. + /// We recommend migrating your existing code to use the new Update a team endpoint. + /// . /// + /// + /// See the API documentation + /// for more information. + /// /// Thrown when a general API error occurs. /// Updated public IObservable Update(int id, UpdateTeam team) @@ -206,8 +231,34 @@ public IObservable Update(int id, UpdateTeam team) } /// - /// Delete a team - must have owner permissions to this + /// To delete a team, the authenticated user must be an organization owner or team maintainer. + /// If you are an organization owner, deleting a parent team will delete all of its child teams as well. + /// + /// + /// See the API documentation + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// Thrown when a general API error occurs. + /// + public IObservable Delete(string org, string teamSlug) + { + Ensure.ArgumentNotNull(org, nameof(org)); + Ensure.ArgumentNotNull(teamSlug, nameof(teamSlug)); + + return _client.Delete(org, teamSlug).ToObservable(); + } + + /// + /// Delete a team - must have owner permissions to do this + /// This endpoint route is deprecated and will be removed from the Teams API. + /// We recommend migrating your existing code to use the new Delete a team endpoint. + /// . /// + /// + /// See the API documentation + /// + /// The unique identifier of the team. /// Thrown when a general API error occurs. /// public IObservable Delete(int id) @@ -391,5 +442,86 @@ public IObservable GetAllPendingInvitations(in return _connection.GetAndFlattenAllPages(ApiUrls.TeamPendingInvitations(id), null, options); } + + /// + /// Checks whether a team has admin, push, maintain, triage, or pull permission for a repository. + /// Repositories inherited through a parent team will also be checked. + /// + /// + /// See the API Documentation + /// for more information. + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// The account owner of the repository. The name is not case sensitive. + /// The name of the repository. The name is not case sensitive. + /// + public IObservable CheckTeamPermissionsForARepository(string org, string teamSlug, string owner, string repo) + { + return _client.CheckTeamPermissionsForARepository(org, teamSlug, owner, repo).ToObservable(); + } + + /// + /// Checks whether a team has admin, push, maintain, triage, or pull permission for a repository. + /// Repositories inherited through a parent team will also be checked. + /// + /// + /// See the API Documentation + /// for more information. + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// The account owner of the repository. The name is not case sensitive. + /// The name of the repository. The name is not case sensitive. + /// Thrown when a general API error occurs. + /// + public IObservable CheckTeamPermissionsForARepositoryWithCustomAcceptHeader(string org, string teamSlug, string owner, string repo) + { + return _client.CheckTeamPermissionsForARepositoryWithCustomAcceptHeader(org, teamSlug, owner, repo).ToObservable(); + } + + /// + /// Add or update team repository permissions + /// + /// + /// See the API Documentation + /// for more information. + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// The account owner of the repository. The name is not case sensitive. + /// The name of the repository. The name is not case sensitive. + /// + /// The permission to grant the team on this repository. We accept the following permissions to be set: + /// pull, triage, push, maintain, admin and you can also specify a custom repository role name, if the + /// owning organization has defined any. If no permission is specified, the team's permission attribute + /// will be used to determine what permission to grant the team on this repository + /// + /// Thrown when a general API error occurs. + /// + [ManualRoute("PUT", "/orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}")] + public IObservable AddOrUpdateTeamRepositoryPermissions(string org, string teamSlug, string owner, string repo, string permission) + { + return _client.AddOrUpdateTeamRepositoryPermissions(org, teamSlug, owner, repo, permission).ToObservable(); + } + + /// + /// Remove a repository from a team + /// + /// + /// See the API Documentation + /// for more information. + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// The account owner of the repository. The name is not case sensitive. + /// The name of the repository. The name is not case sensitive. + /// Thrown when a general API error occurs. + /// + [ManualRoute("DELETE", "/orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}")] + public IObservable RemoveRepositoryFromATeam(string org, string teamSlug, string owner, string repo) + { + return _client.RemoveRepositoryFromATeam(org, teamSlug, owner, repo).ToObservable(); + } } } diff --git a/Octokit.Tests.Conventions/PreviewsTests.cs b/Octokit.Tests.Conventions/PreviewsTests.cs index 8fd06610c2..6a0fdfa6e4 100644 --- a/Octokit.Tests.Conventions/PreviewsTests.cs +++ b/Octokit.Tests.Conventions/PreviewsTests.cs @@ -70,7 +70,8 @@ public void NoStalePreviews() // https://developer.github.com/v3/repos/commits/#get-a-single-commit "application/vnd.github.v3.sha", // https://developer.github.com/v3/activity/starring/#alternative-response-with-star-creation-timestamps - "application/vnd.github.v3.star+json" + "application/vnd.github.v3.star+json", + "application/vnd.github.v3.repository+json" }; var validHeaders = defaultHeaders.Concat(previewAcceptHeaders); diff --git a/Octokit.Tests.Integration/Clients/TeamsClientTests.cs b/Octokit.Tests.Integration/Clients/TeamsClientTests.cs index ac9260270c..0735cc2f60 100644 --- a/Octokit.Tests.Integration/Clients/TeamsClientTests.cs +++ b/Octokit.Tests.Integration/Clients/TeamsClientTests.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using System.Net; using System.Threading.Tasks; using Octokit; using Octokit.Tests.Integration; @@ -33,10 +32,12 @@ public async Task CreatesTeam() Assert.Equal(teamName, team.Name); Assert.Equal(teamDescription, team.Description); Assert.Equal(TeamPrivacy.Closed, team.Privacy); + // Permission defaults to pull when no permission is specified when creating a team + Assert.Equal("pull", team.Permission); Assert.Equal(1, team.MembersCount); Assert.Equal(1, team.ReposCount); - await github.Organization.Team.Delete(team.Id); + await github.Organization.Team.Delete(Helper.Organization, team.Slug); } } } @@ -445,10 +446,49 @@ public async Task UpdatesTeam() { var teamName = Helper.MakeNameWithTimestamp("updated-team"); var teamDescription = Helper.MakeNameWithTimestamp("updated description"); + var update = new UpdateTeam(teamName) { Description = teamDescription, Privacy = TeamPrivacy.Closed, + Permission = TeamPermission.Push, + ParentTeamId = parentTeamContext.TeamId + }; + + var team = await _github.Organization.Team.Update(Helper.Organization, teamContext.Team.Slug, update); + + Assert.Equal(teamName, team.Name); + Assert.Equal(teamDescription, team.Description); + Assert.Equal(TeamPrivacy.Closed, team.Privacy); + Assert.Equal("push", team.Permission); + Assert.Equal(parentTeamContext.TeamId, team.Parent.Id); + } + } + } + + public class TheUpdateLegacyMethod + { + private readonly IGitHubClient _github; + + public TheUpdateLegacyMethod() + { + _github = Helper.GetAuthenticatedClient(); + } + + [OrganizationTest] + public async Task UpdatesTeamLegacy() + { + using (var parentTeamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("parent-team")))) + using (var teamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("team-fixture")))) + { + var teamName = Helper.MakeNameWithTimestamp("updated-team"); + var teamDescription = Helper.MakeNameWithTimestamp("updated description"); + + var update = new UpdateTeam(teamName) + { + Description = teamDescription, + Privacy = TeamPrivacy.Closed, + Permission = TeamPermission.Push, ParentTeamId = parentTeamContext.TeamId }; @@ -457,8 +497,189 @@ public async Task UpdatesTeam() Assert.Equal(teamName, team.Name); Assert.Equal(teamDescription, team.Description); Assert.Equal(TeamPrivacy.Closed, team.Privacy); + Assert.Equal("push", team.Permission); Assert.Equal(parentTeamContext.TeamId, team.Parent.Id); } } } + + public class TheCheckTeamPermissionsForARepositoryMethod + { + private readonly IGitHubClient github; + + public TheCheckTeamPermissionsForARepositoryMethod() + { + github = Helper.GetAuthenticatedClient(); + } + + [OrganizationTest] + public async Task ChecksTeamPermissions() + { + using (var teamContext = await github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("team")))) + using (var repositoryContext = await github.CreateOrganizationRepositoryContext(Helper.Organization, new NewRepository(Helper.MakeNameWithTimestamp("teamrepo")))) + { + github.Organization.Team.AddRepository(teamContext.TeamId, Helper.Organization, repositoryContext.RepositoryName); + + var teamPermissionResponse = await github.Organization.Team.CheckTeamPermissionsForARepository( + Helper.Organization, + teamContext.Team.Slug, + repositoryContext.RepositoryOwner, + repositoryContext.RepositoryName); + + Assert.True(teamPermissionResponse); + } + } + + [OrganizationTest] + public async Task ChecksTeamPermissionsReturnsFalseOnNonTeamRepository() + { + using (var teamContext = await github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("team")))) + using (var repositoryContext = await github.CreateOrganizationRepositoryContext(Helper.Organization, new NewRepository(Helper.MakeNameWithTimestamp("teamrepo")))) + { + var response = await github.Organization.Team.CheckTeamPermissionsForARepository( + Helper.Organization, + teamContext.Team.Slug, + repositoryContext.RepositoryOwner, + repositoryContext.RepositoryName); + + Assert.False(response); + } + } + } + + public class TheCheckTeamPermissionsForARepositoryWithCustomAcceptHeaderMethod + { + private readonly IGitHubClient github; + + public TheCheckTeamPermissionsForARepositoryWithCustomAcceptHeaderMethod() + { + github = Helper.GetAuthenticatedClient(); + } + + [OrganizationTest] + public async Task ChecksTeamPermissionsWithRepositoryMediaTypeInAccepts() + { + using (var teamContext = await github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("team")))) + using (var repositoryContext = await github.CreateOrganizationRepositoryContext(Helper.Organization, new NewRepository(Helper.MakeNameWithTimestamp("teamrepo")))) + { + github.Organization.Team.AddRepository(teamContext.TeamId, Helper.Organization, repositoryContext.RepositoryName); + + var teamPermission = await github.Organization.Team.CheckTeamPermissionsForARepositoryWithCustomAcceptHeader( + Helper.Organization, + teamContext.Team.Slug, + repositoryContext.RepositoryOwner, + repositoryContext.RepositoryName); + + Assert.NotNull(teamPermission); + Assert.NotNull(teamPermission.Permissions); + Assert.True(teamPermission.Permissions.Pull); + } + } + + [OrganizationTest] + public async Task ChecksTeamPermissionsThrowsNotFoundException() + { + using (var teamContext = await github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("team")))) + using (var repositoryContext = await github.CreateOrganizationRepositoryContext(Helper.Organization, new NewRepository(Helper.MakeNameWithTimestamp("teamrepo")))) + { + await Assert.ThrowsAsync(async () => + await github.Organization.Team.CheckTeamPermissionsForARepositoryWithCustomAcceptHeader( + Helper.Organization, + teamContext.Team.Slug, + repositoryContext.RepositoryOwner, + repositoryContext.RepositoryName)); + } + } + } + + public class TheAddOrUpdateTeamRepositoryPermissionsMethod + { + [OrganizationTest] + public async Task AddsTeamRepositoryPermissions() + { + var github = Helper.GetAuthenticatedClient(); + + using (var teamContext = await github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("team")))) + using (var repoContext = await github.CreateOrganizationRepositoryContext(Helper.Organization, new NewRepository(Helper.MakeNameWithTimestamp("team-repository")))) + { + var teamRepositories = await github.Organization.Team.GetAllRepositories(teamContext.TeamId); + + Assert.Equal(0, teamRepositories.Count); + + await github.Organization.Team.AddOrUpdateTeamRepositoryPermissions( + Helper.Organization, + teamContext.Team.Slug, + repoContext.RepositoryOwner, + repoContext.RepositoryName, + "admin"); + + teamRepositories = await github.Organization.Team.GetAllRepositories(teamContext.TeamId); + + Assert.True(teamRepositories.First(x => x.Id == repoContext.RepositoryId).Permissions.Admin); + } + } + + [OrganizationTest] + public async Task UpdatesTeamRepositoryPermissions() + { + var github = Helper.GetAuthenticatedClient(); + + using (var teamContext = await github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("team")))) + using (var repoContext = await github.CreateOrganizationRepositoryContext(Helper.Organization, new NewRepository(Helper.MakeNameWithTimestamp("team-repository")))) + { + await github.Organization.Team.AddOrUpdateTeamRepositoryPermissions( + Helper.Organization, + teamContext.Team.Slug, + repoContext.RepositoryOwner, + repoContext.RepositoryName, + "admin"); + + var teamRepositories = await github.Organization.Team.GetAllRepositories(teamContext.TeamId); + + Assert.True(teamRepositories.First(x => x.Id == repoContext.RepositoryId).Permissions.Admin); + + await github.Organization.Team.AddOrUpdateTeamRepositoryPermissions( + Helper.Organization, + teamContext.Team.Slug, + repoContext.RepositoryOwner, + repoContext.RepositoryName, + "maintain"); + + teamRepositories = await github.Organization.Team.GetAllRepositories(teamContext.TeamId); + + Assert.False(teamRepositories.First(x => x.Id == repoContext.RepositoryId).Permissions.Admin); + Assert.True(teamRepositories.First(x => x.Id == repoContext.RepositoryId).Permissions.Maintain); + } + } + } + + public class TheRemoveRepositoryFromATeamMethod + { + [OrganizationTest] + public async Task RemovesRepositoryFromATeam() + { + var github = Helper.GetAuthenticatedClient(); + + using (var teamContext = await github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("team")))) + using (var repoContext = await github.CreateOrganizationRepositoryContext(Helper.Organization, new NewRepository(Helper.MakeNameWithTimestamp("team-repository")))) + { + await github.Organization.Team.AddOrUpdateTeamRepositoryPermissions( + Helper.Organization, + teamContext.Team.Slug, + repoContext.RepositoryOwner, + repoContext.RepositoryName, + "admin"); + + await github.Organization.Team.RemoveRepositoryFromATeam( + Helper.Organization, + teamContext.Team.Slug, + repoContext.RepositoryOwner, + repoContext.RepositoryName); + + var addedRepo = await github.Organization.Team.GetAllRepositories(teamContext.TeamId); + + Assert.Equal(0, addedRepo.Count); + } + } + } } diff --git a/Octokit.Tests.Integration/Helper.cs b/Octokit.Tests.Integration/Helper.cs index 49ca013140..9c94d78f64 100644 --- a/Octokit.Tests.Integration/Helper.cs +++ b/Octokit.Tests.Integration/Helper.cs @@ -190,15 +190,15 @@ public static void DeleteRepo(IConnection connection, string owner, string name) public static void DeleteTeam(IConnection connection, Team team) { if (team != null) - DeleteTeam(connection, team.Id); + DeleteTeam(connection, team.Slug); } - public static void DeleteTeam(IConnection connection, int teamId) + public static void DeleteTeam(IConnection connection, string slug) { try { var client = new GitHubClient(connection); - client.Organization.Team.Delete(teamId).Wait(TimeSpan.FromSeconds(15)); + client.Organization.Team.Delete(Organization, slug).Wait(TimeSpan.FromSeconds(15)); } catch { } } diff --git a/Octokit.Tests.Integration/Helpers/ObservableGithubClientExtensions.cs b/Octokit.Tests.Integration/Helpers/ObservableGithubClientExtensions.cs index 5c52a61246..506f27cca7 100644 --- a/Octokit.Tests.Integration/Helpers/ObservableGithubClientExtensions.cs +++ b/Octokit.Tests.Integration/Helpers/ObservableGithubClientExtensions.cs @@ -36,6 +36,13 @@ internal static async Task CreateTeamContext(this IObservableGitHub return new TeamContext(client.Connection, team); } + internal static async Task CreateOrganizationRepositoryContext(this IObservableGitHubClient client, string organizationLogin, NewRepository newRepository) + { + var repo = await client.Repository.Create(organizationLogin, newRepository); + + return new RepositoryContext(client.Connection, repo); + } + internal static async Task CreateEnterpriseUserContext(this IObservableGitHubClient client, NewUser newUser) { var user = await client.User.Administration.Create(newUser); diff --git a/Octokit.Tests.Integration/Helpers/TeamContext.cs b/Octokit.Tests.Integration/Helpers/TeamContext.cs index c04ceb2774..f4cc15a26b 100644 --- a/Octokit.Tests.Integration/Helpers/TeamContext.cs +++ b/Octokit.Tests.Integration/Helpers/TeamContext.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Octokit.Tests.Integration.Helpers { diff --git a/Octokit.Tests.Integration/Reactive/ObservableTeamsClientTests.cs b/Octokit.Tests.Integration/Reactive/ObservableTeamsClientTests.cs index 87431a8f56..a818b20c04 100644 --- a/Octokit.Tests.Integration/Reactive/ObservableTeamsClientTests.cs +++ b/Octokit.Tests.Integration/Reactive/ObservableTeamsClientTests.cs @@ -386,12 +386,239 @@ public async Task UpdatesTeam() ParentTeamId = parentTeamContext.TeamId }; + var team = await _github.Organization.Team.Update(Helper.Organization, teamContext.Team.Slug, update); + + Assert.Equal(teamName, team.Name); + Assert.Equal(teamDescription, team.Description); + Assert.Equal(TeamPrivacy.Closed, team.Privacy); + Assert.Equal(parentTeamContext.TeamId, team.Parent.Id); + + _github.Organization.Team.Delete(Helper.Organization, team.Slug); + } + } + } + + public class TheUpdateLegacyMethod + { + private readonly IObservableGitHubClient _github; + + public TheUpdateLegacyMethod() + { + _github = new ObservableGitHubClient(Helper.GetAuthenticatedClient()); + } + + [OrganizationTest] + public async Task UpdatesTeamLegacy() + { + using (var parentTeamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("parent-team")))) + using (var teamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("team-fixture")))) + { + var teamName = Helper.MakeNameWithTimestamp("updated-team"); + var teamDescription = Helper.MakeNameWithTimestamp("updated description"); + var update = new UpdateTeam(teamName) + { + Description = teamDescription, + Privacy = TeamPrivacy.Closed, + ParentTeamId = parentTeamContext.TeamId + }; + var team = await _github.Organization.Team.Update(teamContext.TeamId, update); Assert.Equal(teamName, team.Name); Assert.Equal(teamDescription, team.Description); Assert.Equal(TeamPrivacy.Closed, team.Privacy); Assert.Equal(parentTeamContext.TeamId, team.Parent.Id); + + _github.Organization.Team.Delete(teamContext.TeamId); + } + } + } + + public class TheCheckTeamPermissionsForARepositoryMethod + { + private readonly IObservableGitHubClient _github; + public TheCheckTeamPermissionsForARepositoryMethod() + { + _github = new ObservableGitHubClient(Helper.GetAuthenticatedClient()); + + } + + [OrganizationTest] + public async Task ChecksTeamPermissions() + { + using (var teamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("team")))) + using (var repositoryContext = await _github.CreateOrganizationRepositoryContext(Helper.Organization, new NewRepository(Helper.MakeNameWithTimestamp("teamrepo")))) + { + _github.Organization.Team.AddRepository(teamContext.TeamId, Helper.Organization, repositoryContext.RepositoryName); + + var teamPermissionResponse = await _github.Organization.Team.CheckTeamPermissionsForARepository( + Helper.Organization, + teamContext.Team.Slug, + repositoryContext.RepositoryOwner, + repositoryContext.RepositoryName); + + Assert.True(teamPermissionResponse); + } + } + + [OrganizationTest] + public async Task ChecksTeamPermissionsReturnsFalseOnNonTeamRepository() + { + using (var teamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("team")))) + using (var repositoryContext = await _github.CreateOrganizationRepositoryContext(Helper.Organization, new NewRepository(Helper.MakeNameWithTimestamp("teamrepo")))) + { + var response = await _github.Organization.Team.CheckTeamPermissionsForARepository( + Helper.Organization, + teamContext.Team.Slug, + repositoryContext.RepositoryOwner, + repositoryContext.RepositoryName); + + Assert.False(response); + } + } + } + + public class TheCheckTeamPermissionsForARepositoryWithCustomAcceptHeaderMethod + { + private readonly IObservableGitHubClient _github; + public TheCheckTeamPermissionsForARepositoryWithCustomAcceptHeaderMethod() + { + _github = new ObservableGitHubClient(Helper.GetAuthenticatedClient()); + + } + + [OrganizationTest] + public async Task ChecksTeamPermissions() + { + using (var teamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("team")))) + using (var repositoryContext = await _github.CreateOrganizationRepositoryContext(Helper.Organization, new NewRepository(Helper.MakeNameWithTimestamp("teamrepo")))) + { + _github.Organization.Team.AddRepository(teamContext.TeamId, Helper.Organization, repositoryContext.RepositoryName); + + var teamPermissionResponse = await _github.Organization.Team.CheckTeamPermissionsForARepositoryWithCustomAcceptHeader( + Helper.Organization, + teamContext.Team.Slug, + repositoryContext.RepositoryOwner, + repositoryContext.RepositoryName); + + Assert.NotNull(teamPermissionResponse); + } + } + + [OrganizationTest] + public async Task ChecksTeamPermissionsThrowsNotFoundException() + { + using (var teamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("team")))) + using (var repositoryContext = await _github.CreateOrganizationRepositoryContext(Helper.Organization, new NewRepository(Helper.MakeNameWithTimestamp("teamrepo")))) + { + await Assert.ThrowsAsync(async () => + await _github.Organization.Team.CheckTeamPermissionsForARepositoryWithCustomAcceptHeader( + Helper.Organization, + teamContext.Team.Slug, + repositoryContext.RepositoryOwner, + repositoryContext.RepositoryName)); + } + } + } + + public class TheAddOrUpdateTeamRepositoryPermissionsMethod + { + private readonly IObservableGitHubClient _github; + + public TheAddOrUpdateTeamRepositoryPermissionsMethod() + { + _github = new ObservableGitHubClient(Helper.GetAuthenticatedClient()); + } + + [OrganizationTest] + public async Task AddsTeamRepositoryPermissions() + { + using (var teamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("team")))) + using (var repoContext = await _github.CreateOrganizationRepositoryContext(Helper.Organization, new NewRepository(Helper.MakeNameWithTimestamp("team-repository")))) + { + var teamRepository = await _github.Organization.Team + .GetAllRepositories(teamContext.TeamId) + .FirstOrDefaultAsync(x => x.Id == repoContext.RepositoryId); + + Assert.Null(teamRepository); + + await _github.Organization.Team.AddOrUpdateTeamRepositoryPermissions( + Helper.Organization, + teamContext.Team.Slug, + repoContext.RepositoryOwner, + repoContext.RepositoryName, + "admin"); + + teamRepository = await _github.Organization.Team + .GetAllRepositories(teamContext.TeamId) + .FirstOrDefaultAsync(x => x.Id == repoContext.RepositoryId); + + Assert.NotNull(teamRepository); + } + } + + [OrganizationTest] + public async Task UpdatesTeamRepositoryPermissions() + { + using (var teamContext = await _github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("team")))) + using (var repoContext = await _github.CreateOrganizationRepositoryContext(Helper.Organization, new NewRepository(Helper.MakeNameWithTimestamp("team-repository")))) + { + await _github.Organization.Team.AddOrUpdateTeamRepositoryPermissions( + Helper.Organization, + teamContext.Team.Slug, + repoContext.RepositoryOwner, + repoContext.RepositoryName, + "admin"); + + var teamRepository = await _github.Organization.Team + .GetAllRepositories(teamContext.TeamId) + .FirstOrDefaultAsync(x => x.Id == repoContext.RepositoryId); + + Assert.True(teamRepository.Permissions.Admin); + + await _github.Organization.Team.AddOrUpdateTeamRepositoryPermissions( + Helper.Organization, + teamContext.Team.Slug, + repoContext.RepositoryOwner, + repoContext.RepositoryName, + "maintain"); + + teamRepository = await _github.Organization.Team + .GetAllRepositories(teamContext.TeamId) + .FirstOrDefaultAsync(x => x.Id == repoContext.RepositoryId); + + Assert.True(teamRepository.Permissions.Maintain); + Assert.False(teamRepository.Permissions.Admin); + } + } + } + + public class TheRemoveRepositoryFromATeamMethod + { + [OrganizationTest] + public async Task RemovesRepositoryFromATeam() + { + var github = new ObservableGitHubClient(Helper.GetAuthenticatedClient()); + + using (var teamContext = await github.CreateTeamContext(Helper.Organization, new NewTeam(Helper.MakeNameWithTimestamp("team")))) + using (var repoContext = await github.CreateOrganizationRepositoryContext(Helper.Organization, new NewRepository(Helper.MakeNameWithTimestamp("team-repository")))) + { + await github.Organization.Team.AddOrUpdateTeamRepositoryPermissions( + Helper.Organization, + teamContext.Team.Slug, + repoContext.RepositoryOwner, + repoContext.RepositoryName, + "admin"); + + await github.Organization.Team.RemoveRepositoryFromATeam( + Helper.Organization, + teamContext.Team.Slug, + repoContext.RepositoryOwner, + repoContext.RepositoryName); + + var addedRepo = await github.Organization.Team.GetAllRepositories(teamContext.TeamId).ToList(); + + Assert.Equal(0, addedRepo.Count); } } } diff --git a/Octokit.Tests/Clients/TeamsClientTests.cs b/Octokit.Tests/Clients/TeamsClientTests.cs index a1b7179405..b29e921c07 100644 --- a/Octokit.Tests/Clients/TeamsClientTests.cs +++ b/Octokit.Tests/Clients/TeamsClientTests.cs @@ -169,6 +169,36 @@ public void RequestsTheCorrectUrl() var client = new TeamsClient(connection); var team = new UpdateTeam("Octokittens"); + var org = "org"; + var slug = "slug"; + client.Update(org, slug , team); + + connection.Received().Patch( + Arg.Is(u => u.ToString() == "orgs/org/teams/slug"), + team); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + + await Assert.ThrowsAsync(() => client.Update(null, "b", new UpdateTeam("update-team"))); + await Assert.ThrowsAsync(() => client.Update("a", null, new UpdateTeam("update-team"))); + await Assert.ThrowsAsync(() => client.Update("a", "b", null)); + } + } + + public class TheUpdateTeamLegacyMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + var team = new UpdateTeam("Octokittens"); + client.Update(1, team); connection.Received().Patch( @@ -187,6 +217,34 @@ public async Task EnsuresNonNullArguments() } public class TheDeleteTeamMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + + var org = "org"; + var slug = "slug"; + + client.Delete(org, slug); + + connection.Received().Delete( + Arg.Is(u => u.ToString() == "orgs/org/teams/slug")); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + + await Assert.ThrowsAsync(() => client.Delete("a", null)); + await Assert.ThrowsAsync(() => client.Delete(null, "a")); + } + } + + public class TheDeleteTeamLegacyMethod { [Fact] public void RequestsTheCorrectUrl() @@ -413,5 +471,152 @@ public async Task RequestsTheCorrectUrl() Args.ApiOptions); } } + + public class TheCheckTeamPermissionsForARepositoryMethod + { + [Fact] + public async Task EnsuresNonNullOrEmptyArguments() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + + await Assert.ThrowsAsync(() => client.CheckTeamPermissionsForARepository(null, "teamSlug", "owner", "repo")); + await Assert.ThrowsAsync(() => client.CheckTeamPermissionsForARepository("org", null, "owner", "repo")); + await Assert.ThrowsAsync(() => client.CheckTeamPermissionsForARepository("org", "teamSlug", null, "repo")); + await Assert.ThrowsAsync(() => client.CheckTeamPermissionsForARepository("org", "teamSlug", "owner", null)); + } + + [Fact] + public async Task RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + + await client.CheckTeamPermissionsForARepository("org", "teamSlug", "owner", "repo"); + + var expected = "/orgs/org/teams/teamSlug/repos/owner/repo"; + + connection.Received().Get(Arg.Is(u => u.ToString() == expected)); + } + } + + public class TheCheckTeamPermissionsForARepositoryWithCustomAcceptHeaderMethod + { + [Fact] + public async Task EnsuresNonNullOrEmptyArguments() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + + await Assert.ThrowsAsync(() => + client.CheckTeamPermissionsForARepositoryWithCustomAcceptHeader(null, "teamSlug", "owner", "repo")); + await Assert.ThrowsAsync(() => + client.CheckTeamPermissionsForARepositoryWithCustomAcceptHeader("org", null, "owner", "repo")); + await Assert.ThrowsAsync(() => + client.CheckTeamPermissionsForARepositoryWithCustomAcceptHeader("org", "teamSlug", null, "repo")); + await Assert.ThrowsAsync(() => + client.CheckTeamPermissionsForARepositoryWithCustomAcceptHeader("org", "teamSlug", "owner", null)); + } + + [Fact] + public async Task RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + + await client.CheckTeamPermissionsForARepositoryWithCustomAcceptHeader("org", "teamSlug", "owner", "repo"); + + var expected = "/orgs/org/teams/teamSlug/repos/owner/repo"; + + connection.Received().Get( + Arg.Is(u => u.ToString() == expected), + null, + Arg.Is(s => s.Equals("application/vnd.github.v3.repository+json"))); + } + } + + public class TheAddOrUpdateTeamRepositoryPermissionsMethod + { + [Fact] + public async Task EnsuresNonNullOrEmptyArguments() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + + await Assert.ThrowsAsync(() => client.AddOrUpdateTeamRepositoryPermissions(null, "teamSlug", "owner", "repo", "permission")); + await Assert.ThrowsAsync(() => client.AddOrUpdateTeamRepositoryPermissions("org", null, "owner", "repo", "permission")); + await Assert.ThrowsAsync(() => client.AddOrUpdateTeamRepositoryPermissions("org", "teamSlug", null, "repo", "permission")); + await Assert.ThrowsAsync(() => client.AddOrUpdateTeamRepositoryPermissions("org", "teamSlug", "owner", null, "permission")); + } + + [Fact] + public async Task EnsuresNullPermissionValueDoesNotThrow() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + var exception = await Record.ExceptionAsync(() => client.AddOrUpdateTeamRepositoryPermissions("org", "teamSlug", "owner", "repo", null)); + + Assert.Null(exception); + } + + + [Fact] + public async Task RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + var permission = "a"; + + await client.AddOrUpdateTeamRepositoryPermissions("org", "teamSlug", "owner", "repo", permission); + + var expected = "/orgs/org/teams/teamSlug/repos/owner/repo"; + + connection.Received().Put( + Arg.Is(u => u.ToString() == expected), + Arg.Any()); + } + + [Fact] + public async Task PassesTheCorrestPermission() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + var permission = "a"; + + await client.AddOrUpdateTeamRepositoryPermissions("org", "teamSlug", "owner", "repo", permission); + + connection.Received().Put( + Arg.Any(), + Arg.Is(o => o.GetType().GetProperty("permission").GetValue(o).ToString() == "a")); + } + } + + public class TheRemoveRepositoryFromATeamMethod + { + [Fact] + public async Task EnsuresNonNullOrEmptyArguments() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + + await Assert.ThrowsAsync(() => client.RemoveRepositoryFromATeam(null, "teamSlug", "owner", "repo")); + await Assert.ThrowsAsync(() => client.RemoveRepositoryFromATeam("org", null, "owner", "repo")); + await Assert.ThrowsAsync(() => client.RemoveRepositoryFromATeam("org", "teamSlug", null, "repo")); + await Assert.ThrowsAsync(() => client.RemoveRepositoryFromATeam("org", "teamSlug", "owner", null)); + } + + [Fact] + public async Task RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new TeamsClient(connection); + + await client.RemoveRepositoryFromATeam("org", "teamSlug", "owner", "repo"); + + var expected = "/orgs/org/teams/teamSlug/repos/owner/repo"; + + connection.Received().Delete(Arg.Is(u => u.ToString() == expected)); + } + } } } diff --git a/Octokit.Tests/SimpleJsonSerializerTests.cs b/Octokit.Tests/SimpleJsonSerializerTests.cs index a1265bd002..040b7c107a 100644 --- a/Octokit.Tests/SimpleJsonSerializerTests.cs +++ b/Octokit.Tests/SimpleJsonSerializerTests.cs @@ -472,13 +472,10 @@ public void ShouldDeserializeParentTeamWithNullPermission() var result = new SimpleJsonSerializer().Deserialize(teamJson); // original value works as expected - Assert.Equal(PermissionLevel.Admin, result.Permission.Value); - Assert.Equal("admin", result.Permission.StringValue); + Assert.Equal("admin", result.Permission); // parent permission is marked as null and cannot be parsed - Assert.Equal("null", result.Parent.Permission.StringValue); - PermissionLevel value; - Assert.False(result.Parent.Permission.TryParse(out value)); + Assert.Null(result.Parent.Permission); } } diff --git a/Octokit/Clients/ITeamsClient.cs b/Octokit/Clients/ITeamsClient.cs index ec3321d081..9507c125e4 100644 --- a/Octokit/Clients/ITeamsClient.cs +++ b/Octokit/Clients/ITeamsClient.cs @@ -125,16 +125,54 @@ public interface ITeamsClient /// Newly created Task Create(string org, NewTeam team); + /// + /// Updates a team + /// To edit a team, the authenticated user must either be an organization owner or a team maintainer + /// + /// + /// See the API documentation + /// for more information. + /// + /// updated for the current org + Task Update(string org, string teamSlug, UpdateTeam team); + /// /// Returns updated for the current org. + /// This endpoint route is deprecated and will be removed from the Teams API. + /// We recommend migrating your existing code to use the new Update a team endpoint. + /// . /// + /// + /// See the API documentation + /// for more information. + /// /// Thrown when a general API error occurs. /// Updated Task Update(int id, UpdateTeam team); /// - /// Delte a team - must have owner permissions to this + /// To delete a team, the authenticated user must be an organization owner or team maintainer. + /// If you are an organization owner, deleting a parent team will delete all of its child teams as well. /// + /// + /// See the API documentation + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// Thrown when a general API error occurs. + /// + Task Delete(string org, string teamSlug); + + /// + /// Delete a team - must have owner permissions to do this + /// This endpoint route is deprecated and will be removed from the Teams API. + /// We recommend migrating your existing code to use the new Delete a team endpoint. + /// . + /// + /// + /// See the API documentation + /// + /// The unique identifier of the team. /// Thrown when a general API error occurs. /// Task Delete(int id); @@ -249,5 +287,72 @@ public interface ITeamsClient /// Options to change API behaviour. /// Task> GetAllPendingInvitations(int id, ApiOptions options); + + /// + /// Checks whether a team has admin, push, maintain, triage, or pull permission for a repository. + /// Repositories inherited through a parent team will also be checked. + /// + /// + /// See the API Documentation + /// for more information. + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// The account owner of the repository. The name is not case sensitive. + /// The name of the repository. The name is not case sensitive. + /// + Task CheckTeamPermissionsForARepository(string org, string teamSlug, string owner, string repo); + + /// + /// Checks whether a team has admin, push, maintain, triage, or pull permission for a repository. + /// Repositories inherited through a parent team will also be checked. + /// + /// + /// See the API Documentation + /// for more information. + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// The account owner of the repository. The name is not case sensitive. + /// The name of the repository. The name is not case sensitive. + /// Thrown when a general API error occurs. + /// + Task CheckTeamPermissionsForARepositoryWithCustomAcceptHeader(string org, string teamSlug, string owner, string repo); + + /// + /// Add or update team repository permissions + /// + /// + /// See the API Documentation + /// for more information. + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// The account owner of the repository. The name is not case sensitive. + /// The name of the repository. The name is not case sensitive. + /// + /// The permission to grant the team on this repository. We accept the following permissions to be set: + /// pull, triage, push, maintain, admin and you can also specify a custom repository role name, if the + /// owning organization has defined any. If no permission is specified, the team's permission attribute + /// will be used to determine what permission to grant the team on this repository + /// + /// Thrown when a general API error occurs. + /// + Task AddOrUpdateTeamRepositoryPermissions(string org, string teamSlug, string owner, string repo, string permission); + + /// + /// Remove a repository from a team + /// + /// + /// See the API Documentation + /// for more information. + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// The account owner of the repository. The name is not case sensitive. + /// The name of the repository. The name is not case sensitive. + /// Thrown when a general API error occurs. + /// + Task RemoveRepositoryFromATeam(string org, string teamSlug, string owner, string repo); } } diff --git a/Octokit/Clients/TeamsClient.cs b/Octokit/Clients/TeamsClient.cs index 2d01a3445b..af41cc3119 100644 --- a/Octokit/Clients/TeamsClient.cs +++ b/Octokit/Clients/TeamsClient.cs @@ -227,9 +227,36 @@ public Task Create(string org, NewTeam team) return ApiConnection.Post(endpoint, team); } + /// + /// Updates a team + /// To edit a team, the authenticated user must either be an organization owner or a team maintainer + /// + /// + /// See the API documentation + /// for more information. + /// + /// updated for the current org + [ManualRoute("PATCH", "/orgs/{org}/teams/{team_slug}")] + public Task Update(string org, string teamSlug, UpdateTeam team) + { + Ensure.ArgumentNotNull(org, nameof(org)); + Ensure.ArgumentNotNull(teamSlug, nameof(teamSlug)); + Ensure.ArgumentNotNull(team, nameof(team)); + + var endpoint = ApiUrls.TeamsByOrganizationAndSlug(org, teamSlug); + return ApiConnection.Patch(endpoint, team); + } + /// /// Returns updated for the current org. + /// This endpoint route is deprecated and will be removed from the Teams API. + /// We recommend migrating your existing code to use the new Update a team endpoint. + /// . /// + /// + /// See the API documentation + /// for more information. + /// /// Thrown when a general API error occurs. /// Updated [ManualRoute("PATCH", "/teams/{team_id}")] @@ -242,8 +269,37 @@ public Task Update(int id, UpdateTeam team) } /// - /// Delte a team - must have owner permissions to this + /// To delete a team, the authenticated user must be an organization owner or team maintainer. + /// If you are an organization owner, deleting a parent team will delete all of its child teams as well. /// + /// + /// See the API documentation + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// Thrown when a general API error occurs. + /// + [ManualRoute("DELETE", "/orgs/{org}/teams/{team_slug}")] + public Task Delete(string org, string teamSlug) + { + Ensure.ArgumentNotNull(org, nameof(org)); + Ensure.ArgumentNotNull(teamSlug, nameof(teamSlug)); + + var endpoint = ApiUrls.TeamsByOrganizationAndSlug(org, teamSlug); + + return ApiConnection.Delete(endpoint); + } + + /// + /// Delete a team - must have owner permissions to do this + /// This endpoint route is deprecated and will be removed from the Teams API. + /// We recommend migrating your existing code to use the new Delete a team endpoint. + /// . + /// + /// + /// See the API documentation + /// + /// The unique identifier of the team. /// Thrown when a general API error occurs. /// [ManualRoute("DELETE", "/teams/{team_id}")] @@ -332,11 +388,13 @@ public Task> GetAllRepositories(int id, ApiOptions opt } /// - /// Add a repository to the team + /// Add or update team repository permissions (Legacy) + /// Deprecation Notice: This endpoint route is deprecated and will be removed from the Teams API. + /// We recommend migrating your existing code to use the new "Add or update team repository permissions" endpoint. /// /// Thrown when a general API error occurs. /// - [ManualRoute("PUT", "/orgs/{org}/team/{team_slug}/repos/{owner}/{repo}")] + [ManualRoute("PUT", "/teams/{team_id}/repos/{owner}/{repo}")] public async Task AddRepository(int id, string organization, string repoName) { Ensure.ArgumentNotNullOrEmptyString(organization, nameof(organization)); @@ -368,7 +426,9 @@ public async Task AddRepository(int id, string organization, string repoNa } /// - /// Add a repository to the team + /// Add or update team repository permissions (Legacy) + /// Deprecation Notice: This endpoint route is deprecated and will be removed from the Teams API. + /// We recommend migrating your existing code to use the new "Add or update team repository permissions" endpoint. /// /// The team identifier. /// Org to associate the repo with. @@ -376,7 +436,7 @@ public async Task AddRepository(int id, string organization, string repoNa /// The permission to grant the team on this repository. /// Thrown when a general API error occurs. /// - [ManualRoute("PUT", "/orgs/{org}/team/{team_slug}/repos/{owner}/{repo}")] + [ManualRoute("PUT", "/teams/{team_id}/repos/{owner}/{repo}")] public async Task AddRepository(int id, string organization, string repoName, RepositoryPermissionRequest permission) { Ensure.ArgumentNotNullOrEmptyString(organization, nameof(organization)); @@ -408,11 +468,13 @@ public async Task AddRepository(int id, string organization, string repoNa } /// - /// Remove a repository from the team + /// Remove a repository from a team (Legacy) + /// Deprecation Notice: This endpoint route is deprecated and will be removed from the Teams API. + /// We recommend migrating your existing code to use the new Remove a repository from a team endpoint. /// /// Thrown when a general API error occurs. /// - [ManualRoute("DELETE", "/orgs/:org/teams/:team_slug/repos/:owner/:repo")] + [ManualRoute("DELETE", "/teams/{team_id}/repos/{owner}/{repo}")] public async Task RemoveRepository(int id, string organization, string repoName) { Ensure.ArgumentNotNullOrEmptyString(organization, nameof(organization)); @@ -505,5 +567,124 @@ public Task> GetAllPendingInvita { return ApiConnection.GetAll(ApiUrls.TeamPendingInvitations(id), null, options); } + + /// + /// Checks whether a team has admin, push, maintain, triage, or pull permission for a repository. + /// Repositories inherited through a parent team will also be checked. + /// + /// + /// See the API Documentation + /// for more information. + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// The account owner of the repository. The name is not case sensitive. + /// The name of the repository. The name is not case sensitive. + /// + [ManualRoute("GET", "/orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}")] + public async Task CheckTeamPermissionsForARepository(string org, string teamSlug, string owner, string repo) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNullOrEmptyString(teamSlug, nameof(teamSlug)); + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(repo, nameof(repo)); + + var endpoint = ApiUrls.TeamPermissionsForARepository(org, teamSlug, owner, repo); + + try + { + var response = await ApiConnection.Get(endpoint); + return response == null; + } + catch(NotFoundException) + { + return false; + } + } + + /// + /// Checks whether a team has admin, push, maintain, triage, or pull permission for a repository. + /// Repositories inherited through a parent team will also be checked. + /// + /// + /// See the API Documentation + /// for more information. + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// The account owner of the repository. The name is not case sensitive. + /// The name of the repository. The name is not case sensitive. + /// Thrown when a general API error occurs. + /// + [ManualRoute("GET", "/orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}")] + public Task CheckTeamPermissionsForARepositoryWithCustomAcceptHeader(string org, string teamSlug, string owner, string repo) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNullOrEmptyString(teamSlug, nameof(teamSlug)); + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(repo, nameof(repo)); + + var endpoint = ApiUrls.TeamPermissionsForARepository(org, teamSlug, owner, repo); + + return ApiConnection.Get(endpoint, null, AcceptHeaders.RepositoryContentMediaType); + } + + /// + /// Add or update team repository permissions + /// + /// + /// See the API Documentation + /// for more information. + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// The account owner of the repository. The name is not case sensitive. + /// The name of the repository. The name is not case sensitive. + /// + /// The permission to grant the team on this repository. We accept the following permissions to be set: + /// pull, triage, push, maintain, admin and you can also specify a custom repository role name, if the + /// owning organization has defined any. If no permission is specified, the team's permission attribute + /// will be used to determine what permission to grant the team on this repository + /// + /// Thrown when a general API error occurs. + /// + [ManualRoute("PUT", "/orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}")] + public Task AddOrUpdateTeamRepositoryPermissions(string org, string teamSlug, string owner, string repo, string permission) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNullOrEmptyString(teamSlug, nameof(teamSlug)); + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(repo, nameof(repo)); + + var endpoint = ApiUrls.TeamPermissionsForARepository(org, teamSlug, owner, repo); + + return ApiConnection.Put(endpoint, new { permission }); + } + + /// + /// Remove a repository from a team + /// + /// + /// See the API Documentation + /// for more information. + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// The account owner of the repository. The name is not case sensitive. + /// The name of the repository. The name is not case sensitive. + /// Thrown when a general API error occurs. + /// + [ManualRoute("DELETE", "/orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}")] + public Task RemoveRepositoryFromATeam(string org, string teamSlug, string owner, string repo) + { + Ensure.ArgumentNotNullOrEmptyString(org, nameof(org)); + Ensure.ArgumentNotNullOrEmptyString(teamSlug, nameof(teamSlug)); + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(repo, nameof(repo)); + + var endpoint = ApiUrls.TeamPermissionsForARepository(org, teamSlug, owner, repo); + + return ApiConnection.Delete(endpoint); + } } } diff --git a/Octokit/Helpers/AcceptHeaders.cs b/Octokit/Helpers/AcceptHeaders.cs index c39c1baddf..c43e9cbdfb 100644 --- a/Octokit/Helpers/AcceptHeaders.cs +++ b/Octokit/Helpers/AcceptHeaders.cs @@ -13,5 +13,7 @@ public static class AcceptHeaders /// /// https://developer.github.com/v3/repos/contents/#custom-media-types public const string RawContentMediaType = "application/vnd.github.v3.raw"; + + public const string RepositoryContentMediaType = "application/vnd.github.v3.repository+json"; } } diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs index 7a4613b3c1..82621ada5a 100644 --- a/Octokit/Helpers/ApiUrls.cs +++ b/Octokit/Helpers/ApiUrls.cs @@ -1922,6 +1922,18 @@ public static Uri Teams(int id) return "teams/{0}".FormatUri(id); } + /// + /// Returns the for teams + /// use for updating, or deleteing a . + /// + /// + /// + /// + public static Uri TeamsByOrganizationAndSlug(string org, string teamSlug) + { + return "orgs/{0}/teams/{1}".FormatUri(org,teamSlug); + } + /// /// returns the for team member /// @@ -1961,6 +1973,18 @@ public static Uri TeamRepository(int id, string organization, string repoName) return "teams/{0}/repos/{1}/{2}".FormatUri(id, organization, repoName); } + /// + /// returns the for a team repository + /// + /// The organization name. The name is not case sensitive. + /// The slug of the team name. + /// The account owner of the repository. The name is not case sensitive. + /// The name of the repository. The name is not case sensitive. + public static Uri TeamPermissionsForARepository(string org, string teamSlug, string owner, string repo) + { + return "/orgs/{0}/teams/{1}/repos/{2}/{3}".FormatUri(org, teamSlug, owner, repo); + } + /// /// returns the for the teams pending invitations /// diff --git a/Octokit/Http/ApiConnection.cs b/Octokit/Http/ApiConnection.cs index 1a5694271c..6893766b62 100644 --- a/Octokit/Http/ApiConnection.cs +++ b/Octokit/Http/ApiConnection.cs @@ -338,6 +338,20 @@ public Task Put(Uri uri) return Connection.Put(uri); } + /// + /// Creates or replaces the API resource at the specified URI + /// + /// URI of the API resource to put + /// Object that describes the API resource; this will be serialized and used as the request's body + /// A for the request's execution. + public Task Put(Uri uri, object data) + { + Ensure.ArgumentNotNull(uri, nameof(uri)); + Ensure.ArgumentNotNull(data, nameof(data)); + + return Connection.Put(uri, data); + } + /// /// Creates or replaces the API resource at the specified URI. /// diff --git a/Octokit/Http/Connection.cs b/Octokit/Http/Connection.cs index bcace9f7ee..6a9f065c86 100644 --- a/Octokit/Http/Connection.cs +++ b/Octokit/Http/Connection.cs @@ -498,14 +498,14 @@ public async Task Put(Uri uri) /// Performs an asynchronous HTTP PUT request that expects an empty response. /// /// URI endpoint to send request to - /// Specifies accepted response media types. + /// The object to serialize as the body of the request /// The returned - public async Task Put(Uri uri, string accepts) + public async Task Put(Uri uri, object body) { Ensure.ArgumentNotNull(uri, nameof(uri)); - Ensure.ArgumentNotNull(accepts, nameof(accepts)); + Ensure.ArgumentNotNull(body, nameof(body)); - var response = await SendData(uri, HttpMethod.Put, null, accepts, null, CancellationToken.None).ConfigureAwait(false); + var response = await SendData(uri, HttpMethod.Put, body, null, null, CancellationToken.None).ConfigureAwait(false); return response.HttpResponse.StatusCode; } diff --git a/Octokit/Http/IApiConnection.cs b/Octokit/Http/IApiConnection.cs index 730cd2f5d8..ce915527c7 100644 --- a/Octokit/Http/IApiConnection.cs +++ b/Octokit/Http/IApiConnection.cs @@ -234,6 +234,14 @@ public interface IApiConnection /// A for the request's execution. Task Put(Uri uri); + /// + /// Creates or replaces the API resource at the specified URI + /// + /// URI of the API resource to put + /// Object that describes the API resource; this will be serialized and used as the request's body + /// A for the request's execution. + Task Put(Uri uri, object data); + /// /// Creates or replaces the API resource at the specified URI. /// diff --git a/Octokit/Http/IConnection.cs b/Octokit/Http/IConnection.cs index ff4b6359f1..3eacde0494 100644 --- a/Octokit/Http/IConnection.cs +++ b/Octokit/Http/IConnection.cs @@ -251,9 +251,9 @@ Task> Post( /// Performs an asynchronous HTTP PUT request that expects an empty response. /// /// URI endpoint to send request to - /// Specifies accepted response media types. + /// The object to serialize as the body of the request /// The returned - Task Put(Uri uri, string accepts); + Task Put(Uri uri, object body); /// /// Performs an asynchronous HTTP DELETE request that expects an empty response. diff --git a/Octokit/Models/Request/NewTeam.cs b/Octokit/Models/Request/NewTeam.cs index 23ba322dec..d506705d7b 100644 --- a/Octokit/Models/Request/NewTeam.cs +++ b/Octokit/Models/Request/NewTeam.cs @@ -55,7 +55,7 @@ public NewTeam(string name) /// /// The permission that new repositories will be added to the team with when none is specified (default: Pull) /// - public Permission? Permission { get; set; } + public TeamPermission? Permission { get; set; } /// /// Id of a team to set as the parent team diff --git a/Octokit/Models/Request/Permission.cs b/Octokit/Models/Request/Permission.cs index 8e5eca5c3f..f3f94e003b 100644 --- a/Octokit/Models/Request/Permission.cs +++ b/Octokit/Models/Request/Permission.cs @@ -1,4 +1,6 @@ +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using Octokit.Internal; namespace Octokit @@ -10,19 +12,19 @@ namespace Octokit public enum Permission { /// - /// team members can pull, push and administer these repositories. + /// team members can pull, push and administer these repositories. /// [Parameter(Value = "admin")] Admin, /// - /// team members can manage the repository without access to sensitive or destructive actions. Recommended for project managers. Only applies to repositories owned by organizations. + /// team members can manage the repository without access to sensitive or destructive actions. Recommended for project managers. Only applies to repositories owned by organizations. /// [Parameter(Value = "maintain")] Maintain, /// - /// team members can proactively manage issues and pull requests without write access. Recommended for contributors who triage a repository. Only applies to repositories owned by organizations. + /// team members can proactively manage issues and pull requests without write access. Recommended for contributors who triage a repository. Only applies to repositories owned by organizations. /// [Parameter(Value = "triage")] Triage, @@ -39,4 +41,86 @@ public enum Permission [Parameter(Value = "pull")] Pull } + + /// + /// Deprecated. The permission that new repositories will be added to the team with when none is specified + /// Default: pull + /// Can be one of: pull, push + /// + [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] + public enum TeamPermission + { + /// + /// team members can pull, but not push to these repositories + /// + [Parameter(Value = "pull")] + Pull, + + /// + /// team members can pull and push to these repositories + /// + [Parameter(Value = "push")] + Push + } + + /// + /// Object for team repository permissions + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class TeamRepositoryPermissions + { + public TeamRepositoryPermissions() { } + public TeamRepositoryPermissions(bool pull, bool triage, bool push, bool maintain, bool admin) + { + Pull = pull; + Triage = triage; + Push = push; + Maintain = maintain; + Admin = admin; + } + + /// + /// Can read and clone repository. + /// Can also open and comment on issues and pull requests. + /// Required + /// + public bool Pull { get; private set; } + + /// + /// Can read and clone repository. + /// Can also manage issues and pull requests. + /// Required + /// + public bool Triage { get; private set; } + + /// + /// Can read, clone, and push to repository. + /// Can also manage issues and pull requests. + /// Required + /// + public bool Push { get; private set; } + + /// + /// Can read, clone, and push to repository. + /// They can also manage issues, pull requests, and some repository settings. + /// Required + /// + public bool Maintain { get; private set; } + + /// + /// Can read, clone, and push to repository. + /// Can also manage issues, pull requests, and repository settings, including adding collaborators. + /// Required + /// + public bool Admin { get; private set; } + + internal string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, + $"Permissions: Pull: {Pull}, Triage: {Triage}, Push: {Push}, Maintain: {Maintain}, Admin: {Admin}"); + } + } + } } diff --git a/Octokit/Models/Request/UpdateTeam.cs b/Octokit/Models/Request/UpdateTeam.cs index 960019094d..e9297a2ecf 100644 --- a/Octokit/Models/Request/UpdateTeam.cs +++ b/Octokit/Models/Request/UpdateTeam.cs @@ -36,8 +36,11 @@ public UpdateTeam(string name) /// /// The permission that new repositories will be added to the team with when none is specified (default: Pull) + /// Although permission can be one of : pull, push, or admin based on documentation, passing admin results in an error response. + /// That's why TeamPermission does not contain an admin value. + /// See the issue here https://github.com/github/rest-api-description/issues/1952 /// - public Permission? Permission { get; set; } + public TeamPermission? Permission { get; set; } /// /// Id of a team to set as the parent team diff --git a/Octokit/Models/Response/Team.cs b/Octokit/Models/Response/Team.cs index 99fba9ecca..4c4be950f6 100644 --- a/Octokit/Models/Response/Team.cs +++ b/Octokit/Models/Response/Team.cs @@ -12,7 +12,7 @@ public class Team { public Team() { } - public Team(string url, string htmlUrl, int id, string nodeId, string slug, string name, string description, TeamPrivacy privacy, PermissionLevel permission, int membersCount, int reposCount, Organization organization, Team parent, string ldapDistinguishedName) + public Team(string url, string htmlUrl, int id, string nodeId, string slug, string name, string description, TeamPrivacy privacy, string permission, TeamRepositoryPermissions teamRepositoryPermissions, int membersCount, int reposCount, Organization organization, Team parent, string ldapDistinguishedName) { Url = url; HtmlUrl = htmlUrl; @@ -23,6 +23,7 @@ public Team(string url, string htmlUrl, int id, string nodeId, string slug, stri Description = description; Privacy = privacy; Permission = permission; + TeamRepositoryPermissions = teamRepositoryPermissions; MembersCount = membersCount; ReposCount = reposCount; Organization = organization; @@ -71,9 +72,14 @@ public Team(string url, string htmlUrl, int id, string nodeId, string slug, stri public StringEnum Privacy { get; private set; } /// - /// permission attached to this team + /// Deprecated. The permission that new repositories will be added to the team with when none is specified /// - public StringEnum Permission { get; private set; } + public string Permission { get; private set; } + + /// + /// + /// + public TeamRepositoryPermissions TeamRepositoryPermissions { get; private set; } /// /// how many members in this team diff --git a/Octokit/Models/Response/TeamRepository.cs b/Octokit/Models/Response/TeamRepository.cs new file mode 100644 index 0000000000..bab62979c9 --- /dev/null +++ b/Octokit/Models/Response/TeamRepository.cs @@ -0,0 +1,618 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; + +namespace Octokit +{ + /// + /// A teams's repository + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class TeamRepository + { + public TeamRepository() { } + + public TeamRepository(int id, + string nodeId, + string name, + string fullName, + LicenseMetadata license, + TeamRepositoryPermissions permissions, + string roleName, + User owner, + bool @private, + string htmlUrl, + string description, + bool fork, + string url, + string archiveUrl, + string assigneesUrl, + string blobsUrl, + string branchesUrl, + string collaboratorsUrl, + string commentsUrl, + string commitsUrl, + string compareUrl, + string contentsUrl, + string contributorsUrl, + string deploymentsUrl, + string downloadsUrl, + string eventsUrl, + string forksUrl, + string gitCommitUrl, + string gitRefsUrl, + string gitTagsUrl, + string gitUrl, + string issueCommentUrl, + string issueEventsUrl, + string issuesUrl, + string keysUrl, + string labelsUrl, + string languagesUrl, + string mergesUrl, + string milestonesUrl, + string notificationsUrl, + string pullsUrl, + string releasesUrl, + string sshUrl, + string stargazersUrl, + string statusesUrl, + string subscribersUrl, + string subscriptionUrl, + string tagsUrl, + string teamsUrl, + string treesUrl, + string cloneUrl, + string mirrorUrl, + string hooksUrl, + string svnUrl, + string homePage, + string language, + int forksCount, + int stargazersCount, + int watchersCount, + int size, + string defaultBranch, + int openIssuesCount, + bool isTemplate, + IReadOnlyList topics, + bool hasIssues, + bool hasProjects, + bool hasWiki, + bool hasPages, + bool hasDownloads, + bool archived, + bool disabled, + RepositoryVisibility? visibility, + DateTimeOffset? pushedAt, + DateTimeOffset createdAt, + DateTimeOffset updatedAt, + bool? allowRebaseMerge, + Repository templateRepository, + string tempCloneToken, + bool? allowSquashMerge, + bool? allowAutoMerge, + bool? deleteBranchOnMerge, + bool? allowMergeCommit, + bool? allowForking, + bool? webCommitSignoffRequired, + int subscribersCount, + int networkCount, + int openIssues, + int watchers, + string masterBranch) + { + Id = id; + NodeId = nodeId; + Name = name; + FullName = fullName; + License = license; + Permissions = permissions; + RoleName = roleName; + Owner = owner; + Private = @private; + HtmlUrl = htmlUrl; + Description = description; + Fork = fork; + Url = url; + ArchiveUrl = archiveUrl; + AssigneesUrl = assigneesUrl; + BlobsUrl = blobsUrl; + BranchesUrl = branchesUrl; + CollaboratorsUrl = collaboratorsUrl; + CommentsUrl = commentsUrl; + CommitsUrl = commitsUrl; + CompareUrl = compareUrl; + ContentsUrl = contentsUrl; + ContributorsUrl = contributorsUrl; + DeploymentsUrl = deploymentsUrl; + DownloadsUrl = downloadsUrl; + EventsUrl = eventsUrl; + ForksUrl = forksUrl; + GitCommitUrl = gitCommitUrl; + GitRefsUrl = gitRefsUrl; + GitTagsUrl = gitTagsUrl; + GitUrl = gitUrl; + IssueCommentUrl = issueCommentUrl; + IssueEventsUrl = issueEventsUrl; + IssuesUrl = issuesUrl; + KeysUrl = keysUrl; + LabelsUrl = labelsUrl; + LanguagesUrl = languagesUrl; + MergesUrl = mergesUrl; + MilestonesUrl = milestonesUrl; + NotificationsUrl = notificationsUrl; + PullsUrl = pullsUrl; + ReleasesUrl = releasesUrl; + SshUrl = sshUrl; + StargazersUrl = stargazersUrl; + StatusesUrl = statusesUrl; + SubscribersUrl = subscribersUrl; + SubscriptionUrl = subscriptionUrl; + TagsUrl = tagsUrl; + TeamsUrl = teamsUrl; + TreesUrl = treesUrl; + CloneUrl = cloneUrl; + MirrorUrl = mirrorUrl; + HooksUrl = hooksUrl; + SvnUrl = svnUrl; + HomePage = homePage; + Language = language; + ForksCount = forksCount; + StargazersCount = stargazersCount; + WatchersCount = watchersCount; + Size = size; + DefaultBranch = defaultBranch; + OpenIssuesCount = openIssuesCount; + IsTemplate = isTemplate; + Topics = topics; + HasIssues = hasIssues; + HasProjects = hasProjects; + HasWiki = hasWiki; + HasPages = hasPages; + HasDownloads = hasDownloads; + Archived = archived; + Disabled = disabled; + Visibility = visibility; + PushedAt = pushedAt; + CreatedAt = createdAt; + UpdatedAt = updatedAt; + AllowRebaseMerge = allowRebaseMerge; + TemplateRepository = templateRepository; + TempCloneToken = tempCloneToken; + AllowSquashMerge = allowSquashMerge; + AllowAutoMerge = allowAutoMerge; + DeleteBranchOnMerge = deleteBranchOnMerge; + AllowMergeCommit = allowMergeCommit; + AllowForking = allowForking; + WebCommitSignoffRequired = webCommitSignoffRequired; + SubscribersCount = subscribersCount; + NetworkCount = networkCount; + OpenIssues = openIssues; + Watchers = watchers; + MasterBranch = masterBranch; + } + + + /// + /// Unique identifier of the repository + /// + public int Id { get; private set; } + + /// + /// GraphQL Node Id + /// + public string NodeId { get; private set; } + + /// + /// The name of the repository + /// + public string Name { get; private set; } + + /// + /// example: octocat/Hello-World + /// + public string FullName { get; private set; } + + public LicenseMetadata License { get; private set; } + + public TeamRepositoryPermissions Permissions { get; private set; } + + public string RoleName { get; private set; } + + public User Owner { get; private set; } + + /// + /// hether the repository is private or public. + /// default: false + /// + public bool Private { get; private set; } + + /// + /// format: uri + /// example: https://github.com/octocat/Hello-World + /// + public string HtmlUrl { get; private set; } + + /// + /// example: This your first repo! + /// nullable: true + /// + public string Description { get; private set; } + + public bool Fork { get; private set; } + + /// + /// format: uri + /// example: https://api.github.com/repos/octocat/Hello-World + /// + public string Url { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref} + /// + public string ArchiveUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/assignees{/user} + /// + public string AssigneesUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/git/blobs{/sha} + /// + public string BlobsUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/branches{/branch} + /// + public string BranchesUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator} + /// + public string CollaboratorsUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/comments{/number} + /// + public string CommentsUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/commits{/sha} + /// + public string CommitsUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/compare/{base}...{head} + /// + public string CompareUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/contents/{+path} + /// + public string ContentsUrl { get; private set; } + + /// + /// format: uri + /// example: http://api.github.com/repos/octocat/Hello-World/contributors + /// + public string ContributorsUrl { get; private set; } + + /// + /// format: uri + /// example: http://api.github.com/repos/octocat/Hello-World/deployments + /// + public string DeploymentsUrl { get; private set; } + + /// + /// format: uri + /// example: http://api.github.com/repos/octocat/Hello-World/downloads + /// + public string DownloadsUrl { get; private set; } + + /// + /// format: uri + /// example: http://api.github.com/repos/octocat/Hello-World/events + /// + public string EventsUrl { get; private set; } + + /// + /// format: uri + /// example: http://api.github.com/repos/octocat/Hello-World/forks + /// + public string ForksUrl { get; private set; } + ///example: http://api.github.com/repos/octocat/Hello-World/git/commits{/sha} + public string GitCommitUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/git/refs{/sha} + /// + public string GitRefsUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/git/tags{/sha} + /// + public string GitTagsUrl { get; private set; } + + /// + /// example: git:github.com/octocat/Hello-World.git + /// + public string GitUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/issues/comments{/number} + /// + public string IssueCommentUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/issues/events{/number} + /// + public string IssueEventsUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/issues{/number} + /// + public string IssuesUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/keys{/key_id} + /// + public string KeysUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/labels{/name} + /// + public string LabelsUrl { get; private set; } + + /// + /// format: uri + /// example: http://api.github.com/repos/octocat/Hello-World/languages + /// + public string LanguagesUrl { get; private set; } + + /// + /// format: uri + /// example: http://api.github.com/repos/octocat/Hello-World/merges + /// + public string MergesUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/milestones{/number} + /// + public string MilestonesUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating} + /// + public string NotificationsUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/pulls{/number} + /// + public string PullsUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/releases{/id} + /// + public string ReleasesUrl { get; private set; } + + /// + /// example: git @github.com:octocat/Hello-World.git + /// + /// + public string SshUrl { get; private set; } + + /// + /// format: uri + /// example: http://api.github.com/repos/octocat/Hello-World/stargazers + /// + public string StargazersUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/statuses/{sha} + /// + public string StatusesUrl { get; private set; } + + /// + /// format: uri + /// example: http://api.github.com/repos/octocat/Hello-World/subscribers + /// + public string SubscribersUrl { get; private set; } + + /// + /// format: uri + /// example: http://api.github.com/repos/octocat/Hello-World/subscription + /// + public string SubscriptionUrl { get; private set; } + + /// + /// format: uri + /// example: http://api.github.com/repos/octocat/Hello-World/tags + /// + public string TagsUrl { get; private set; } + + + /// + /// format: uri + /// example: http://api.github.com/repos/octocat/Hello-World/teams + /// + public string TeamsUrl { get; private set; } + + /// + /// example: http://api.github.com/repos/octocat/Hello-World/git/trees{/sha} + /// + public string TreesUrl { get; private set; } + + /// + /// example: https://github.com/octocat/Hello-World.git + /// + public string CloneUrl { get; private set; } + + /// + /// format: uri + /// example: git:git.example.com/octocat/Hello-World + /// nullable: true + /// + public string MirrorUrl { get; private set; } + + + /// + /// format: uri + /// example: http://api.github.com/repos/octocat/Hello-World/hooks + /// + public string HooksUrl { get; private set; } + + /// + /// format: uri + /// example: https://svn.github.com/octocat/Hello-World + /// + public string SvnUrl { get; private set; } + + /// + /// format: uri + /// example: https://github.com + /// + public string HomePage { get; private set; } + + public string Language { get; private set; } + + public int ForksCount { get; private set; } + + public int StargazersCount { get; private set; } + + public int WatchersCount { get; private set; } + + public int Size { get; private set; } + + /// + /// he default branch of the repository. + /// example: master + /// + public string DefaultBranch { get; private set; } + + public int OpenIssuesCount { get; private set; } + + /// + /// Whether this repository acts as a template that can be used to generate new repositories + /// default: false + /// + public bool IsTemplate { get; private set; } + + public IReadOnlyList Topics { get; private set; } + + /// + /// Whether issues are enabled. + /// + public bool HasIssues { get; private set; } + + /// + /// Whether projects are enabled. + /// + public bool HasProjects { get; private set; } + + /// + /// Whether the wiki is enabled + /// + public bool HasWiki { get; private set; } + + public bool HasPages { get; private set; } + + /// + /// Whether downloads are enabled + /// default: true + /// + public bool HasDownloads { get; private set; } + + /// + /// Whether the repository is archived + /// + public bool Archived { get; private set; } + + /// + /// Returns whether or not this repository disabled. + /// + public bool Disabled { get; private set; } + + /// + /// The repository visibility: public, private, or internal. + /// + public RepositoryVisibility? Visibility { get; private set; } + + /// + /// format: date-time + /// example: '2011-01-26T19:06:43Z' + /// + public DateTimeOffset? PushedAt { get; private set; } + + /// + /// format: date-time + /// example: '2011-01-26T19:01:12Z' + /// + public DateTimeOffset CreatedAt { get; private set; } + + /// + /// format: date-time + /// example: '2011-01-26T19:14:43Z' + /// + public DateTimeOffset UpdatedAt { get; private set; } + + /// + /// Whether to allow rebase merges for pull requests. + /// + public bool? AllowRebaseMerge { get; private set; } + + /// + /// Template repository (nullable) + /// + public Repository TemplateRepository { get; private set; } + + public string TempCloneToken { get; private set; } + + /// + /// Whether to allow squash merges for pull requests. + /// + public bool? AllowSquashMerge { get; private set; } + + /// + /// Whether to allow Auto-merge to be used on pull requests. + /// + public bool? AllowAutoMerge { get; private set; } + + /// + /// Whether to delete head branches when pull requests are merged + /// + public bool? DeleteBranchOnMerge { get; private set; } + + /// + /// hether to allow merge commits for pull requests. + /// + public bool? AllowMergeCommit { get; private set; } + + /// + /// Whether to allow forking this repo + /// + public bool? AllowForking { get; private set; } + + /// + /// Whether to require contributors to sign off on web-based commits + /// + public bool? WebCommitSignoffRequired { get; private set; } + + public int SubscribersCount { get; private set; } + + public int NetworkCount { get; private set; } + + public int OpenIssues { get; private set; } + + public int Watchers { get; private set; } + + public string MasterBranch { get; private set; } + + internal string DebuggerDisplay + { + get { return string.Format(CultureInfo.InvariantCulture, "Name: {0} ", FullName); } + } + } +} diff --git a/docs/teams.md b/docs/teams.md new file mode 100644 index 0000000000..2bfb1f9d56 --- /dev/null +++ b/docs/teams.md @@ -0,0 +1,77 @@ +# Working with Teams + +To access the teams API, you need to be an authenticated member of the organization's team. OAuth access tokens require the read:org scope. The ITeamsClient houses the endpoints for the teams API. + +### Create, update or delete teams + +To create a new team, you need to be a member of owner of the organization. + +```csharp +var newTeam = new NewTeam("team-name") +{ + Description = "my cool team description", + Privacy = TeamPrivacy.Closed +}; +newTeam.Maintainers.Add("maintainer-name"); +newTeam.RepoNames.Add("repository-name"); + +var team = await github.Organization.Team.Create("organization-name", newTeam); +``` + +Updating and deleting a team is also possible + +```csharp +var update = new UpdateTeam("team-name",) +{ + Description = "my new team description", + Privacy = TeamPrivacy.Closed, + Permission = TeamPermission.Push, +}; + +var team = await _github.Organization.Team.Update("organization-name", "team-slug", update); +``` + +```csharp +var team = await _github.Organization.Team.Delete("organization-name", "team-slug"); +``` + +### Working with repositories for the team + +You can get the list of repositories for the team by following + +```csharp +var allRepositories = await github.Organization.Team.GetAllRepositories(teamContext.TeamId); +``` +Or check permissions for a specific repository with the CheckTeamPermissionsForARepository method. + +```csharp +await github.Organization.Team.CheckTeamPermissionsForARepository( + "organization-name", + "team-slug", + "repository-owner", + "repository-name", + false); +``` + + The following snippet shows how to add or update team repository permissions. + + Permissions can be one of pull, triage, push, maintain, admin and you can also specify a custom repository role name, if the owning organization has defined any. If no permission is specified, the team's permission attribute will be used to determine what permission to grant the team on this repository. + +```csharp +await github.Organization.Team.AddOrUpdateTeamRepositoryPermissions( + "organization-name", + "team-slug", + "repository-owner", + "repository-name", + "admin"); +``` + +To remove a repository form a team, use + +```csharp +await github.Organization.Team.RemoveRepositoryFromATeam( + "organization-name", + "team-slug", + "repository-owner", + "repository-name"); +``` \ No newline at end of file