diff --git a/github/apps.go b/github/apps.go index ff3389382ee..a83ea7c2ecb 100644 --- a/github/apps.go +++ b/github/apps.go @@ -5,7 +5,11 @@ package github -import "context" +import ( + "context" + "fmt" + "time" +) // AppsService provides access to the installation related functions // in the GitHub API. @@ -13,6 +17,57 @@ import "context" // GitHub API docs: https://developer.github.com/v3/apps/ type AppsService service +// App represents a GitHub App. +type App struct { + ID *int `json:"id,omitempty"` + Owner *User `json:"owner,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + ExternalURL *string `json:"external_url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} + +// InstallationToken represents an installation token. +type InstallationToken struct { + Token *string `json:"token,omitempty"` + ExpiresAt *time.Time `json:"expires_at,omitempty"` +} + +// Get a single GitHub App. Passing the empty string will get +// the authenticated GitHub App. +// +// Note: appSlug is just the URL-friendly name of your GitHub App. +// You can find this on the settings page for your GitHub App +// (e.g., https://github.com/settings/apps/:app_slug). +// +// GitHub API docs: https://developer.github.com/v3/apps/#get-a-single-github-app +func (s *AppsService) Get(ctx context.Context, appSlug string) (*App, *Response, error) { + var u string + if appSlug != "" { + u = fmt.Sprintf("apps/%v", appSlug) + } else { + u = "app" + } + + 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", mediaTypeIntegrationPreview) + + app := new(App) + resp, err := s.client.Do(ctx, req, app) + if err != nil { + return nil, resp, err + } + + return app, resp, nil +} + // ListInstallations lists the installations that the current GitHub App has. // // GitHub API docs: https://developer.github.com/v3/apps/#find-installations @@ -38,3 +93,77 @@ func (s *AppsService) ListInstallations(ctx context.Context, opt *ListOptions) ( return i, resp, nil } + +// GetInstallation returns the specified installation. +// +// GitHub API docs: https://developer.github.com/v3/apps/#get-a-single-installation +func (s *AppsService) GetInstallation(ctx context.Context, id int) (*Installation, *Response, error) { + u := fmt.Sprintf("app/installations/%v", id) + + 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", mediaTypeIntegrationPreview) + + i := new(Installation) + resp, err := s.client.Do(ctx, req, i) + if err != nil { + return nil, resp, err + } + + return i, resp, nil +} + +// ListUserInstallations lists installations that are accessible to the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/apps/#list-installations-for-user +func (s *AppsService) ListUserInstallations(ctx context.Context, opt *ListOptions) ([]*Installation, *Response, error) { + u, err := addOptions("user/installations", 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", mediaTypeIntegrationPreview) + + var i struct { + Installations []*Installation `json:"installations"` + } + resp, err := s.client.Do(ctx, req, &i) + if err != nil { + return nil, resp, err + } + + return i.Installations, resp, nil +} + +// CreateInstallationToken creates a new installation token. +// +// GitHub API docs: https://developer.github.com/v3/apps/#create-a-new-installation-token +func (s *AppsService) CreateInstallationToken(ctx context.Context, id int) (*InstallationToken, *Response, error) { + u := fmt.Sprintf("installations/%v/access_tokens", id) + + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeIntegrationPreview) + + t := new(InstallationToken) + resp, err := s.client.Do(ctx, req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, nil +} diff --git a/github/apps_installation.go b/github/apps_installation.go index 5c932919fff..ccac316c4ec 100644 --- a/github/apps_installation.go +++ b/github/apps_installation.go @@ -51,6 +51,33 @@ func (s *AppsService) ListRepos(ctx context.Context, opt *ListOptions) ([]*Repos return r.Repositories, resp, nil } +// ListUserRepos lists repositories that are accessible +// to the authenticated user for an installation. +// +// GitHub API docs: https://developer.github.com/v3/apps/installations/#list-repositories-accessible-to-the-user-for-an-installation +func (s *AppsService) ListUserRepos(ctx context.Context, id int, opt *ListOptions) ([]*Repository, *Response, error) { + u := fmt.Sprintf("user/installations/%v/repositories", id) + 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 + } + + var r struct { + Repositories []*Repository `json:"repositories"` + } + resp, err := s.client.Do(ctx, req, &r) + if err != nil { + return nil, resp, err + } + + return r.Repositories, resp, nil +} + // AddRepository adds a single repository to an installation. // // GitHub API docs: https://developer.github.com/v3/apps/installations/#add-repository-to-installation diff --git a/github/apps_installation_test.go b/github/apps_installation_test.go index 69598cfe5f8..ec796cd3384 100644 --- a/github/apps_installation_test.go +++ b/github/apps_installation_test.go @@ -39,6 +39,31 @@ func TestAppsService_ListRepos(t *testing.T) { } } +func TestAppsService_ListUserRepos(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/installations/1/repositories", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "1", + "per_page": "2", + }) + fmt.Fprint(w, `{"repositories": [{"id":1}]}`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + repositories, _, err := client.Apps.ListUserRepos(context.Background(), 1, opt) + if err != nil { + t.Errorf("Apps.ListUserRepos returned error: %v", err) + } + + want := []*Repository{{ID: Int(1)}} + if !reflect.DeepEqual(repositories, want) { + t.Errorf("Apps.ListUserRepos returned %+v, want %+v", repositories, want) + } +} + func TestAppsService_AddRepository(t *testing.T) { setup() defer teardown() diff --git a/github/apps_test.go b/github/apps_test.go index f847b84271c..9ef1f468291 100644 --- a/github/apps_test.go +++ b/github/apps_test.go @@ -13,6 +13,48 @@ import ( "testing" ) +func TestAppsService_Get_authenticatedApp(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/app", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeIntegrationPreview) + fmt.Fprint(w, `{"id":1}`) + }) + + app, _, err := client.Apps.Get(context.Background(), "") + if err != nil { + t.Errorf("Apps.Get returned error: %v", err) + } + + want := &App{ID: Int(1)} + if !reflect.DeepEqual(app, want) { + t.Errorf("Apps.Get returned %+v, want %+v", app, want) + } +} + +func TestAppsService_Get_specifiedApp(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/apps/a", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeIntegrationPreview) + fmt.Fprint(w, `{"html_url":"https://github.com/apps/a"}`) + }) + + app, _, err := client.Apps.Get(context.Background(), "a") + if err != nil { + t.Errorf("Apps.Get returned error: %v", err) + } + + want := &App{HTMLURL: String("https://github.com/apps/a")} + if !reflect.DeepEqual(app, want) { + t.Errorf("Apps.Get returned %+v, want %+v", *app.HTMLURL, *want.HTMLURL) + } +} + func TestAppsService_ListInstallations(t *testing.T) { setup() defer teardown() @@ -38,3 +80,71 @@ func TestAppsService_ListInstallations(t *testing.T) { t.Errorf("Apps.ListInstallations returned %+v, want %+v", installations, want) } } + +func TestAppsService_GetInstallation(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/app/installations/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeIntegrationPreview) + fmt.Fprint(w, `{"id":1}`) + }) + + installation, _, err := client.Apps.GetInstallation(context.Background(), 1) + if err != nil { + t.Errorf("Apps.GetInstallation returned error: %v", err) + } + + want := &Installation{ID: Int(1)} + if !reflect.DeepEqual(installation, want) { + t.Errorf("Apps.GetInstallation returned %+v, want %+v", installation, want) + } +} + +func TestAppsService_ListUserInstallations(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/installations", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeIntegrationPreview) + testFormValues(t, r, values{ + "page": "1", + "per_page": "2", + }) + fmt.Fprint(w, `{"installations":[{"id":1}]}`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + installations, _, err := client.Apps.ListUserInstallations(context.Background(), opt) + if err != nil { + t.Errorf("Apps.ListUserInstallations returned error: %v", err) + } + + want := []*Installation{{ID: Int(1)}} + if !reflect.DeepEqual(installations, want) { + t.Errorf("Apps.ListUserInstallations returned %+v, want %+v", installations, want) + } +} + +func TestAppsService_CreateInstallationToken(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/installations/1/access_tokens", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeIntegrationPreview) + fmt.Fprint(w, `{"token":"t"}`) + }) + + token, _, err := client.Apps.CreateInstallationToken(context.Background(), 1) + if err != nil { + t.Errorf("Apps.CreateInstallationToken returned error: %v", err) + } + + want := &InstallationToken{Token: String("t")} + if !reflect.DeepEqual(token, want) { + t.Errorf("Apps.CreateInstallationToken returned %+v, want %+v", token, want) + } +} diff --git a/github/github-accessors.go b/github/github-accessors.go index 6da009baf1f..4e0ce60017d 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -36,6 +36,62 @@ func (a *APIMeta) GetVerifiablePasswordAuthentication() bool { return *a.VerifiablePasswordAuthentication } +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (a *App) GetCreatedAt() time.Time { + if a == nil || a.CreatedAt == nil { + return time.Time{} + } + return *a.CreatedAt +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (a *App) GetDescription() string { + if a == nil || a.Description == nil { + return "" + } + return *a.Description +} + +// GetExternalURL returns the ExternalURL field if it's non-nil, zero value otherwise. +func (a *App) GetExternalURL() string { + if a == nil || a.ExternalURL == nil { + return "" + } + return *a.ExternalURL +} + +// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise. +func (a *App) GetHTMLURL() string { + if a == nil || a.HTMLURL == nil { + return "" + } + return *a.HTMLURL +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (a *App) GetID() int { + if a == nil || a.ID == nil { + return 0 + } + return *a.ID +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (a *App) GetName() string { + if a == nil || a.Name == nil { + return "" + } + return *a.Name +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (a *App) GetUpdatedAt() time.Time { + if a == nil || a.UpdatedAt == nil { + return time.Time{} + } + return *a.UpdatedAt +} + // GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. func (a *Authorization) GetCreatedAt() Timestamp { if a == nil || a.CreatedAt == nil { @@ -2036,6 +2092,22 @@ func (i *InstallationRepositoriesEvent) GetRepositorySelection() string { return *i.RepositorySelection } +// GetExpiresAt returns the ExpiresAt field if it's non-nil, zero value otherwise. +func (i *InstallationToken) GetExpiresAt() time.Time { + if i == nil || i.ExpiresAt == nil { + return time.Time{} + } + return *i.ExpiresAt +} + +// GetToken returns the Token field if it's non-nil, zero value otherwise. +func (i *InstallationToken) GetToken() string { + if i == nil || i.Token == nil { + return "" + } + return *i.Token +} + // GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. func (i *Invitation) GetCreatedAt() time.Time { if i == nil || i.CreatedAt == nil {