diff --git a/github/github.go b/github/github.go index a4152bb1ad5..8b19960842c 100644 --- a/github/github.go +++ b/github/github.go @@ -81,6 +81,9 @@ const ( // https://developer.github.com/changes/2016-05-23-timeline-preview-api/ mediaTypeTimelinePreview = "application/vnd.github.mockingbird-preview+json" + + // https://developer.github.com/changes/2016-06-14-repository-invitations/ + mediaTypeRepositoryInvitationsPreview = "application/vnd.github.swamp-thing-preview+json" ) // A Client manages communication with the GitHub API. diff --git a/github/repos_collaborators.go b/github/repos_collaborators.go index f188361b364..68a9f466c84 100644 --- a/github/repos_collaborators.go +++ b/github/repos_collaborators.go @@ -58,13 +58,13 @@ type RepositoryAddCollaboratorOptions struct { // push - team members can pull and push, but not administer this repository // admin - team members can pull, push and administer this repository // - // Default value is "pull". This option is only valid for organization-owned repositories. + // Default value is "push". This option is only valid for organization-owned repositories. Permission string `json:"permission,omitempty"` } // AddCollaborator adds the specified Github user as collaborator to the given repo. // -// GitHub API docs: http://developer.github.com/v3/repos/collaborators/#add-collaborator +// GitHub API docs: https://developer.github.com/v3/repos/collaborators/#add-user-as-a-collaborator func (s *RepositoriesService) AddCollaborator(owner, repo, user string, opt *RepositoryAddCollaboratorOptions) (*Response, error) { u := fmt.Sprintf("repos/%v/%v/collaborators/%v", owner, repo, user) req, err := s.client.NewRequest("PUT", u, opt) @@ -72,6 +72,9 @@ func (s *RepositoriesService) AddCollaborator(owner, repo, user string, opt *Rep return nil, err } + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeRepositoryInvitationsPreview) + return s.client.Do(req, nil) } diff --git a/github/repos_collaborators_test.go b/github/repos_collaborators_test.go index b35c278300c..75e3cf393de 100644 --- a/github/repos_collaborators_test.go +++ b/github/repos_collaborators_test.go @@ -94,6 +94,7 @@ func TestRepositoriesService_AddCollaborator(t *testing.T) { json.NewDecoder(r.Body).Decode(v) testMethod(t, r, "PUT") + testHeader(t, r, "Accept", mediaTypeRepositoryInvitationsPreview) if !reflect.DeepEqual(v, opt) { t.Errorf("Request body = %+v, want %+v", v, opt) } diff --git a/github/repos_invitations.go b/github/repos_invitations.go new file mode 100644 index 00000000000..f2806d11cf6 --- /dev/null +++ b/github/repos_invitations.go @@ -0,0 +1,91 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// RepositoryInvitation represents an invitation to collaborate on a repo. +type RepositoryInvitation struct { + ID *int `json:"id,omitempty"` + Repo *Repository `json:"repository,omitempty"` + Invitee *User `json:"invitee,omitempty"` + Inviter *User `json:"inviter,omitempty"` + + // Permissions represents the permissions that the associated user will have + // on the repository. Possible values are: "read", "write", "admin". + Permissions *string `json:"permissions,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` +} + +// ListInvitations lists all currently-open repository invitations. +// +// GitHub API docs: https://developer.github.com/v3/repos/invitations/#list-invitations-for-a-repository +func (s *RepositoriesService) ListInvitations(repoID int, opt *ListOptions) ([]*RepositoryInvitation, *Response, error) { + u := fmt.Sprintf("repositories/%v/invitations", repoID) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeRepositoryInvitationsPreview) + + invites := []*RepositoryInvitation{} + resp, err := s.client.Do(req, &invites) + if err != nil { + return nil, resp, err + } + + return invites, resp, err +} + +// DeleteInvitation deletes a repository invitation. +// +// GitHub API docs: https://developer.github.com/v3/repos/invitations/#delete-a-repository-invitation +func (s *RepositoriesService) DeleteInvitation(repoID, invitationID int) (*Response, error) { + u := fmt.Sprintf("repositories/%v/invitations/%v", repoID, invitationID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeRepositoryInvitationsPreview) + + return s.client.Do(req, nil) +} + +// UpdateInvitation updates the permissions associated with a repository +// invitation. +// +// permissions represents the permissions that the associated user will have +// on the repository. Possible values are: "read", "write", "admin". +// +// GitHub API docs: https://developer.github.com/v3/repos/invitations/#update-a-repository-invitation +func (s *RepositoriesService) UpdateInvitation(repoID, invitationID int, permissions string) (*RepositoryInvitation, *Response, error) { + opts := &struct { + Permissions string `json:"permissions"` + }{Permissions: permissions} + u := fmt.Sprintf("repositories/%v/invitations/%v", repoID, invitationID) + req, err := s.client.NewRequest("PATCH", u, opts) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeRepositoryInvitationsPreview) + + invite := &RepositoryInvitation{} + resp, err := s.client.Do(req, invite) + return invite, resp, err +} diff --git a/github/repos_invitations_test.go b/github/repos_invitations_test.go new file mode 100644 index 00000000000..c5688cb6635 --- /dev/null +++ b/github/repos_invitations_test.go @@ -0,0 +1,73 @@ +// Copyright 2016 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_ListInvitations(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repositories/1/invitations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeRepositoryInvitationsPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprintf(w, `[{"id":1}, {"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + got, _, err := client.Repositories.ListInvitations(1, opt) + if err != nil { + t.Errorf("Repositories.ListInvitations returned error: %v", err) + } + + want := []*RepositoryInvitation{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(got, want) { + t.Errorf("Repositories.ListInvitations = %+v, want %+v", got, want) + } +} + +func TestRepositoriesService_DeleteInvitation(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repositories/1/invitations/2", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeRepositoryInvitationsPreview) + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Repositories.DeleteInvitation(1, 2) + if err != nil { + t.Errorf("Repositories.DeleteInvitation returned error: %v", err) + } +} + +func TestRepositoriesService_UpdateInvitation(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repositories/1/invitations/2", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + testHeader(t, r, "Accept", mediaTypeRepositoryInvitationsPreview) + fmt.Fprintf(w, `{"id":1}`) + }) + + got, _, err := client.Repositories.UpdateInvitation(1, 2, "write") + if err != nil { + t.Errorf("Repositories.UpdateInvitation returned error: %v", err) + } + + want := &RepositoryInvitation{ID: Int(1)} + if !reflect.DeepEqual(got, want) { + t.Errorf("Repositories.UpdateInvitation = %+v, want %+v", got, want) + } +} diff --git a/github/users.go b/github/users.go index 5d901340a91..17208041f84 100644 --- a/github/users.go +++ b/github/users.go @@ -166,3 +166,59 @@ func (s *UsersService) ListAll(opt *UserListOptions) ([]*User, *Response, error) return *users, resp, err } + +// ListInvitations lists all currently-open repository invitations for the +// authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/repos/invitations/#list-a-users-repository-invitations +func (s *UsersService) ListInvitations() ([]*RepositoryInvitation, *Response, error) { + req, err := s.client.NewRequest("GET", "user/repository_invitations", nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeRepositoryInvitationsPreview) + + invites := []*RepositoryInvitation{} + resp, err := s.client.Do(req, &invites) + if err != nil { + return nil, resp, err + } + + return invites, resp, err +} + +// AcceptInvitation accepts the currently-open repository invitation for the +// authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/repos/invitations/#accept-a-repository-invitation +func (s *UsersService) AcceptInvitation(invitationID int) (*Response, error) { + u := fmt.Sprintf("user/repository_invitations/%v", invitationID) + req, err := s.client.NewRequest("PATCH", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeRepositoryInvitationsPreview) + + return s.client.Do(req, nil) +} + +// DeclineInvitation declines the currently-open repository invitation for the +// authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/repos/invitations/#decline-a-repository-invitation +func (s *UsersService) DeclineInvitation(invitationID int) (*Response, error) { + u := fmt.Sprintf("user/repository_invitations/%v", invitationID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeRepositoryInvitationsPreview) + + return s.client.Do(req, nil) +} diff --git a/github/users_gpg_keys.go b/github/users_gpg_keys.go index 0ee0fd20335..08cfbed57cf 100644 --- a/github/users_gpg_keys.go +++ b/github/users_gpg_keys.go @@ -90,10 +90,8 @@ func (s *UsersService) GetGPGKey(id int) (*GPGKey, *Response, error) { // GitHub API docs: https://developer.github.com/v3/users/gpg_keys/#create-a-gpg-key func (s *UsersService) CreateGPGKey(armoredPublicKey string) (*GPGKey, *Response, error) { gpgKey := &struct { - ArmoredPublicKey *string `json:"armored_public_key,omitempty"` - }{ - ArmoredPublicKey: String(armoredPublicKey), - } + ArmoredPublicKey string `json:"armored_public_key"` + }{ArmoredPublicKey: armoredPublicKey} req, err := s.client.NewRequest("POST", "user/gpg_keys", gpgKey) if err != nil { return nil, nil, err diff --git a/github/users_test.go b/github/users_test.go index 251744725ef..db72afa8a9b 100644 --- a/github/users_test.go +++ b/github/users_test.go @@ -170,3 +170,54 @@ func TestUsersService_ListAll(t *testing.T) { t.Errorf("Users.ListAll returned %+v, want %+v", users, want) } } + +func TestUsersService_ListInvitations(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/repository_invitations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeRepositoryInvitationsPreview) + fmt.Fprintf(w, `[{"id":1}, {"id":2}]`) + }) + + got, _, err := client.Users.ListInvitations() + if err != nil { + t.Errorf("Users.ListInvitations returned error: %v", err) + } + + want := []*RepositoryInvitation{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(got, want) { + t.Errorf("Users.ListInvitations = %+v, want %+v", got, want) + } +} + +func TestUsersService_AcceptInvitation(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/repository_invitations/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + testHeader(t, r, "Accept", mediaTypeRepositoryInvitationsPreview) + w.WriteHeader(http.StatusNoContent) + }) + + if _, err := client.Users.AcceptInvitation(1); err != nil { + t.Errorf("Users.AcceptInvitation returned error: %v", err) + } +} + +func TestUsersService_DeclineInvitation(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/repository_invitations/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeRepositoryInvitationsPreview) + w.WriteHeader(http.StatusNoContent) + }) + + if _, err := client.Users.DeclineInvitation(1); err != nil { + t.Errorf("Users.DeclineInvitation returned error: %v", err) + } +}