1// Copyright 2013 The go-github AUTHORS. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file.
5
6package github
7
8import (
9	"context"
10	"fmt"
11)
12
13// UsersService handles communication with the user related
14// methods of the GitHub API.
15//
16// GitHub API docs: https://developer.github.com/v3/users/
17type UsersService service
18
19// User represents a GitHub user.
20type User struct {
21	Login                   *string    `json:"login,omitempty"`
22	ID                      *int64     `json:"id,omitempty"`
23	NodeID                  *string    `json:"node_id,omitempty"`
24	AvatarURL               *string    `json:"avatar_url,omitempty"`
25	HTMLURL                 *string    `json:"html_url,omitempty"`
26	GravatarID              *string    `json:"gravatar_id,omitempty"`
27	Name                    *string    `json:"name,omitempty"`
28	Company                 *string    `json:"company,omitempty"`
29	Blog                    *string    `json:"blog,omitempty"`
30	Location                *string    `json:"location,omitempty"`
31	Email                   *string    `json:"email,omitempty"`
32	Hireable                *bool      `json:"hireable,omitempty"`
33	Bio                     *string    `json:"bio,omitempty"`
34	PublicRepos             *int       `json:"public_repos,omitempty"`
35	PublicGists             *int       `json:"public_gists,omitempty"`
36	Followers               *int       `json:"followers,omitempty"`
37	Following               *int       `json:"following,omitempty"`
38	CreatedAt               *Timestamp `json:"created_at,omitempty"`
39	UpdatedAt               *Timestamp `json:"updated_at,omitempty"`
40	SuspendedAt             *Timestamp `json:"suspended_at,omitempty"`
41	Type                    *string    `json:"type,omitempty"`
42	SiteAdmin               *bool      `json:"site_admin,omitempty"`
43	TotalPrivateRepos       *int       `json:"total_private_repos,omitempty"`
44	OwnedPrivateRepos       *int       `json:"owned_private_repos,omitempty"`
45	PrivateGists            *int       `json:"private_gists,omitempty"`
46	DiskUsage               *int       `json:"disk_usage,omitempty"`
47	Collaborators           *int       `json:"collaborators,omitempty"`
48	TwoFactorAuthentication *bool      `json:"two_factor_authentication,omitempty"`
49	Plan                    *Plan      `json:"plan,omitempty"`
50	LdapDn                  *string    `json:"ldap_dn,omitempty"`
51
52	// API URLs
53	URL               *string `json:"url,omitempty"`
54	EventsURL         *string `json:"events_url,omitempty"`
55	FollowingURL      *string `json:"following_url,omitempty"`
56	FollowersURL      *string `json:"followers_url,omitempty"`
57	GistsURL          *string `json:"gists_url,omitempty"`
58	OrganizationsURL  *string `json:"organizations_url,omitempty"`
59	ReceivedEventsURL *string `json:"received_events_url,omitempty"`
60	ReposURL          *string `json:"repos_url,omitempty"`
61	StarredURL        *string `json:"starred_url,omitempty"`
62	SubscriptionsURL  *string `json:"subscriptions_url,omitempty"`
63
64	// TextMatches is only populated from search results that request text matches
65	// See: search.go and https://developer.github.com/v3/search/#text-match-metadata
66	TextMatches []TextMatch `json:"text_matches,omitempty"`
67
68	// Permissions identifies the permissions that a user has on a given
69	// repository. This is only populated when calling Repositories.ListCollaborators.
70	Permissions *map[string]bool `json:"permissions,omitempty"`
71}
72
73func (u User) String() string {
74	return Stringify(u)
75}
76
77// Get fetches a user. Passing the empty string will fetch the authenticated
78// user.
79//
80// GitHub API docs: https://developer.github.com/v3/users/#get-a-single-user
81// and: https://developer.github.com/v3/users/#get-the-authenticated-user
82func (s *UsersService) Get(ctx context.Context, user string) (*User, *Response, error) {
83	var u string
84	if user != "" {
85		u = fmt.Sprintf("users/%v", user)
86	} else {
87		u = "user"
88	}
89	req, err := s.client.NewRequest("GET", u, nil)
90	if err != nil {
91		return nil, nil, err
92	}
93
94	uResp := new(User)
95	resp, err := s.client.Do(ctx, req, uResp)
96	if err != nil {
97		return nil, resp, err
98	}
99
100	return uResp, resp, nil
101}
102
103// GetByID fetches a user.
104//
105// Note: GetByID uses the undocumented GitHub API endpoint /user/:id.
106func (s *UsersService) GetByID(ctx context.Context, id int64) (*User, *Response, error) {
107	u := fmt.Sprintf("user/%d", id)
108	req, err := s.client.NewRequest("GET", u, nil)
109	if err != nil {
110		return nil, nil, err
111	}
112
113	user := new(User)
114	resp, err := s.client.Do(ctx, req, user)
115	if err != nil {
116		return nil, resp, err
117	}
118
119	return user, resp, nil
120}
121
122// Edit the authenticated user.
123//
124// GitHub API docs: https://developer.github.com/v3/users/#update-the-authenticated-user
125func (s *UsersService) Edit(ctx context.Context, user *User) (*User, *Response, error) {
126	u := "user"
127	req, err := s.client.NewRequest("PATCH", u, user)
128	if err != nil {
129		return nil, nil, err
130	}
131
132	uResp := new(User)
133	resp, err := s.client.Do(ctx, req, uResp)
134	if err != nil {
135		return nil, resp, err
136	}
137
138	return uResp, resp, nil
139}
140
141// HovercardOptions specifies optional parameters to the UsersService.GetHovercard
142// method.
143type HovercardOptions struct {
144	// SubjectType specifies the additional information to be received about the hovercard.
145	// Possible values are: organization, repository, issue, pull_request. (Required when using subject_id.)
146	SubjectType string `url:"subject_type"`
147
148	// SubjectID specifies the ID for the SubjectType. (Required when using subject_type.)
149	SubjectID string `url:"subject_id"`
150}
151
152// Hovercard represents hovercard information about a user.
153type Hovercard struct {
154	Contexts []*UserContext `json:"contexts,omitempty"`
155}
156
157// UserContext represents the contextual information about user.
158type UserContext struct {
159	Message *string `json:"message,omitempty"`
160	Octicon *string `json:"octicon,omitempty"`
161}
162
163// GetHovercard fetches contextual information about user. It requires authentication
164// via Basic Auth or via OAuth with the repo scope.
165//
166// GitHub API docs: https://developer.github.com/v3/users/#get-contextual-information-about-a-user
167func (s *UsersService) GetHovercard(ctx context.Context, user string, opt *HovercardOptions) (*Hovercard, *Response, error) {
168	u := fmt.Sprintf("users/%v/hovercard", user)
169	u, err := addOptions(u, opt)
170	if err != nil {
171		return nil, nil, err
172	}
173
174	req, err := s.client.NewRequest("GET", u, nil)
175	if err != nil {
176		return nil, nil, err
177	}
178
179	// TODO: remove custom Accept header when this API fully launches.
180	req.Header.Set("Accept", mediaTypeHovercardPreview)
181
182	hc := new(Hovercard)
183	resp, err := s.client.Do(ctx, req, hc)
184	if err != nil {
185		return nil, resp, err
186	}
187
188	return hc, resp, nil
189}
190
191// UserListOptions specifies optional parameters to the UsersService.ListAll
192// method.
193type UserListOptions struct {
194	// ID of the last user seen
195	Since int64 `url:"since,omitempty"`
196
197	// Note: Pagination is powered exclusively by the Since parameter,
198	// ListOptions.Page has no effect.
199	// ListOptions.PerPage controls an undocumented GitHub API parameter.
200	ListOptions
201}
202
203// ListAll lists all GitHub users.
204//
205// To paginate through all users, populate 'Since' with the ID of the last user.
206//
207// GitHub API docs: https://developer.github.com/v3/users/#get-all-users
208func (s *UsersService) ListAll(ctx context.Context, opt *UserListOptions) ([]*User, *Response, error) {
209	u, err := addOptions("users", opt)
210	if err != nil {
211		return nil, nil, err
212	}
213
214	req, err := s.client.NewRequest("GET", u, nil)
215	if err != nil {
216		return nil, nil, err
217	}
218
219	var users []*User
220	resp, err := s.client.Do(ctx, req, &users)
221	if err != nil {
222		return nil, resp, err
223	}
224
225	return users, resp, nil
226}
227
228// ListInvitations lists all currently-open repository invitations for the
229// authenticated user.
230//
231// GitHub API docs: https://developer.github.com/v3/repos/invitations/#list-a-users-repository-invitations
232func (s *UsersService) ListInvitations(ctx context.Context, opt *ListOptions) ([]*RepositoryInvitation, *Response, error) {
233	u, err := addOptions("user/repository_invitations", opt)
234	if err != nil {
235		return nil, nil, err
236	}
237
238	req, err := s.client.NewRequest("GET", u, nil)
239	if err != nil {
240		return nil, nil, err
241	}
242
243	invites := []*RepositoryInvitation{}
244	resp, err := s.client.Do(ctx, req, &invites)
245	if err != nil {
246		return nil, resp, err
247	}
248
249	return invites, resp, nil
250}
251
252// AcceptInvitation accepts the currently-open repository invitation for the
253// authenticated user.
254//
255// GitHub API docs: https://developer.github.com/v3/repos/invitations/#accept-a-repository-invitation
256func (s *UsersService) AcceptInvitation(ctx context.Context, invitationID int64) (*Response, error) {
257	u := fmt.Sprintf("user/repository_invitations/%v", invitationID)
258	req, err := s.client.NewRequest("PATCH", u, nil)
259	if err != nil {
260		return nil, err
261	}
262
263	return s.client.Do(ctx, req, nil)
264}
265
266// DeclineInvitation declines the currently-open repository invitation for the
267// authenticated user.
268//
269// GitHub API docs: https://developer.github.com/v3/repos/invitations/#decline-a-repository-invitation
270func (s *UsersService) DeclineInvitation(ctx context.Context, invitationID int64) (*Response, error) {
271	u := fmt.Sprintf("user/repository_invitations/%v", invitationID)
272	req, err := s.client.NewRequest("DELETE", u, nil)
273	if err != nil {
274		return nil, err
275	}
276
277	return s.client.Do(ctx, req, nil)
278}
279