Skip to content

add support for new repository invitations #373

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 20, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions github/github.go
Original file line number Diff line number Diff line change
@@ -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.
7 changes: 5 additions & 2 deletions github/repos_collaborators.go
Original file line number Diff line number Diff line change
@@ -58,20 +58,23 @@ 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)
if err != nil {
return nil, err
}

// TODO: remove custom Accept header when this API fully launches.
req.Header.Set("Accept", mediaTypeRepositoryInvitationsPreview)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's interesting that this takes an existing, previously stable endpoint, and changes it slightly to use the (potentially less stable) preview API. (Usually preview APIs are for new endpoints.)

I think it's fine per our discussion in #376 because people who have really really specific needs will be vendoring this library anyway.


return s.client.Do(req, nil)
}

1 change: 1 addition & 0 deletions github/repos_collaborators_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
91 changes: 91 additions & 0 deletions github/repos_invitations.go
Original file line number Diff line number Diff line change
@@ -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
}
73 changes: 73 additions & 0 deletions github/repos_invitations_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
56 changes: 56 additions & 0 deletions github/users.go
Original file line number Diff line number Diff line change
@@ -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)
}
6 changes: 2 additions & 4 deletions github/users_gpg_keys.go
Original file line number Diff line number Diff line change
@@ -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
51 changes: 51 additions & 0 deletions github/users_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}