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	"strings"
12	"time"
13)
14
15// Team represents a team within a GitHub organization. Teams are used to
16// manage access to an organization's repositories.
17type Team struct {
18	ID          *int64  `json:"id,omitempty"`
19	Name        *string `json:"name,omitempty"`
20	Description *string `json:"description,omitempty"`
21	URL         *string `json:"url,omitempty"`
22	Slug        *string `json:"slug,omitempty"`
23
24	// Permission specifies the default permission for repositories owned by the team.
25	Permission *string `json:"permission,omitempty"`
26
27	// Privacy identifies the level of privacy this team should have.
28	// Possible values are:
29	//     secret - only visible to organization owners and members of this team
30	//     closed - visible to all members of this organization
31	// Default is "secret".
32	Privacy *string `json:"privacy,omitempty"`
33
34	MembersCount    *int          `json:"members_count,omitempty"`
35	ReposCount      *int          `json:"repos_count,omitempty"`
36	Organization    *Organization `json:"organization,omitempty"`
37	MembersURL      *string       `json:"members_url,omitempty"`
38	RepositoriesURL *string       `json:"repositories_url,omitempty"`
39	Parent          *Team         `json:"parent,omitempty"`
40
41	// LDAPDN is only available in GitHub Enterprise and when the team
42	// membership is synchronized with LDAP.
43	LDAPDN *string `json:"ldap_dn,omitempty"`
44}
45
46func (t Team) String() string {
47	return Stringify(t)
48}
49
50// Invitation represents a team member's invitation status.
51type Invitation struct {
52	ID    *int64  `json:"id,omitempty"`
53	Login *string `json:"login,omitempty"`
54	Email *string `json:"email,omitempty"`
55	// Role can be one of the values - 'direct_member', 'admin', 'billing_manager', 'hiring_manager', or 'reinstate'.
56	Role      *string    `json:"role,omitempty"`
57	CreatedAt *time.Time `json:"created_at,omitempty"`
58	Inviter   *User      `json:"inviter,omitempty"`
59}
60
61func (i Invitation) String() string {
62	return Stringify(i)
63}
64
65// ListTeams lists all of the teams for an organization.
66//
67// GitHub API docs: https://developer.github.com/v3/orgs/teams/#list-teams
68func (s *OrganizationsService) ListTeams(ctx context.Context, org string, opt *ListOptions) ([]*Team, *Response, error) {
69	u := fmt.Sprintf("orgs/%v/teams", org)
70	u, err := addOptions(u, opt)
71	if err != nil {
72		return nil, nil, err
73	}
74
75	req, err := s.client.NewRequest("GET", u, nil)
76	if err != nil {
77		return nil, nil, err
78	}
79
80	// TODO: remove custom Accept header when this API fully launches.
81	req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
82
83	var teams []*Team
84	resp, err := s.client.Do(ctx, req, &teams)
85	if err != nil {
86		return nil, resp, err
87	}
88
89	return teams, resp, nil
90}
91
92// GetTeam fetches a team by ID.
93//
94// GitHub API docs: https://developer.github.com/v3/orgs/teams/#get-team
95func (s *OrganizationsService) GetTeam(ctx context.Context, team int64) (*Team, *Response, error) {
96	u := fmt.Sprintf("teams/%v", team)
97	req, err := s.client.NewRequest("GET", u, nil)
98	if err != nil {
99		return nil, nil, err
100	}
101
102	// TODO: remove custom Accept header when this API fully launches.
103	req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
104
105	t := new(Team)
106	resp, err := s.client.Do(ctx, req, t)
107	if err != nil {
108		return nil, resp, err
109	}
110
111	return t, resp, nil
112}
113
114// NewTeam represents a team to be created or modified.
115type NewTeam struct {
116	Name         string   `json:"name"` // Name of the team. (Required.)
117	Description  *string  `json:"description,omitempty"`
118	Maintainers  []string `json:"maintainers,omitempty"`
119	RepoNames    []string `json:"repo_names,omitempty"`
120	ParentTeamID *int64   `json:"parent_team_id,omitempty"`
121
122	// Deprecated: Permission is deprecated when creating or editing a team in an org
123	// using the new GitHub permission model. It no longer identifies the
124	// permission a team has on its repos, but only specifies the default
125	// permission a repo is initially added with. Avoid confusion by
126	// specifying a permission value when calling AddTeamRepo.
127	Permission *string `json:"permission,omitempty"`
128
129	// Privacy identifies the level of privacy this team should have.
130	// Possible values are:
131	//     secret - only visible to organization owners and members of this team
132	//     closed - visible to all members of this organization
133	// Default is "secret".
134	Privacy *string `json:"privacy,omitempty"`
135
136	// LDAPDN may be used in GitHub Enterprise when the team membership
137	// is synchronized with LDAP.
138	LDAPDN *string `json:"ldap_dn,omitempty"`
139}
140
141func (s NewTeam) String() string {
142	return Stringify(s)
143}
144
145// CreateTeam creates a new team within an organization.
146//
147// GitHub API docs: https://developer.github.com/v3/orgs/teams/#create-team
148func (s *OrganizationsService) CreateTeam(ctx context.Context, org string, team *NewTeam) (*Team, *Response, error) {
149	u := fmt.Sprintf("orgs/%v/teams", org)
150	req, err := s.client.NewRequest("POST", u, team)
151	if err != nil {
152		return nil, nil, err
153	}
154
155	// TODO: remove custom Accept header when this API fully launches.
156	req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
157
158	t := new(Team)
159	resp, err := s.client.Do(ctx, req, t)
160	if err != nil {
161		return nil, resp, err
162	}
163
164	return t, resp, nil
165}
166
167// EditTeam edits a team.
168//
169// GitHub API docs: https://developer.github.com/v3/orgs/teams/#edit-team
170func (s *OrganizationsService) EditTeam(ctx context.Context, id int64, team *NewTeam) (*Team, *Response, error) {
171	u := fmt.Sprintf("teams/%v", id)
172	req, err := s.client.NewRequest("PATCH", u, team)
173	if err != nil {
174		return nil, nil, err
175	}
176
177	// TODO: remove custom Accept header when this API fully launches.
178	req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
179
180	t := new(Team)
181	resp, err := s.client.Do(ctx, req, t)
182	if err != nil {
183		return nil, resp, err
184	}
185
186	return t, resp, nil
187}
188
189// DeleteTeam deletes a team.
190//
191// GitHub API docs: https://developer.github.com/v3/orgs/teams/#delete-team
192func (s *OrganizationsService) DeleteTeam(ctx context.Context, team int64) (*Response, error) {
193	u := fmt.Sprintf("teams/%v", team)
194	req, err := s.client.NewRequest("DELETE", u, nil)
195	if err != nil {
196		return nil, err
197	}
198
199	req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
200
201	return s.client.Do(ctx, req, nil)
202}
203
204// OrganizationListTeamMembersOptions specifies the optional parameters to the
205// OrganizationsService.ListTeamMembers method.
206type OrganizationListTeamMembersOptions struct {
207	// Role filters members returned by their role in the team. Possible
208	// values are "all", "member", "maintainer". Default is "all".
209	Role string `url:"role,omitempty"`
210
211	ListOptions
212}
213
214// ListChildTeams lists child teams for a team.
215//
216// GitHub API docs: https://developer.github.com/v3/orgs/teams/#list-child-teams
217func (s *OrganizationsService) ListChildTeams(ctx context.Context, teamID int64, opt *ListOptions) ([]*Team, *Response, error) {
218	u := fmt.Sprintf("teams/%v/teams", teamID)
219	u, err := addOptions(u, opt)
220	if err != nil {
221		return nil, nil, err
222	}
223
224	req, err := s.client.NewRequest("GET", u, nil)
225	if err != nil {
226		return nil, nil, err
227	}
228
229	req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
230
231	var teams []*Team
232	resp, err := s.client.Do(ctx, req, &teams)
233	if err != nil {
234		return nil, resp, err
235	}
236
237	return teams, resp, nil
238}
239
240// ListTeamMembers lists all of the users who are members of the specified
241// team.
242//
243// GitHub API docs: https://developer.github.com/v3/orgs/teams/#list-team-members
244func (s *OrganizationsService) ListTeamMembers(ctx context.Context, team int64, opt *OrganizationListTeamMembersOptions) ([]*User, *Response, error) {
245	u := fmt.Sprintf("teams/%v/members", team)
246	u, err := addOptions(u, opt)
247	if err != nil {
248		return nil, nil, err
249	}
250
251	req, err := s.client.NewRequest("GET", u, nil)
252	if err != nil {
253		return nil, nil, err
254	}
255
256	req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
257
258	var members []*User
259	resp, err := s.client.Do(ctx, req, &members)
260	if err != nil {
261		return nil, resp, err
262	}
263
264	return members, resp, nil
265}
266
267// IsTeamMember checks if a user is a member of the specified team.
268//
269// GitHub API docs: https://developer.github.com/v3/orgs/teams/#get-team-member
270//
271// Deprecated: This API has been marked as deprecated in the Github API docs,
272// OrganizationsService.GetTeamMembership method should be used instead.
273func (s *OrganizationsService) IsTeamMember(ctx context.Context, team int64, user string) (bool, *Response, error) {
274	u := fmt.Sprintf("teams/%v/members/%v", team, user)
275	req, err := s.client.NewRequest("GET", u, nil)
276	if err != nil {
277		return false, nil, err
278	}
279
280	resp, err := s.client.Do(ctx, req, nil)
281	member, err := parseBoolResponse(err)
282	return member, resp, err
283}
284
285// ListTeamRepos lists the repositories that the specified team has access to.
286//
287// GitHub API docs: https://developer.github.com/v3/orgs/teams/#list-team-repos
288func (s *OrganizationsService) ListTeamRepos(ctx context.Context, team int64, opt *ListOptions) ([]*Repository, *Response, error) {
289	u := fmt.Sprintf("teams/%v/repos", team)
290	u, err := addOptions(u, opt)
291	if err != nil {
292		return nil, nil, err
293	}
294
295	req, err := s.client.NewRequest("GET", u, nil)
296	if err != nil {
297		return nil, nil, err
298	}
299
300	// TODO: remove custom Accept header when topics API fully launches.
301	headers := []string{mediaTypeTopicsPreview, mediaTypeNestedTeamsPreview}
302	req.Header.Set("Accept", strings.Join(headers, ", "))
303
304	var repos []*Repository
305	resp, err := s.client.Do(ctx, req, &repos)
306	if err != nil {
307		return nil, resp, err
308	}
309
310	return repos, resp, nil
311}
312
313// IsTeamRepo checks if a team manages the specified repository. If the
314// repository is managed by team, a Repository is returned which includes the
315// permissions team has for that repo.
316//
317// GitHub API docs: https://developer.github.com/v3/orgs/teams/#check-if-a-team-manages-a-repository
318func (s *OrganizationsService) IsTeamRepo(ctx context.Context, team int64, owner string, repo string) (*Repository, *Response, error) {
319	u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo)
320	req, err := s.client.NewRequest("GET", u, nil)
321	if err != nil {
322		return nil, nil, err
323	}
324
325	headers := []string{mediaTypeOrgPermissionRepo, mediaTypeNestedTeamsPreview}
326	req.Header.Set("Accept", strings.Join(headers, ", "))
327
328	repository := new(Repository)
329	resp, err := s.client.Do(ctx, req, repository)
330	if err != nil {
331		return nil, resp, err
332	}
333
334	return repository, resp, nil
335}
336
337// OrganizationAddTeamRepoOptions specifies the optional parameters to the
338// OrganizationsService.AddTeamRepo method.
339type OrganizationAddTeamRepoOptions struct {
340	// Permission specifies the permission to grant the team on this repository.
341	// Possible values are:
342	//     pull - team members can pull, but not push to or administer this repository
343	//     push - team members can pull and push, but not administer this repository
344	//     admin - team members can pull, push and administer this repository
345	//
346	// If not specified, the team's permission attribute will be used.
347	Permission string `json:"permission,omitempty"`
348}
349
350// AddTeamRepo adds a repository to be managed by the specified team. The
351// specified repository must be owned by the organization to which the team
352// belongs, or a direct fork of a repository owned by the organization.
353//
354// GitHub API docs: https://developer.github.com/v3/orgs/teams/#add-team-repo
355func (s *OrganizationsService) AddTeamRepo(ctx context.Context, team int64, owner string, repo string, opt *OrganizationAddTeamRepoOptions) (*Response, error) {
356	u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo)
357	req, err := s.client.NewRequest("PUT", u, opt)
358	if err != nil {
359		return nil, err
360	}
361
362	return s.client.Do(ctx, req, nil)
363}
364
365// RemoveTeamRepo removes a repository from being managed by the specified
366// team. Note that this does not delete the repository, it just removes it
367// from the team.
368//
369// GitHub API docs: https://developer.github.com/v3/orgs/teams/#remove-team-repo
370func (s *OrganizationsService) RemoveTeamRepo(ctx context.Context, team int64, owner string, repo string) (*Response, error) {
371	u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo)
372	req, err := s.client.NewRequest("DELETE", u, nil)
373	if err != nil {
374		return nil, err
375	}
376
377	return s.client.Do(ctx, req, nil)
378}
379
380// ListUserTeams lists a user's teams
381// GitHub API docs: https://developer.github.com/v3/orgs/teams/#list-user-teams
382func (s *OrganizationsService) ListUserTeams(ctx context.Context, opt *ListOptions) ([]*Team, *Response, error) {
383	u := "user/teams"
384	u, err := addOptions(u, opt)
385	if err != nil {
386		return nil, nil, err
387	}
388
389	req, err := s.client.NewRequest("GET", u, nil)
390	if err != nil {
391		return nil, nil, err
392	}
393
394	// TODO: remove custom Accept header when this API fully launches.
395	req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
396
397	var teams []*Team
398	resp, err := s.client.Do(ctx, req, &teams)
399	if err != nil {
400		return nil, resp, err
401	}
402
403	return teams, resp, nil
404}
405
406// GetTeamMembership returns the membership status for a user in a team.
407//
408// GitHub API docs: https://developer.github.com/v3/orgs/teams/#get-team-membership
409func (s *OrganizationsService) GetTeamMembership(ctx context.Context, team int64, user string) (*Membership, *Response, error) {
410	u := fmt.Sprintf("teams/%v/memberships/%v", team, user)
411	req, err := s.client.NewRequest("GET", u, nil)
412	if err != nil {
413		return nil, nil, err
414	}
415
416	req.Header.Set("Accept", mediaTypeNestedTeamsPreview)
417
418	t := new(Membership)
419	resp, err := s.client.Do(ctx, req, t)
420	if err != nil {
421		return nil, resp, err
422	}
423
424	return t, resp, nil
425}
426
427// OrganizationAddTeamMembershipOptions does stuff specifies the optional
428// parameters to the OrganizationsService.AddTeamMembership method.
429type OrganizationAddTeamMembershipOptions struct {
430	// Role specifies the role the user should have in the team. Possible
431	// values are:
432	//     member - a normal member of the team
433	//     maintainer - a team maintainer. Able to add/remove other team
434	//                  members, promote other team members to team
435	//                  maintainer, and edit the team’s name and description
436	//
437	// Default value is "member".
438	Role string `json:"role,omitempty"`
439}
440
441// AddTeamMembership adds or invites a user to a team.
442//
443// In order to add a membership between a user and a team, the authenticated
444// user must have 'admin' permissions to the team or be an owner of the
445// organization that the team is associated with.
446//
447// If the user is already a part of the team's organization (meaning they're on
448// at least one other team in the organization), this endpoint will add the
449// user to the team.
450//
451// If the user is completely unaffiliated with the team's organization (meaning
452// they're on none of the organization's teams), this endpoint will send an
453// invitation to the user via email. This newly-created membership will be in
454// the "pending" state until the user accepts the invitation, at which point
455// the membership will transition to the "active" state and the user will be
456// added as a member of the team.
457//
458// GitHub API docs: https://developer.github.com/v3/orgs/teams/#add-team-membership
459func (s *OrganizationsService) AddTeamMembership(ctx context.Context, team int64, user string, opt *OrganizationAddTeamMembershipOptions) (*Membership, *Response, error) {
460	u := fmt.Sprintf("teams/%v/memberships/%v", team, user)
461	req, err := s.client.NewRequest("PUT", u, opt)
462	if err != nil {
463		return nil, nil, err
464	}
465
466	t := new(Membership)
467	resp, err := s.client.Do(ctx, req, t)
468	if err != nil {
469		return nil, resp, err
470	}
471
472	return t, resp, nil
473}
474
475// RemoveTeamMembership removes a user from a team.
476//
477// GitHub API docs: https://developer.github.com/v3/orgs/teams/#remove-team-membership
478func (s *OrganizationsService) RemoveTeamMembership(ctx context.Context, team int64, user string) (*Response, error) {
479	u := fmt.Sprintf("teams/%v/memberships/%v", team, user)
480	req, err := s.client.NewRequest("DELETE", u, nil)
481	if err != nil {
482		return nil, err
483	}
484
485	return s.client.Do(ctx, req, nil)
486}
487
488// ListPendingTeamInvitations get pending invitaion list in team.
489// Warning: The API may change without advance notice during the preview period.
490// Preview features are not supported for production use.
491//
492// GitHub API docs: https://developer.github.com/v3/orgs/teams/#list-pending-team-invitations
493func (s *OrganizationsService) ListPendingTeamInvitations(ctx context.Context, team int64, opt *ListOptions) ([]*Invitation, *Response, error) {
494	u := fmt.Sprintf("teams/%v/invitations", team)
495	u, err := addOptions(u, opt)
496	if err != nil {
497		return nil, nil, err
498	}
499
500	req, err := s.client.NewRequest("GET", u, nil)
501	if err != nil {
502		return nil, nil, err
503	}
504
505	var pendingInvitations []*Invitation
506	resp, err := s.client.Do(ctx, req, &pendingInvitations)
507	if err != nil {
508		return nil, resp, err
509	}
510
511	return pendingInvitations, resp, nil
512}
513