diff --git a/Octokit.Reactive/Clients/IObservableTeamsClient.cs b/Octokit.Reactive/Clients/IObservableTeamsClient.cs index 95d382a9..745aa620 100644 --- a/Octokit.Reactive/Clients/IObservableTeamsClient.cs +++ b/Octokit.Reactive/Clients/IObservableTeamsClient.cs @@ -125,15 +125,53 @@ namespace Octokit.Reactive IObservable Create(string org, NewTeam team); /// - /// Returns updated for the current org. + /// 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 @@ namespace Octokit.Reactive /// 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 9fa0d3f7..f068d468 100644 --- a/Octokit.Reactive/Clients/ObservableTeamsClient.cs +++ b/Octokit.Reactive/Clients/ObservableTeamsClient.cs @@ -194,8 +194,33 @@ namespace Octokit.Reactive } /// - /// Returns updated for the current org. + /// 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 @@ namespace Octokit.Reactive } /// - /// 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 @@ namespace Octokit.Reactive 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 8fd06610..6a0fdfa6 100644 --- a/Octokit.Tests.Conventions/PreviewsTests.cs +++ b/Octokit.Tests.Conventions/PreviewsTests.cs @@ -70,7 +70,8 @@ namespace Octokit.Tests.Conventions // 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 ac926027..0735cc2f 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 class TeamsClientTests 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 class TeamsClientTests { 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 class TeamsClientTests 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 49ca0131..9c94d78f 100644 --- a/Octokit.Tests.Integration/Helper.cs +++ b/Octokit.Tests.Integration/Helper.cs @@ -190,15 +190,15 @@ namespace Octokit.Tests.Integration 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 5c52a612..506f27cc 100644 --- a/Octokit.Tests.Integration/Helpers/ObservableGithubClientExtensions.cs +++ b/Octokit.Tests.Integration/Helpers/ObservableGithubClientExtensions.cs @@ -36,6 +36,13 @@ namespace Octokit.Tests.Integration.Helpers 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 c04ceb27..f4cc15a2 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 87431a8f..a818b20c 100644 --- a/Octokit.Tests.Integration/Reactive/ObservableTeamsClientTests.cs +++ b/Octokit.Tests.Integration/Reactive/ObservableTeamsClientTests.cs @@ -386,12 +386,239 @@ public class ObservableTeamsClientTests 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 a1b71794..b29e921c 100644 --- a/Octokit.Tests/Clients/TeamsClientTests.cs +++ b/Octokit.Tests/Clients/TeamsClientTests.cs @@ -169,6 +169,36 @@ namespace Octokit.Tests.Clients 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 @@ namespace Octokit.Tests.Clients } 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 @@ namespace Octokit.Tests.Clients 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 a1265bd0..040b7c10 100644 --- a/Octokit.Tests/SimpleJsonSerializerTests.cs +++ b/Octokit.Tests/SimpleJsonSerializerTests.cs @@ -472,13 +472,10 @@ namespace Octokit.Tests 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 ec3321d0..9507c125 100644 --- a/Octokit/Clients/ITeamsClient.cs +++ b/Octokit/Clients/ITeamsClient.cs @@ -126,15 +126,53 @@ namespace Octokit Task Create(string org, NewTeam team); /// - /// Returns updated for the current org. + /// 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 @@ namespace Octokit /// 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 2d01a344..af41cc31 100644 --- a/Octokit/Clients/TeamsClient.cs +++ b/Octokit/Clients/TeamsClient.cs @@ -228,8 +228,35 @@ namespace Octokit } /// - /// Returns updated for the current org. + /// 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 @@ namespace Octokit } /// - /// 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 @@ namespace Octokit } /// - /// 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 @@ namespace Octokit } /// - /// 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 @@ namespace Octokit /// 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 @@ namespace Octokit } /// - /// 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 @@ namespace Octokit { 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 c39c1bad..c43e9cbd 100644 --- a/Octokit/Helpers/AcceptHeaders.cs +++ b/Octokit/Helpers/AcceptHeaders.cs @@ -13,5 +13,7 @@ /// /// 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 7a4613b3..82621ada 100644 --- a/Octokit/Helpers/ApiUrls.cs +++ b/Octokit/Helpers/ApiUrls.cs @@ -1922,6 +1922,18 @@ namespace Octokit 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 @@ namespace Octokit 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 1a569427..6893766b 100644 --- a/Octokit/Http/ApiConnection.cs +++ b/Octokit/Http/ApiConnection.cs @@ -338,6 +338,20 @@ namespace Octokit 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 bcace9f7..6a9f065c 100644 --- a/Octokit/Http/Connection.cs +++ b/Octokit/Http/Connection.cs @@ -498,14 +498,14 @@ namespace Octokit /// 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 730cd2f5..ce915527 100644 --- a/Octokit/Http/IApiConnection.cs +++ b/Octokit/Http/IApiConnection.cs @@ -234,6 +234,14 @@ namespace Octokit /// 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 ff4b6359..3eacde04 100644 --- a/Octokit/Http/IConnection.cs +++ b/Octokit/Http/IConnection.cs @@ -251,9 +251,9 @@ namespace Octokit /// 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 23ba322d..d506705d 100644 --- a/Octokit/Models/Request/NewTeam.cs +++ b/Octokit/Models/Request/NewTeam.cs @@ -55,7 +55,7 @@ namespace Octokit /// /// 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 8e5eca5c..f3f94e00 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 @@ namespace Octokit [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 96001909..e9297a2e 100644 --- a/Octokit/Models/Request/UpdateTeam.cs +++ b/Octokit/Models/Request/UpdateTeam.cs @@ -36,8 +36,11 @@ namespace Octokit /// /// 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 99fba9ec..4c4be950 100644 --- a/Octokit/Models/Response/Team.cs +++ b/Octokit/Models/Response/Team.cs @@ -12,7 +12,7 @@ namespace Octokit { 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 @@ namespace Octokit Description = description; Privacy = privacy; Permission = permission; + TeamRepositoryPermissions = teamRepositoryPermissions; MembersCount = membersCount; ReposCount = reposCount; Organization = organization; @@ -71,9 +72,14 @@ namespace Octokit 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 00000000..bab62979 --- /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 00000000..2bfb1f9d --- /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