1// Copyright 2018 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	"strings"
12	"time"
13)
14
15// TeamsService provides access to the team-related functions
16// in the GitHub API.
17//
18// GitHub API docs: https://developer.github.com/v3/teams/
19type TeamsService service
20
21// Team represents a team within a GitHub organization. Teams are used to
22// manage access to an organization's repositories.
23type Team struct {
24	ID          *int64  `json:"id,omitempty"`
25	Name        *string `json:"name,omitempty"`
26	Description *string `json:"description,omitempty"`
27	URL         *string `json:"url,omitempty"`
28	Slug        *string `json:"slug,omitempty"`
29
30	// Permission specifies the default permission for repositories owned by the team.
31	Permission *string `json:"permission,omitempty"`
32
33	// Privacy identifies the level of privacy this team should have.
34	// Possible values are:
35	//     secret - only visible to organization owners and members of this team
36	//     closed - visible to all members of this organization
37	// Default is "secret".
38	Privacy *string `json:"privacy,omitempty"`
39
40	MembersCount    *int          `json:"members_count,omitempty"`
41	ReposCount      *int          `json:"repos_count,omitempty"`
42	Organization    *Organization `json:"organization,omitempty"`
43	MembersURL      *string       `json:"members_url,omitempty"`
44	RepositoriesURL *string       `json:"repositories_url,omitempty"`
45	Parent          *Team         `json:"parent,omitempty"`
46
47	// LDAPDN is only available in GitHub Enterprise and when the team
48	// membership is synchronized with LDAP.
49	LDAPDN *string `json:"ldap_dn,omitempty"`
50}
51
52func (t Team) String() string {
53	return Stringify(t)
54}
55
56// Invitation represents a team member's invitation status.
57type Invitation struct {
58	ID    *int64  `json:"id,omitempty"`
59	Login *string `json:"login,omitempty"`
60	Email *string `json:"email,omitempty"`
61	// Role can be one of the values - 'direct_member', 'admin', 'billing_manager', 'hiring_manager', or 'reinstate'.
62	Role              *string    `json:"role,omitempty"`
63	CreatedAt         *time.Time `json:"created_at,omitempty"`
64	Inviter           *User      `json:"inviter,omitempty"`
65	TeamCount         *int       `json:"team_count,omitempty"`
66	InvitationTeamURL *string    `json:"invitation_team_url,omitempty"`
67}
68
69func (i Invitation) String() string {
70	return Stringify(i)
71}
72
73// ListTeams lists all of the teams for an organization.
74//
75// GitHub API docs: https://developer.github.com/v3/teams/#list-teams
76func (s *TeamsService) ListTeams(ctx context.Context, org string, opt *ListOptions) ([]*Team, *Response, error) {
77	u := fmt.Sprintf("orgs/%v/teams", org)
78	u, err := addOptions(u, opt)
79	if err != nil {
80		return nil, nil, err
81	}
82
83	req, err := s.client.NewRequest("GET", u, nil)
84	if err != nil {
85		return nil, nil, err
86	}
87
88	// TODO: remove custom Accept header when this API fully launches.
89	req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
90
91	var teams []*Team
92	resp, err := s.client.Do(ctx, req, &teams)
93	if err != nil {
94		return nil, resp, err
95	}
96
97	return teams, resp, nil
98}
99
100// GetTeam fetches a team by ID.
101//
102// GitHub API docs: https://developer.github.com/v3/teams/#get-team
103func (s *TeamsService) GetTeam(ctx context.Context, team int64) (*Team, *Response, error) {
104	u := fmt.Sprintf("teams/%v", team)
105	req, err := s.client.NewRequest("GET", u, nil)
106	if err != nil {
107		return nil, nil, err
108	}
109
110	// TODO: remove custom Accept header when this API fully launches.
111	req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
112
113	t := new(Team)
114	resp, err := s.client.Do(ctx, req, t)
115	if err != nil {
116		return nil, resp, err
117	}
118
119	return t, resp, nil
120}
121
122// NewTeam represents a team to be created or modified.
123type NewTeam struct {
124	Name         string   `json:"name"` // Name of the team. (Required.)
125	Description  *string  `json:"description,omitempty"`
126	Maintainers  []string `json:"maintainers,omitempty"`
127	RepoNames    []string `json:"repo_names,omitempty"`
128	ParentTeamID *int64   `json:"parent_team_id,omitempty"`
129
130	// Deprecated: Permission is deprecated when creating or editing a team in an org
131	// using the new GitHub permission model. It no longer identifies the
132	// permission a team has on its repos, but only specifies the default
133	// permission a repo is initially added with. Avoid confusion by
134	// specifying a permission value when calling AddTeamRepo.
135	Permission *string `json:"permission,omitempty"`
136
137	// Privacy identifies the level of privacy this team should have.
138	// Possible values are:
139	//     secret - only visible to organization owners and members of this team
140	//     closed - visible to all members of this organization
141	// Default is "secret".
142	Privacy *string `json:"privacy,omitempty"`
143
144	// LDAPDN may be used in GitHub Enterprise when the team membership
145	// is synchronized with LDAP.
146	LDAPDN *string `json:"ldap_dn,omitempty"`
147}
148
149func (s NewTeam) String() string {
150	return Stringify(s)
151}
152
153// CreateTeam creates a new team within an organization.
154//
155// GitHub API docs: https://developer.github.com/v3/teams/#create-team
156func (s *TeamsService) CreateTeam(ctx context.Context, org string, team NewTeam) (*Team, *Response, error) {
157	u := fmt.Sprintf("orgs/%v/teams", org)
158	req, err := s.client.NewRequest("POST", u, team)
159	if err != nil {
160		return nil, nil, err
161	}
162
163	// TODO: remove custom Accept header when this API fully launches.
164	req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
165
166	t := new(Team)
167	resp, err := s.client.Do(ctx, req, t)
168	if err != nil {
169		return nil, resp, err
170	}
171
172	return t, resp, nil
173}
174
175// EditTeam edits a team.
176//
177// GitHub API docs: https://developer.github.com/v3/teams/#edit-team
178func (s *TeamsService) EditTeam(ctx context.Context, id int64, team NewTeam) (*Team, *Response, error) {
179	u := fmt.Sprintf("teams/%v", id)
180	req, err := s.client.NewRequest("PATCH", u, team)
181	if err != nil {
182		return nil, nil, err
183	}
184
185	// TODO: remove custom Accept header when this API fully launches.
186	req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
187
188	t := new(Team)
189	resp, err := s.client.Do(ctx, req, t)
190	if err != nil {
191		return nil, resp, err
192	}
193
194	return t, resp, nil
195}
196
197// DeleteTeam deletes a team.
198//
199// GitHub API docs: https://developer.github.com/v3/teams/#delete-team
200func (s *TeamsService) DeleteTeam(ctx context.Context, team int64) (*Response, error) {
201	u := fmt.Sprintf("teams/%v", team)
202	req, err := s.client.NewRequest("DELETE", u, nil)
203	if err != nil {
204		return nil, err
205	}
206
207	req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
208
209	return s.client.Do(ctx, req, nil)
210}
211
212// ListChildTeams lists child teams for a team.
213//
214// GitHub API docs: https://developer.github.com/v3/teams/#list-child-teams
215func (s *TeamsService) ListChildTeams(ctx context.Context, teamID int64, opt *ListOptions) ([]*Team, *Response, error) {
216	u := fmt.Sprintf("teams/%v/teams", teamID)
217	u, err := addOptions(u, opt)
218	if err != nil {
219		return nil, nil, err
220	}
221
222	req, err := s.client.NewRequest("GET", u, nil)
223	if err != nil {
224		return nil, nil, err
225	}
226
227	req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
228
229	var teams []*Team
230	resp, err := s.client.Do(ctx, req, &teams)
231	if err != nil {
232		return nil, resp, err
233	}
234
235	return teams, resp, nil
236}
237
238// ListTeamRepos lists the repositories that the specified team has access to.
239//
240// GitHub API docs: https://developer.github.com/v3/teams/#list-team-repos
241func (s *TeamsService) ListTeamRepos(ctx context.Context, team int64, opt *ListOptions) ([]*Repository, *Response, error) {
242	u := fmt.Sprintf("teams/%v/repos", team)
243	u, err := addOptions(u, opt)
244	if err != nil {
245		return nil, nil, err
246	}
247
248	req, err := s.client.NewRequest("GET", u, nil)
249	if err != nil {
250		return nil, nil, err
251	}
252
253	// TODO: remove custom Accept header when topics API fully launches.
254	headers := []string{mediaTypeTopicsPreview, mediaTypeNestedTeamsPreview}
255	req.Header.Set("Accept", strings.Join(headers, ", "))
256
257	var repos []*Repository
258	resp, err := s.client.Do(ctx, req, &repos)
259	if err != nil {
260		return nil, resp, err
261	}
262
263	return repos, resp, nil
264}
265
266// IsTeamRepo checks if a team manages the specified repository. If the
267// repository is managed by team, a Repository is returned which includes the
268// permissions team has for that repo.
269//
270// GitHub API docs: https://developer.github.com/v3/teams/#check-if-a-team-manages-a-repository
271func (s *TeamsService) IsTeamRepo(ctx context.Context, team int64, owner string, repo string) (*Repository, *Response, error) {
272	u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo)
273	req, err := s.client.NewRequest("GET", u, nil)
274	if err != nil {
275		return nil, nil, err
276	}
277
278	headers := []string{mediaTypeOrgPermissionRepo, mediaTypeNestedTeamsPreview}
279	req.Header.Set("Accept", strings.Join(headers, ", "))
280
281	repository := new(Repository)
282	resp, err := s.client.Do(ctx, req, repository)
283	if err != nil {
284		return nil, resp, err
285	}
286
287	return repository, resp, nil
288}
289
290// TeamAddTeamRepoOptions specifies the optional parameters to the
291// TeamsService.AddTeamRepo method.
292type TeamAddTeamRepoOptions struct {
293	// Permission specifies the permission to grant the team on this repository.
294	// Possible values are:
295	//     pull - team members can pull, but not push to or administer this repository
296	//     push - team members can pull and push, but not administer this repository
297	//     admin - team members can pull, push and administer this repository
298	//
299	// If not specified, the team's permission attribute will be used.
300	Permission string `json:"permission,omitempty"`
301}
302
303// AddTeamRepo adds a repository to be managed by the specified team. The
304// specified repository must be owned by the organization to which the team
305// belongs, or a direct fork of a repository owned by the organization.
306//
307// GitHub API docs: https://developer.github.com/v3/teams/#add-team-repo
308func (s *TeamsService) AddTeamRepo(ctx context.Context, team int64, owner string, repo string, opt *TeamAddTeamRepoOptions) (*Response, error) {
309	u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo)
310	req, err := s.client.NewRequest("PUT", u, opt)
311	if err != nil {
312		return nil, err
313	}
314
315	return s.client.Do(ctx, req, nil)
316}
317
318// RemoveTeamRepo removes a repository from being managed by the specified
319// team. Note that this does not delete the repository, it just removes it
320// from the team.
321//
322// GitHub API docs: https://developer.github.com/v3/teams/#remove-team-repo
323func (s *TeamsService) RemoveTeamRepo(ctx context.Context, team int64, owner string, repo string) (*Response, error) {
324	u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo)
325	req, err := s.client.NewRequest("DELETE", u, nil)
326	if err != nil {
327		return nil, err
328	}
329
330	return s.client.Do(ctx, req, nil)
331}
332
333// ListUserTeams lists a user's teams
334// GitHub API docs: https://developer.github.com/v3/teams/#list-user-teams
335func (s *TeamsService) ListUserTeams(ctx context.Context, opt *ListOptions) ([]*Team, *Response, error) {
336	u := "user/teams"
337	u, err := addOptions(u, opt)
338	if err != nil {
339		return nil, nil, err
340	}
341
342	req, err := s.client.NewRequest("GET", u, nil)
343	if err != nil {
344		return nil, nil, err
345	}
346
347	// TODO: remove custom Accept header when this API fully launches.
348	req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
349
350	var teams []*Team
351	resp, err := s.client.Do(ctx, req, &teams)
352	if err != nil {
353		return nil, resp, err
354	}
355
356	return teams, resp, nil
357}
358