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	"net/http"
12	"strings"
13	"time"
14)
15
16// TeamsService provides access to the team-related functions
17// in the GitHub API.
18//
19// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/
20type TeamsService service
21
22// Team represents a team within a GitHub organization. Teams are used to
23// manage access to an organization's repositories.
24type Team struct {
25	ID          *int64  `json:"id,omitempty"`
26	NodeID      *string `json:"node_id,omitempty"`
27	Name        *string `json:"name,omitempty"`
28	Description *string `json:"description,omitempty"`
29	URL         *string `json:"url,omitempty"`
30	Slug        *string `json:"slug,omitempty"`
31
32	// Permission specifies the default permission for repositories owned by the team.
33	Permission *string `json:"permission,omitempty"`
34
35	// Permissions identifies the permissions that a team has on a given
36	// repository. This is only populated when calling Repositories.ListTeams.
37	Permissions map[string]bool `json:"permissions,omitempty"`
38
39	// Privacy identifies the level of privacy this team should have.
40	// Possible values are:
41	//     secret - only visible to organization owners and members of this team
42	//     closed - visible to all members of this organization
43	// Default is "secret".
44	Privacy *string `json:"privacy,omitempty"`
45
46	MembersCount    *int          `json:"members_count,omitempty"`
47	ReposCount      *int          `json:"repos_count,omitempty"`
48	Organization    *Organization `json:"organization,omitempty"`
49	MembersURL      *string       `json:"members_url,omitempty"`
50	RepositoriesURL *string       `json:"repositories_url,omitempty"`
51	Parent          *Team         `json:"parent,omitempty"`
52
53	// LDAPDN is only available in GitHub Enterprise and when the team
54	// membership is synchronized with LDAP.
55	LDAPDN *string `json:"ldap_dn,omitempty"`
56}
57
58func (t Team) String() string {
59	return Stringify(t)
60}
61
62// Invitation represents a team member's invitation status.
63type Invitation struct {
64	ID     *int64  `json:"id,omitempty"`
65	NodeID *string `json:"node_id,omitempty"`
66	Login  *string `json:"login,omitempty"`
67	Email  *string `json:"email,omitempty"`
68	// Role can be one of the values - 'direct_member', 'admin', 'billing_manager', 'hiring_manager', or 'reinstate'.
69	Role              *string    `json:"role,omitempty"`
70	CreatedAt         *time.Time `json:"created_at,omitempty"`
71	Inviter           *User      `json:"inviter,omitempty"`
72	TeamCount         *int       `json:"team_count,omitempty"`
73	InvitationTeamURL *string    `json:"invitation_team_url,omitempty"`
74	FailedAt          *Timestamp `json:"failed_at,omitempty"`
75	FailedReason      *string    `json:"failed_reason,omitempty"`
76}
77
78func (i Invitation) String() string {
79	return Stringify(i)
80}
81
82// ListTeams lists all of the teams for an organization.
83//
84// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#list-teams
85func (s *TeamsService) ListTeams(ctx context.Context, org string, opts *ListOptions) ([]*Team, *Response, error) {
86	u := fmt.Sprintf("orgs/%v/teams", org)
87	u, err := addOptions(u, opts)
88	if err != nil {
89		return nil, nil, err
90	}
91
92	req, err := s.client.NewRequest("GET", u, nil)
93	if err != nil {
94		return nil, nil, err
95	}
96
97	var teams []*Team
98	resp, err := s.client.Do(ctx, req, &teams)
99	if err != nil {
100		return nil, resp, err
101	}
102
103	return teams, resp, nil
104}
105
106// GetTeamByID fetches a team, given a specified organization ID, by ID.
107//
108// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#get-a-team-by-name
109func (s *TeamsService) GetTeamByID(ctx context.Context, orgID, teamID int64) (*Team, *Response, error) {
110	u := fmt.Sprintf("organizations/%v/team/%v", orgID, teamID)
111	req, err := s.client.NewRequest("GET", u, nil)
112	if err != nil {
113		return nil, nil, err
114	}
115
116	t := new(Team)
117	resp, err := s.client.Do(ctx, req, t)
118	if err != nil {
119		return nil, resp, err
120	}
121
122	return t, resp, nil
123}
124
125// GetTeamBySlug fetches a team, given a specified organization name, by slug.
126//
127// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#get-a-team-by-name
128func (s *TeamsService) GetTeamBySlug(ctx context.Context, org, slug string) (*Team, *Response, error) {
129	u := fmt.Sprintf("orgs/%v/teams/%v", org, slug)
130	req, err := s.client.NewRequest("GET", u, nil)
131	if err != nil {
132		return nil, nil, err
133	}
134
135	t := new(Team)
136	resp, err := s.client.Do(ctx, req, t)
137	if err != nil {
138		return nil, resp, err
139	}
140
141	return t, resp, nil
142}
143
144// NewTeam represents a team to be created or modified.
145type NewTeam struct {
146	Name         string   `json:"name"` // Name of the team. (Required.)
147	Description  *string  `json:"description,omitempty"`
148	Maintainers  []string `json:"maintainers,omitempty"`
149	RepoNames    []string `json:"repo_names,omitempty"`
150	ParentTeamID *int64   `json:"parent_team_id,omitempty"`
151
152	// Deprecated: Permission is deprecated when creating or editing a team in an org
153	// using the new GitHub permission model. It no longer identifies the
154	// permission a team has on its repos, but only specifies the default
155	// permission a repo is initially added with. Avoid confusion by
156	// specifying a permission value when calling AddTeamRepo.
157	Permission *string `json:"permission,omitempty"`
158
159	// Privacy identifies the level of privacy this team should have.
160	// Possible values are:
161	//     secret - only visible to organization owners and members of this team
162	//     closed - visible to all members of this organization
163	// Default is "secret".
164	Privacy *string `json:"privacy,omitempty"`
165
166	// LDAPDN may be used in GitHub Enterprise when the team membership
167	// is synchronized with LDAP.
168	LDAPDN *string `json:"ldap_dn,omitempty"`
169}
170
171func (s NewTeam) String() string {
172	return Stringify(s)
173}
174
175// CreateTeam creates a new team within an organization.
176//
177// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#create-a-team
178func (s *TeamsService) CreateTeam(ctx context.Context, org string, team NewTeam) (*Team, *Response, error) {
179	u := fmt.Sprintf("orgs/%v/teams", org)
180	req, err := s.client.NewRequest("POST", u, team)
181	if err != nil {
182		return nil, nil, err
183	}
184
185	t := new(Team)
186	resp, err := s.client.Do(ctx, req, t)
187	if err != nil {
188		return nil, resp, err
189	}
190
191	return t, resp, nil
192}
193
194// newTeamNoParent is the same as NewTeam but ensures that the
195// "parent_team_id" field will be null. It is for internal use
196// only and should not be exported.
197type newTeamNoParent struct {
198	Name         string   `json:"name"`
199	Description  *string  `json:"description,omitempty"`
200	Maintainers  []string `json:"maintainers,omitempty"`
201	RepoNames    []string `json:"repo_names,omitempty"`
202	ParentTeamID *int64   `json:"parent_team_id"` // This will be "null"
203	Privacy      *string  `json:"privacy,omitempty"`
204	LDAPDN       *string  `json:"ldap_dn,omitempty"`
205}
206
207// copyNewTeamWithoutParent is used to set the "parent_team_id"
208// field to "null" after copying the other fields from a NewTeam.
209// It is for internal use only and should not be exported.
210func copyNewTeamWithoutParent(team *NewTeam) *newTeamNoParent {
211	return &newTeamNoParent{
212		Name:        team.Name,
213		Description: team.Description,
214		Maintainers: team.Maintainers,
215		RepoNames:   team.RepoNames,
216		Privacy:     team.Privacy,
217		LDAPDN:      team.LDAPDN,
218	}
219}
220
221// EditTeamByID edits a team, given an organization ID, selected by ID.
222//
223// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#update-a-team
224func (s *TeamsService) EditTeamByID(ctx context.Context, orgID, teamID int64, team NewTeam, removeParent bool) (*Team, *Response, error) {
225	u := fmt.Sprintf("organizations/%v/team/%v", orgID, teamID)
226
227	var req *http.Request
228	var err error
229	if removeParent {
230		teamRemoveParent := copyNewTeamWithoutParent(&team)
231		req, err = s.client.NewRequest("PATCH", u, teamRemoveParent)
232	} else {
233		req, err = s.client.NewRequest("PATCH", u, team)
234	}
235	if err != nil {
236		return nil, nil, err
237	}
238
239	t := new(Team)
240	resp, err := s.client.Do(ctx, req, t)
241	if err != nil {
242		return nil, resp, err
243	}
244
245	return t, resp, nil
246}
247
248// EditTeamBySlug edits a team, given an organization name, by slug.
249//
250// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#update-a-team
251func (s *TeamsService) EditTeamBySlug(ctx context.Context, org, slug string, team NewTeam, removeParent bool) (*Team, *Response, error) {
252	u := fmt.Sprintf("orgs/%v/teams/%v", org, slug)
253
254	var req *http.Request
255	var err error
256	if removeParent {
257		teamRemoveParent := copyNewTeamWithoutParent(&team)
258		req, err = s.client.NewRequest("PATCH", u, teamRemoveParent)
259	} else {
260		req, err = s.client.NewRequest("PATCH", u, team)
261	}
262	if err != nil {
263		return nil, nil, err
264	}
265
266	t := new(Team)
267	resp, err := s.client.Do(ctx, req, t)
268	if err != nil {
269		return nil, resp, err
270	}
271
272	return t, resp, nil
273}
274
275// DeleteTeamByID deletes a team referenced by ID.
276//
277// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#delete-a-team
278func (s *TeamsService) DeleteTeamByID(ctx context.Context, orgID, teamID int64) (*Response, error) {
279	u := fmt.Sprintf("organizations/%v/team/%v", orgID, teamID)
280	req, err := s.client.NewRequest("DELETE", u, nil)
281	if err != nil {
282		return nil, err
283	}
284
285	return s.client.Do(ctx, req, nil)
286}
287
288// DeleteTeamBySlug deletes a team reference by slug.
289//
290// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#delete-a-team
291func (s *TeamsService) DeleteTeamBySlug(ctx context.Context, org, slug string) (*Response, error) {
292	u := fmt.Sprintf("orgs/%v/teams/%v", org, slug)
293	req, err := s.client.NewRequest("DELETE", u, nil)
294	if err != nil {
295		return nil, err
296	}
297
298	return s.client.Do(ctx, req, nil)
299}
300
301// ListChildTeamsByParentID lists child teams for a parent team given parent ID.
302//
303// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#list-child-teams
304func (s *TeamsService) ListChildTeamsByParentID(ctx context.Context, orgID, teamID int64, opts *ListOptions) ([]*Team, *Response, error) {
305	u := fmt.Sprintf("organizations/%v/team/%v/teams", orgID, teamID)
306	u, err := addOptions(u, opts)
307	if err != nil {
308		return nil, nil, err
309	}
310
311	req, err := s.client.NewRequest("GET", u, nil)
312	if err != nil {
313		return nil, nil, err
314	}
315
316	var teams []*Team
317	resp, err := s.client.Do(ctx, req, &teams)
318	if err != nil {
319		return nil, resp, err
320	}
321
322	return teams, resp, nil
323}
324
325// ListChildTeamsByParentSlug lists child teams for a parent team given parent slug.
326//
327// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#list-child-teams
328func (s *TeamsService) ListChildTeamsByParentSlug(ctx context.Context, org, slug string, opts *ListOptions) ([]*Team, *Response, error) {
329	u := fmt.Sprintf("orgs/%v/teams/%v/teams", org, slug)
330	u, err := addOptions(u, opts)
331	if err != nil {
332		return nil, nil, err
333	}
334
335	req, err := s.client.NewRequest("GET", u, nil)
336	if err != nil {
337		return nil, nil, err
338	}
339
340	var teams []*Team
341	resp, err := s.client.Do(ctx, req, &teams)
342	if err != nil {
343		return nil, resp, err
344	}
345
346	return teams, resp, nil
347}
348
349// ListTeamReposByID lists the repositories given a team ID that the specified team has access to.
350//
351// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#list-team-repositories
352func (s *TeamsService) ListTeamReposByID(ctx context.Context, orgID, teamID int64, opts *ListOptions) ([]*Repository, *Response, error) {
353	u := fmt.Sprintf("organizations/%v/team/%v/repos", orgID, teamID)
354	u, err := addOptions(u, opts)
355	if err != nil {
356		return nil, nil, err
357	}
358
359	req, err := s.client.NewRequest("GET", u, nil)
360	if err != nil {
361		return nil, nil, err
362	}
363
364	// TODO: remove custom Accept header when topics API fully launches.
365	headers := []string{mediaTypeTopicsPreview}
366	req.Header.Set("Accept", strings.Join(headers, ", "))
367
368	var repos []*Repository
369	resp, err := s.client.Do(ctx, req, &repos)
370	if err != nil {
371		return nil, resp, err
372	}
373
374	return repos, resp, nil
375}
376
377// ListTeamReposBySlug lists the repositories given a team slug that the specified team has access to.
378//
379// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#list-team-repositories
380func (s *TeamsService) ListTeamReposBySlug(ctx context.Context, org, slug string, opts *ListOptions) ([]*Repository, *Response, error) {
381	u := fmt.Sprintf("orgs/%v/teams/%v/repos", org, slug)
382	u, err := addOptions(u, opts)
383	if err != nil {
384		return nil, nil, err
385	}
386
387	req, err := s.client.NewRequest("GET", u, nil)
388	if err != nil {
389		return nil, nil, err
390	}
391
392	// TODO: remove custom Accept header when topics API fully launches.
393	headers := []string{mediaTypeTopicsPreview}
394	req.Header.Set("Accept", strings.Join(headers, ", "))
395
396	var repos []*Repository
397	resp, err := s.client.Do(ctx, req, &repos)
398	if err != nil {
399		return nil, resp, err
400	}
401
402	return repos, resp, nil
403}
404
405// IsTeamRepoByID checks if a team, given its ID, manages the specified repository. If the
406// repository is managed by team, a Repository is returned which includes the
407// permissions team has for that repo.
408//
409// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#check-team-permissions-for-a-repository
410func (s *TeamsService) IsTeamRepoByID(ctx context.Context, orgID, teamID int64, owner, repo string) (*Repository, *Response, error) {
411	u := fmt.Sprintf("organizations/%v/team/%v/repos/%v/%v", orgID, teamID, owner, repo)
412	req, err := s.client.NewRequest("GET", u, nil)
413	if err != nil {
414		return nil, nil, err
415	}
416
417	headers := []string{mediaTypeOrgPermissionRepo}
418	req.Header.Set("Accept", strings.Join(headers, ", "))
419
420	repository := new(Repository)
421	resp, err := s.client.Do(ctx, req, repository)
422	if err != nil {
423		return nil, resp, err
424	}
425
426	return repository, resp, nil
427}
428
429// IsTeamRepoBySlug checks if a team, given its slug, manages the specified repository. If the
430// repository is managed by team, a Repository is returned which includes the
431// permissions team has for that repo.
432//
433// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#check-team-permissions-for-a-repository
434func (s *TeamsService) IsTeamRepoBySlug(ctx context.Context, org, slug, owner, repo string) (*Repository, *Response, error) {
435	u := fmt.Sprintf("orgs/%v/teams/%v/repos/%v/%v", org, slug, owner, repo)
436	req, err := s.client.NewRequest("GET", u, nil)
437	if err != nil {
438		return nil, nil, err
439	}
440
441	headers := []string{mediaTypeOrgPermissionRepo}
442	req.Header.Set("Accept", strings.Join(headers, ", "))
443
444	repository := new(Repository)
445	resp, err := s.client.Do(ctx, req, repository)
446	if err != nil {
447		return nil, resp, err
448	}
449
450	return repository, resp, nil
451}
452
453// TeamAddTeamRepoOptions specifies the optional parameters to the
454// TeamsService.AddTeamRepo method.
455type TeamAddTeamRepoOptions struct {
456	// Permission specifies the permission to grant the team on this repository.
457	// Possible values are:
458	//     pull - team members can pull, but not push to or administer this repository
459	//     push - team members can pull and push, but not administer this repository
460	//     admin - team members can pull, push and administer this repository
461	//     maintain - team members can manage the repository without access to sensitive or destructive actions.
462	//     triage - team members can proactively manage issues and pull requests without write access.
463	//
464	// If not specified, the team's permission attribute will be used.
465	Permission string `json:"permission,omitempty"`
466}
467
468// AddTeamRepoByID adds a repository to be managed by the specified team given the team ID.
469// The specified repository must be owned by the organization to which the team
470// belongs, or a direct fork of a repository owned by the organization.
471//
472// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#add-or-update-team-repository-permissions
473func (s *TeamsService) AddTeamRepoByID(ctx context.Context, orgID, teamID int64, owner, repo string, opts *TeamAddTeamRepoOptions) (*Response, error) {
474	u := fmt.Sprintf("organizations/%v/team/%v/repos/%v/%v", orgID, teamID, owner, repo)
475	req, err := s.client.NewRequest("PUT", u, opts)
476	if err != nil {
477		return nil, err
478	}
479
480	return s.client.Do(ctx, req, nil)
481}
482
483// AddTeamRepoBySlug adds a repository to be managed by the specified team given the team slug.
484// The specified repository must be owned by the organization to which the team
485// belongs, or a direct fork of a repository owned by the organization.
486//
487// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#add-or-update-team-repository-permissions
488func (s *TeamsService) AddTeamRepoBySlug(ctx context.Context, org, slug, owner, repo string, opts *TeamAddTeamRepoOptions) (*Response, error) {
489	u := fmt.Sprintf("orgs/%v/teams/%v/repos/%v/%v", org, slug, owner, repo)
490	req, err := s.client.NewRequest("PUT", u, opts)
491	if err != nil {
492		return nil, err
493	}
494
495	return s.client.Do(ctx, req, nil)
496}
497
498// RemoveTeamRepoByID removes a repository from being managed by the specified
499// team given the team ID. Note that this does not delete the repository, it
500// just removes it from the team.
501//
502// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#remove-a-repository-from-a-team
503func (s *TeamsService) RemoveTeamRepoByID(ctx context.Context, orgID, teamID int64, owner, repo string) (*Response, error) {
504	u := fmt.Sprintf("organizations/%v/team/%v/repos/%v/%v", orgID, teamID, owner, repo)
505	req, err := s.client.NewRequest("DELETE", u, nil)
506	if err != nil {
507		return nil, err
508	}
509
510	return s.client.Do(ctx, req, nil)
511}
512
513// RemoveTeamRepoBySlug removes a repository from being managed by the specified
514// team given the team slug. Note that this does not delete the repository, it
515// just removes it from the team.
516//
517// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#remove-a-repository-from-a-team
518func (s *TeamsService) RemoveTeamRepoBySlug(ctx context.Context, org, slug, owner, repo string) (*Response, error) {
519	u := fmt.Sprintf("orgs/%v/teams/%v/repos/%v/%v", org, slug, owner, repo)
520	req, err := s.client.NewRequest("DELETE", u, nil)
521	if err != nil {
522		return nil, err
523	}
524
525	return s.client.Do(ctx, req, nil)
526}
527
528// ListUserTeams lists a user's teams
529// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#list-teams-for-the-authenticated-user
530func (s *TeamsService) ListUserTeams(ctx context.Context, opts *ListOptions) ([]*Team, *Response, error) {
531	u := "user/teams"
532	u, err := addOptions(u, opts)
533	if err != nil {
534		return nil, nil, err
535	}
536
537	req, err := s.client.NewRequest("GET", u, nil)
538	if err != nil {
539		return nil, nil, err
540	}
541
542	var teams []*Team
543	resp, err := s.client.Do(ctx, req, &teams)
544	if err != nil {
545		return nil, resp, err
546	}
547
548	return teams, resp, nil
549}
550
551// ListTeamProjectsByID lists the organization projects for a team given the team ID.
552//
553// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#list-team-projects
554func (s *TeamsService) ListTeamProjectsByID(ctx context.Context, orgID, teamID int64) ([]*Project, *Response, error) {
555	u := fmt.Sprintf("organizations/%v/team/%v/projects", orgID, teamID)
556
557	req, err := s.client.NewRequest("GET", u, nil)
558	if err != nil {
559		return nil, nil, err
560	}
561
562	// TODO: remove custom Accept header when this API fully launches.
563	acceptHeaders := []string{mediaTypeProjectsPreview}
564	req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
565
566	var projects []*Project
567	resp, err := s.client.Do(ctx, req, &projects)
568	if err != nil {
569		return nil, resp, err
570	}
571
572	return projects, resp, nil
573}
574
575// ListTeamProjectsBySlug lists the organization projects for a team given the team slug.
576//
577// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#list-team-projects
578func (s *TeamsService) ListTeamProjectsBySlug(ctx context.Context, org, slug string) ([]*Project, *Response, error) {
579	u := fmt.Sprintf("orgs/%v/teams/%v/projects", org, slug)
580
581	req, err := s.client.NewRequest("GET", u, nil)
582	if err != nil {
583		return nil, nil, err
584	}
585
586	// TODO: remove custom Accept header when this API fully launches.
587	acceptHeaders := []string{mediaTypeProjectsPreview}
588	req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
589
590	var projects []*Project
591	resp, err := s.client.Do(ctx, req, &projects)
592	if err != nil {
593		return nil, resp, err
594	}
595
596	return projects, resp, nil
597}
598
599// ReviewTeamProjectsByID checks whether a team, given its ID, has read, write, or admin
600// permissions for an organization project.
601//
602// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#check-team-permissions-for-a-project
603func (s *TeamsService) ReviewTeamProjectsByID(ctx context.Context, orgID, teamID, projectID int64) (*Project, *Response, error) {
604	u := fmt.Sprintf("organizations/%v/team/%v/projects/%v", orgID, teamID, projectID)
605	req, err := s.client.NewRequest("GET", u, nil)
606	if err != nil {
607		return nil, nil, err
608	}
609
610	// TODO: remove custom Accept header when this API fully launches.
611	acceptHeaders := []string{mediaTypeProjectsPreview}
612	req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
613
614	projects := &Project{}
615	resp, err := s.client.Do(ctx, req, &projects)
616	if err != nil {
617		return nil, resp, err
618	}
619
620	return projects, resp, nil
621}
622
623// ReviewTeamProjectsBySlug checks whether a team, given its slug, has read, write, or admin
624// permissions for an organization project.
625//
626// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#check-team-permissions-for-a-project
627func (s *TeamsService) ReviewTeamProjectsBySlug(ctx context.Context, org, slug string, projectID int64) (*Project, *Response, error) {
628	u := fmt.Sprintf("orgs/%v/teams/%v/projects/%v", org, slug, projectID)
629	req, err := s.client.NewRequest("GET", u, nil)
630	if err != nil {
631		return nil, nil, err
632	}
633
634	// TODO: remove custom Accept header when this API fully launches.
635	acceptHeaders := []string{mediaTypeProjectsPreview}
636	req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
637
638	projects := &Project{}
639	resp, err := s.client.Do(ctx, req, &projects)
640	if err != nil {
641		return nil, resp, err
642	}
643
644	return projects, resp, nil
645}
646
647// TeamProjectOptions specifies the optional parameters to the
648// TeamsService.AddTeamProject method.
649type TeamProjectOptions struct {
650	// Permission specifies the permission to grant to the team for this project.
651	// Possible values are:
652	//     "read" - team members can read, but not write to or administer this project.
653	//     "write" - team members can read and write, but not administer this project.
654	//     "admin" - team members can read, write and administer this project.
655	//
656	Permission *string `json:"permission,omitempty"`
657}
658
659// AddTeamProjectByID adds an organization project to a team given the team ID.
660// To add a project to a team or update the team's permission on a project, the
661// authenticated user must have admin permissions for the project.
662//
663// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#add-or-update-team-project-permissions
664func (s *TeamsService) AddTeamProjectByID(ctx context.Context, orgID, teamID, projectID int64, opts *TeamProjectOptions) (*Response, error) {
665	u := fmt.Sprintf("organizations/%v/team/%v/projects/%v", orgID, teamID, projectID)
666	req, err := s.client.NewRequest("PUT", u, opts)
667	if err != nil {
668		return nil, err
669	}
670
671	// TODO: remove custom Accept header when this API fully launches.
672	acceptHeaders := []string{mediaTypeProjectsPreview}
673	req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
674
675	return s.client.Do(ctx, req, nil)
676}
677
678// AddTeamProjectBySlug adds an organization project to a team given the team slug.
679// To add a project to a team or update the team's permission on a project, the
680// authenticated user must have admin permissions for the project.
681//
682// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#add-or-update-team-project-permissions
683func (s *TeamsService) AddTeamProjectBySlug(ctx context.Context, org, slug string, projectID int64, opts *TeamProjectOptions) (*Response, error) {
684	u := fmt.Sprintf("orgs/%v/teams/%v/projects/%v", org, slug, projectID)
685	req, err := s.client.NewRequest("PUT", u, opts)
686	if err != nil {
687		return nil, err
688	}
689
690	// TODO: remove custom Accept header when this API fully launches.
691	acceptHeaders := []string{mediaTypeProjectsPreview}
692	req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
693
694	return s.client.Do(ctx, req, nil)
695}
696
697// RemoveTeamProjectByID removes an organization project from a team given team ID.
698// An organization owner or a team maintainer can remove any project from the team.
699// To remove a project from a team as an organization member, the authenticated user
700// must have "read" access to both the team and project, or "admin" access to the team
701// or project.
702// Note: This endpoint removes the project from the team, but does not delete it.
703//
704// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#remove-a-project-from-a-team
705func (s *TeamsService) RemoveTeamProjectByID(ctx context.Context, orgID, teamID, projectID int64) (*Response, error) {
706	u := fmt.Sprintf("organizations/%v/team/%v/projects/%v", orgID, teamID, projectID)
707	req, err := s.client.NewRequest("DELETE", u, nil)
708	if err != nil {
709		return nil, err
710	}
711
712	// TODO: remove custom Accept header when this API fully launches.
713	acceptHeaders := []string{mediaTypeProjectsPreview}
714	req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
715
716	return s.client.Do(ctx, req, nil)
717}
718
719// RemoveTeamProjectBySlug removes an organization project from a team given team slug.
720// An organization owner or a team maintainer can remove any project from the team.
721// To remove a project from a team as an organization member, the authenticated user
722// must have "read" access to both the team and project, or "admin" access to the team
723// or project.
724// Note: This endpoint removes the project from the team, but does not delete it.
725//
726// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#remove-a-project-from-a-team
727func (s *TeamsService) RemoveTeamProjectBySlug(ctx context.Context, org, slug string, projectID int64) (*Response, error) {
728	u := fmt.Sprintf("orgs/%v/teams/%v/projects/%v", org, slug, projectID)
729	req, err := s.client.NewRequest("DELETE", u, nil)
730	if err != nil {
731		return nil, err
732	}
733
734	// TODO: remove custom Accept header when this API fully launches.
735	acceptHeaders := []string{mediaTypeProjectsPreview}
736	req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
737
738	return s.client.Do(ctx, req, nil)
739}
740
741// IDPGroupList represents a list of external identity provider (IDP) groups.
742type IDPGroupList struct {
743	Groups []*IDPGroup `json:"groups"`
744}
745
746// IDPGroup represents an external identity provider (IDP) group.
747type IDPGroup struct {
748	GroupID          *string `json:"group_id,omitempty"`
749	GroupName        *string `json:"group_name,omitempty"`
750	GroupDescription *string `json:"group_description,omitempty"`
751}
752
753// ListIDPGroupsInOrganization lists IDP groups available in an organization.
754//
755// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#list-idp-groups-for-an-organization
756func (s *TeamsService) ListIDPGroupsInOrganization(ctx context.Context, org string, opts *ListCursorOptions) (*IDPGroupList, *Response, error) {
757	u := fmt.Sprintf("orgs/%v/team-sync/groups", org)
758	u, err := addOptions(u, opts)
759	if err != nil {
760		return nil, nil, err
761	}
762
763	req, err := s.client.NewRequest("GET", u, nil)
764	if err != nil {
765		return nil, nil, err
766	}
767
768	groups := new(IDPGroupList)
769	resp, err := s.client.Do(ctx, req, groups)
770	if err != nil {
771		return nil, resp, err
772	}
773	return groups, resp, nil
774}
775
776// ListIDPGroupsForTeamByID lists IDP groups connected to a team on GitHub
777// given organization and team IDs.
778//
779// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#list-idp-groups-for-a-team
780func (s *TeamsService) ListIDPGroupsForTeamByID(ctx context.Context, orgID, teamID int64) (*IDPGroupList, *Response, error) {
781	u := fmt.Sprintf("organizations/%v/team/%v/team-sync/group-mappings", orgID, teamID)
782
783	req, err := s.client.NewRequest("GET", u, nil)
784	if err != nil {
785		return nil, nil, err
786	}
787
788	groups := new(IDPGroupList)
789	resp, err := s.client.Do(ctx, req, groups)
790	if err != nil {
791		return nil, resp, err
792	}
793	return groups, resp, err
794}
795
796// ListIDPGroupsForTeamBySlug lists IDP groups connected to a team on GitHub
797// given organization name and team slug.
798//
799// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#list-idp-groups-for-a-team
800func (s *TeamsService) ListIDPGroupsForTeamBySlug(ctx context.Context, org, slug string) (*IDPGroupList, *Response, error) {
801	u := fmt.Sprintf("orgs/%v/teams/%v/team-sync/group-mappings", org, slug)
802
803	req, err := s.client.NewRequest("GET", u, nil)
804	if err != nil {
805		return nil, nil, err
806	}
807
808	groups := new(IDPGroupList)
809	resp, err := s.client.Do(ctx, req, groups)
810	if err != nil {
811		return nil, resp, err
812	}
813	return groups, resp, err
814}
815
816// CreateOrUpdateIDPGroupConnectionsByID creates, updates, or removes a connection
817// between a team and an IDP group given organization and team IDs.
818//
819// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#create-or-update-idp-group-connections
820func (s *TeamsService) CreateOrUpdateIDPGroupConnectionsByID(ctx context.Context, orgID, teamID int64, opts IDPGroupList) (*IDPGroupList, *Response, error) {
821	u := fmt.Sprintf("organizations/%v/team/%v/team-sync/group-mappings", orgID, teamID)
822
823	req, err := s.client.NewRequest("PATCH", u, opts)
824	if err != nil {
825		return nil, nil, err
826	}
827
828	groups := new(IDPGroupList)
829	resp, err := s.client.Do(ctx, req, groups)
830	if err != nil {
831		return nil, resp, err
832	}
833
834	return groups, resp, nil
835}
836
837// CreateOrUpdateIDPGroupConnectionsBySlug creates, updates, or removes a connection
838// between a team and an IDP group given organization name and team slug.
839//
840// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/teams/#create-or-update-idp-group-connections
841func (s *TeamsService) CreateOrUpdateIDPGroupConnectionsBySlug(ctx context.Context, org, slug string, opts IDPGroupList) (*IDPGroupList, *Response, error) {
842	u := fmt.Sprintf("orgs/%v/teams/%v/team-sync/group-mappings", org, slug)
843
844	req, err := s.client.NewRequest("PATCH", u, opts)
845	if err != nil {
846		return nil, nil, err
847	}
848
849	groups := new(IDPGroupList)
850	resp, err := s.client.Do(ctx, req, groups)
851	if err != nil {
852		return nil, resp, err
853	}
854
855	return groups, resp, nil
856}
857