1package github
2
3import (
4	"context"
5	"fmt"
6	"net/http"
7
8	ghb "github.com/google/go-github/v26/github"
9	"github.com/wtfutil/wtf/utils"
10	"golang.org/x/oauth2"
11)
12
13type GithubRepo struct {
14	apiKey    string
15	baseURL   string
16	uploadURL string
17
18	Name         string
19	Owner        string
20	PullRequests []*ghb.PullRequest
21	RemoteRepo   *ghb.Repository
22}
23
24func NewGithubRepo(name, owner, apiKey, baseURL, uploadURL string) *GithubRepo {
25	repo := GithubRepo{
26		Name:  name,
27		Owner: owner,
28
29		apiKey:    apiKey,
30		baseURL:   baseURL,
31		uploadURL: uploadURL,
32	}
33
34	return &repo
35}
36
37func (repo *GithubRepo) Open() {
38	utils.OpenFile(*repo.RemoteRepo.HTMLURL)
39}
40
41// Refresh reloads the github data via the Github API
42func (repo *GithubRepo) Refresh() {
43	repo.PullRequests, _ = repo.loadPullRequests()
44	repo.RemoteRepo, _ = repo.loadRemoteRepository()
45}
46
47/* -------------------- Counts -------------------- */
48
49func (repo *GithubRepo) IssueCount() int {
50	if repo.RemoteRepo == nil {
51		return 0
52	}
53
54	return *repo.RemoteRepo.OpenIssuesCount
55}
56
57func (repo *GithubRepo) PullRequestCount() int {
58	return len(repo.PullRequests)
59}
60
61func (repo *GithubRepo) StarCount() int {
62	if repo.RemoteRepo == nil {
63		return 0
64	}
65
66	return *repo.RemoteRepo.StargazersCount
67}
68
69/* -------------------- Unexported Functions -------------------- */
70
71func (repo *GithubRepo) isGitHubEnterprise() bool {
72	if len(repo.baseURL) > 0 {
73		if len(repo.uploadURL) == 0 {
74			repo.uploadURL = repo.baseURL
75		}
76		return true
77	}
78	return false
79}
80
81func (repo *GithubRepo) oauthClient() *http.Client {
82	tokenService := oauth2.StaticTokenSource(
83		&oauth2.Token{AccessToken: repo.apiKey},
84	)
85
86	return oauth2.NewClient(context.Background(), tokenService)
87}
88
89func (repo *GithubRepo) githubClient() (*ghb.Client, error) {
90	oauthClient := repo.oauthClient()
91
92	if repo.isGitHubEnterprise() {
93		return ghb.NewEnterpriseClient(repo.baseURL, repo.uploadURL, oauthClient)
94	}
95
96	return ghb.NewClient(oauthClient), nil
97}
98
99// myPullRequests returns a list of pull requests created by username on this repo
100func (repo *GithubRepo) myPullRequests(username string, showStatus bool) []*ghb.PullRequest {
101	prs := []*ghb.PullRequest{}
102
103	for _, pr := range repo.PullRequests {
104		user := *pr.User
105
106		if *user.Login == username {
107			prs = append(prs, pr)
108		}
109	}
110
111	if showStatus {
112		prs = repo.individualPRs(prs)
113	}
114
115	return prs
116}
117
118// individualPRs takes a list of pull requests (presumably returned from
119// github.PullRequests.List) and fetches them individually to get more detailed
120// status info on each. see: https://developer.github.com/v3/git/#checking-mergeability-of-pull-requests
121func (repo *GithubRepo) individualPRs(prs []*ghb.PullRequest) []*ghb.PullRequest {
122	github, err := repo.githubClient()
123	if err != nil {
124		return prs
125	}
126
127	var ret []*ghb.PullRequest
128	for i := range prs {
129		pr, _, err := github.PullRequests.Get(context.Background(), repo.Owner, repo.Name, prs[i].GetNumber())
130		if err != nil {
131			// worst case, just keep the original one
132			ret = append(ret, prs[i])
133		} else {
134			ret = append(ret, pr)
135		}
136	}
137	return ret
138}
139
140// myReviewRequests returns a list of pull requests for which username has been
141// requested to do a code review
142func (repo *GithubRepo) myReviewRequests(username string) []*ghb.PullRequest {
143	prs := []*ghb.PullRequest{}
144
145	for _, pr := range repo.PullRequests {
146		for _, reviewer := range pr.RequestedReviewers {
147			if *reviewer.Login == username {
148				prs = append(prs, pr)
149			}
150		}
151	}
152
153	return prs
154}
155
156func (repo *GithubRepo) customIssueQuery(filter string, perPage int) *ghb.IssuesSearchResult {
157	github, err := repo.githubClient()
158	if err != nil {
159		return nil
160	}
161
162	opts := &ghb.SearchOptions{}
163	if perPage != 0 {
164		opts.ListOptions.PerPage = perPage
165	}
166
167	prs, _, _ := github.Search.Issues(context.Background(), fmt.Sprintf("%s repo:%s/%s", filter, repo.Owner, repo.Name), opts)
168	return prs
169}
170
171func (repo *GithubRepo) loadPullRequests() ([]*ghb.PullRequest, error) {
172	github, err := repo.githubClient()
173	if err != nil {
174		return nil, err
175	}
176
177	opts := &ghb.PullRequestListOptions{}
178	opts.ListOptions.PerPage = 100
179
180	prs, _, err := github.PullRequests.List(context.Background(), repo.Owner, repo.Name, opts)
181
182	if err != nil {
183		return nil, err
184	}
185
186	return prs, nil
187}
188
189func (repo *GithubRepo) loadRemoteRepository() (*ghb.Repository, error) {
190	github, err := repo.githubClient()
191
192	if err != nil {
193		return nil, err
194	}
195
196	repository, _, err := github.Repositories.Get(context.Background(), repo.Owner, repo.Name)
197
198	if err != nil {
199		return nil, err
200	}
201
202	return repository, nil
203}
204