1// Copyright 2016 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// ProjectsService provides access to the projects functions in the
14// GitHub API.
15//
16// GitHub API docs: https://developer.github.com/v3/projects/
17type ProjectsService service
18
19// Project represents a GitHub Project.
20type Project struct {
21	ID         *int64     `json:"id,omitempty"`
22	URL        *string    `json:"url,omitempty"`
23	HTMLURL    *string    `json:"html_url,omitempty"`
24	ColumnsURL *string    `json:"columns_url,omitempty"`
25	OwnerURL   *string    `json:"owner_url,omitempty"`
26	Name       *string    `json:"name,omitempty"`
27	Body       *string    `json:"body,omitempty"`
28	Number     *int       `json:"number,omitempty"`
29	State      *string    `json:"state,omitempty"`
30	CreatedAt  *Timestamp `json:"created_at,omitempty"`
31	UpdatedAt  *Timestamp `json:"updated_at,omitempty"`
32	NodeID     *string    `json:"node_id,omitempty"`
33
34	// The User object that generated the project.
35	Creator *User `json:"creator,omitempty"`
36}
37
38func (p Project) String() string {
39	return Stringify(p)
40}
41
42// GetProject gets a GitHub Project for a repo.
43//
44// GitHub API docs: https://developer.github.com/v3/projects/#get-a-project
45func (s *ProjectsService) GetProject(ctx context.Context, id int64) (*Project, *Response, error) {
46	u := fmt.Sprintf("projects/%v", id)
47	req, err := s.client.NewRequest("GET", u, nil)
48	if err != nil {
49		return nil, nil, err
50	}
51
52	// TODO: remove custom Accept headers when APIs fully launch.
53	req.Header.Set("Accept", mediaTypeProjectsPreview)
54
55	project := &Project{}
56	resp, err := s.client.Do(ctx, req, project)
57	if err != nil {
58		return nil, resp, err
59	}
60
61	return project, resp, nil
62}
63
64// ProjectOptions specifies the parameters to the
65// RepositoriesService.CreateProject and
66// ProjectsService.UpdateProject methods.
67type ProjectOptions struct {
68	// The name of the project. (Required for creation; optional for update.)
69	Name *string `json:"name,omitempty"`
70	// The body of the project. (Optional.)
71	Body *string `json:"body,omitempty"`
72
73	// The following field(s) are only applicable for update.
74	// They should be left with zero values for creation.
75
76	// State of the project. Either "open" or "closed". (Optional.)
77	State *string `json:"state,omitempty"`
78	// The permission level that all members of the project's organization
79	// will have on this project.
80	// Setting the organization permission is only available
81	// for organization projects. (Optional.)
82	OrganizationPermission *string `json:"organization_permission,omitempty"`
83	// Sets visibility of the project within the organization.
84	// Setting visibility is only available
85	// for organization projects.(Optional.)
86	Public *bool `json:"public,omitempty"`
87}
88
89// UpdateProject updates a repository project.
90//
91// GitHub API docs: https://developer.github.com/v3/projects/#update-a-project
92func (s *ProjectsService) UpdateProject(ctx context.Context, id int64, opts *ProjectOptions) (*Project, *Response, error) {
93	u := fmt.Sprintf("projects/%v", id)
94	req, err := s.client.NewRequest("PATCH", u, opts)
95	if err != nil {
96		return nil, nil, err
97	}
98
99	// TODO: remove custom Accept headers when APIs fully launch.
100	req.Header.Set("Accept", mediaTypeProjectsPreview)
101
102	project := &Project{}
103	resp, err := s.client.Do(ctx, req, project)
104	if err != nil {
105		return nil, resp, err
106	}
107
108	return project, resp, nil
109}
110
111// DeleteProject deletes a GitHub Project from a repository.
112//
113// GitHub API docs: https://developer.github.com/v3/projects/#delete-a-project
114func (s *ProjectsService) DeleteProject(ctx context.Context, id int64) (*Response, error) {
115	u := fmt.Sprintf("projects/%v", id)
116	req, err := s.client.NewRequest("DELETE", u, nil)
117	if err != nil {
118		return nil, err
119	}
120
121	// TODO: remove custom Accept header when this API fully launches.
122	req.Header.Set("Accept", mediaTypeProjectsPreview)
123
124	return s.client.Do(ctx, req, nil)
125}
126
127// ProjectColumn represents a column of a GitHub Project.
128//
129// GitHub API docs: https://developer.github.com/v3/repos/projects/
130type ProjectColumn struct {
131	ID         *int64     `json:"id,omitempty"`
132	Name       *string    `json:"name,omitempty"`
133	URL        *string    `json:"url,omitempty"`
134	ProjectURL *string    `json:"project_url,omitempty"`
135	CardsURL   *string    `json:"cards_url,omitempty"`
136	CreatedAt  *Timestamp `json:"created_at,omitempty"`
137	UpdatedAt  *Timestamp `json:"updated_at,omitempty"`
138	NodeID     *string    `json:"node_id,omitempty"`
139}
140
141// ListProjectColumns lists the columns of a GitHub Project for a repo.
142//
143// GitHub API docs: https://developer.github.com/v3/projects/columns/#list-project-columns
144func (s *ProjectsService) ListProjectColumns(ctx context.Context, projectID int64, opts *ListOptions) ([]*ProjectColumn, *Response, error) {
145	u := fmt.Sprintf("projects/%v/columns", projectID)
146	u, err := addOptions(u, opts)
147	if err != nil {
148		return nil, nil, err
149	}
150
151	req, err := s.client.NewRequest("GET", u, nil)
152	if err != nil {
153		return nil, nil, err
154	}
155
156	// TODO: remove custom Accept headers when APIs fully launch.
157	req.Header.Set("Accept", mediaTypeProjectsPreview)
158
159	columns := []*ProjectColumn{}
160	resp, err := s.client.Do(ctx, req, &columns)
161	if err != nil {
162		return nil, resp, err
163	}
164
165	return columns, resp, nil
166}
167
168// GetProjectColumn gets a column of a GitHub Project for a repo.
169//
170// GitHub API docs: https://developer.github.com/v3/projects/columns/#get-a-project-column
171func (s *ProjectsService) GetProjectColumn(ctx context.Context, id int64) (*ProjectColumn, *Response, error) {
172	u := fmt.Sprintf("projects/columns/%v", id)
173	req, err := s.client.NewRequest("GET", u, nil)
174	if err != nil {
175		return nil, nil, err
176	}
177
178	// TODO: remove custom Accept headers when APIs fully launch.
179	req.Header.Set("Accept", mediaTypeProjectsPreview)
180
181	column := &ProjectColumn{}
182	resp, err := s.client.Do(ctx, req, column)
183	if err != nil {
184		return nil, resp, err
185	}
186
187	return column, resp, nil
188}
189
190// ProjectColumnOptions specifies the parameters to the
191// ProjectsService.CreateProjectColumn and
192// ProjectsService.UpdateProjectColumn methods.
193type ProjectColumnOptions struct {
194	// The name of the project column. (Required for creation and update.)
195	Name string `json:"name"`
196}
197
198// CreateProjectColumn creates a column for the specified (by number) project.
199//
200// GitHub API docs: https://developer.github.com/v3/projects/columns/#create-a-project-column
201func (s *ProjectsService) CreateProjectColumn(ctx context.Context, projectID int64, opts *ProjectColumnOptions) (*ProjectColumn, *Response, error) {
202	u := fmt.Sprintf("projects/%v/columns", projectID)
203	req, err := s.client.NewRequest("POST", u, opts)
204	if err != nil {
205		return nil, nil, err
206	}
207
208	// TODO: remove custom Accept headers when APIs fully launch.
209	req.Header.Set("Accept", mediaTypeProjectsPreview)
210
211	column := &ProjectColumn{}
212	resp, err := s.client.Do(ctx, req, column)
213	if err != nil {
214		return nil, resp, err
215	}
216
217	return column, resp, nil
218}
219
220// UpdateProjectColumn updates a column of a GitHub Project.
221//
222// GitHub API docs: https://developer.github.com/v3/projects/columns/#update-a-project-column
223func (s *ProjectsService) UpdateProjectColumn(ctx context.Context, columnID int64, opts *ProjectColumnOptions) (*ProjectColumn, *Response, error) {
224	u := fmt.Sprintf("projects/columns/%v", columnID)
225	req, err := s.client.NewRequest("PATCH", u, opts)
226	if err != nil {
227		return nil, nil, err
228	}
229
230	// TODO: remove custom Accept headers when APIs fully launch.
231	req.Header.Set("Accept", mediaTypeProjectsPreview)
232
233	column := &ProjectColumn{}
234	resp, err := s.client.Do(ctx, req, column)
235	if err != nil {
236		return nil, resp, err
237	}
238
239	return column, resp, nil
240}
241
242// DeleteProjectColumn deletes a column from a GitHub Project.
243//
244// GitHub API docs: https://developer.github.com/v3/projects/columns/#delete-a-project-column
245func (s *ProjectsService) DeleteProjectColumn(ctx context.Context, columnID int64) (*Response, error) {
246	u := fmt.Sprintf("projects/columns/%v", columnID)
247	req, err := s.client.NewRequest("DELETE", u, nil)
248	if err != nil {
249		return nil, err
250	}
251
252	// TODO: remove custom Accept header when this API fully launches.
253	req.Header.Set("Accept", mediaTypeProjectsPreview)
254
255	return s.client.Do(ctx, req, nil)
256}
257
258// ProjectColumnMoveOptions specifies the parameters to the
259// ProjectsService.MoveProjectColumn method.
260type ProjectColumnMoveOptions struct {
261	// Position can be one of "first", "last", or "after:<column-id>", where
262	// <column-id> is the ID of a column in the same project. (Required.)
263	Position string `json:"position"`
264}
265
266// MoveProjectColumn moves a column within a GitHub Project.
267//
268// GitHub API docs: https://developer.github.com/v3/projects/columns/#move-a-project-column
269func (s *ProjectsService) MoveProjectColumn(ctx context.Context, columnID int64, opts *ProjectColumnMoveOptions) (*Response, error) {
270	u := fmt.Sprintf("projects/columns/%v/moves", columnID)
271	req, err := s.client.NewRequest("POST", u, opts)
272	if err != nil {
273		return nil, err
274	}
275
276	// TODO: remove custom Accept header when this API fully launches.
277	req.Header.Set("Accept", mediaTypeProjectsPreview)
278
279	return s.client.Do(ctx, req, nil)
280}
281
282// ProjectCard represents a card in a column of a GitHub Project.
283//
284// GitHub API docs: https://developer.github.com/v3/projects/cards/#get-a-project-card
285type ProjectCard struct {
286	URL        *string    `json:"url,omitempty"`
287	ColumnURL  *string    `json:"column_url,omitempty"`
288	ContentURL *string    `json:"content_url,omitempty"`
289	ID         *int64     `json:"id,omitempty"`
290	Note       *string    `json:"note,omitempty"`
291	Creator    *User      `json:"creator,omitempty"`
292	CreatedAt  *Timestamp `json:"created_at,omitempty"`
293	UpdatedAt  *Timestamp `json:"updated_at,omitempty"`
294	NodeID     *string    `json:"node_id,omitempty"`
295	Archived   *bool      `json:"archived,omitempty"`
296
297	// The following fields are only populated by Webhook events.
298	ColumnID *int64 `json:"column_id,omitempty"`
299
300	// The following fields are only populated by Events API.
301	ProjectID          *int64  `json:"project_id,omitempty"`
302	ProjectURL         *string `json:"project_url,omitempty"`
303	ColumnName         *string `json:"column_name,omitempty"`
304	PreviousColumnName *string `json:"previous_column_name,omitempty"` // Populated in "moved_columns_in_project" event deliveries.
305}
306
307// ProjectCardListOptions specifies the optional parameters to the
308// ProjectsService.ListProjectCards method.
309type ProjectCardListOptions struct {
310	// ArchivedState is used to list all, archived, or not_archived project cards.
311	// Defaults to not_archived when you omit this parameter.
312	ArchivedState *string `url:"archived_state,omitempty"`
313
314	ListOptions
315}
316
317// ListProjectCards lists the cards in a column of a GitHub Project.
318//
319// GitHub API docs: https://developer.github.com/v3/projects/cards/#list-project-cards
320func (s *ProjectsService) ListProjectCards(ctx context.Context, columnID int64, opts *ProjectCardListOptions) ([]*ProjectCard, *Response, error) {
321	u := fmt.Sprintf("projects/columns/%v/cards", columnID)
322	u, err := addOptions(u, opts)
323	if err != nil {
324		return nil, nil, err
325	}
326
327	req, err := s.client.NewRequest("GET", u, nil)
328	if err != nil {
329		return nil, nil, err
330	}
331
332	// TODO: remove custom Accept headers when APIs fully launch.
333	req.Header.Set("Accept", mediaTypeProjectsPreview)
334
335	cards := []*ProjectCard{}
336	resp, err := s.client.Do(ctx, req, &cards)
337	if err != nil {
338		return nil, resp, err
339	}
340
341	return cards, resp, nil
342}
343
344// GetProjectCard gets a card in a column of a GitHub Project.
345//
346// GitHub API docs: https://developer.github.com/v3/projects/cards/#get-a-project-card
347func (s *ProjectsService) GetProjectCard(ctx context.Context, cardID int64) (*ProjectCard, *Response, error) {
348	u := fmt.Sprintf("projects/columns/cards/%v", cardID)
349	req, err := s.client.NewRequest("GET", u, nil)
350	if err != nil {
351		return nil, nil, err
352	}
353
354	// TODO: remove custom Accept headers when APIs fully launch.
355	req.Header.Set("Accept", mediaTypeProjectsPreview)
356
357	card := &ProjectCard{}
358	resp, err := s.client.Do(ctx, req, card)
359	if err != nil {
360		return nil, resp, err
361	}
362
363	return card, resp, nil
364}
365
366// ProjectCardOptions specifies the parameters to the
367// ProjectsService.CreateProjectCard and
368// ProjectsService.UpdateProjectCard methods.
369type ProjectCardOptions struct {
370	// The note of the card. Note and ContentID are mutually exclusive.
371	Note string `json:"note,omitempty"`
372	// The ID (not Number) of the Issue to associate with this card.
373	// Note and ContentID are mutually exclusive.
374	ContentID int64 `json:"content_id,omitempty"`
375	// The type of content to associate with this card. Possible values are: "Issue" and "PullRequest".
376	ContentType string `json:"content_type,omitempty"`
377	// Use true to archive a project card.
378	// Specify false if you need to restore a previously archived project card.
379	Archived *bool `json:"archived,omitempty"`
380}
381
382// CreateProjectCard creates a card in the specified column of a GitHub Project.
383//
384// GitHub API docs: https://developer.github.com/v3/projects/cards/#create-a-project-card
385func (s *ProjectsService) CreateProjectCard(ctx context.Context, columnID int64, opts *ProjectCardOptions) (*ProjectCard, *Response, error) {
386	u := fmt.Sprintf("projects/columns/%v/cards", columnID)
387	req, err := s.client.NewRequest("POST", u, opts)
388	if err != nil {
389		return nil, nil, err
390	}
391
392	// TODO: remove custom Accept headers when APIs fully launch.
393	req.Header.Set("Accept", mediaTypeProjectsPreview)
394
395	card := &ProjectCard{}
396	resp, err := s.client.Do(ctx, req, card)
397	if err != nil {
398		return nil, resp, err
399	}
400
401	return card, resp, nil
402}
403
404// UpdateProjectCard updates a card of a GitHub Project.
405//
406// GitHub API docs: https://developer.github.com/v3/projects/cards/#update-a-project-card
407func (s *ProjectsService) UpdateProjectCard(ctx context.Context, cardID int64, opts *ProjectCardOptions) (*ProjectCard, *Response, error) {
408	u := fmt.Sprintf("projects/columns/cards/%v", cardID)
409	req, err := s.client.NewRequest("PATCH", u, opts)
410	if err != nil {
411		return nil, nil, err
412	}
413
414	// TODO: remove custom Accept headers when APIs fully launch.
415	req.Header.Set("Accept", mediaTypeProjectsPreview)
416
417	card := &ProjectCard{}
418	resp, err := s.client.Do(ctx, req, card)
419	if err != nil {
420		return nil, resp, err
421	}
422
423	return card, resp, nil
424}
425
426// DeleteProjectCard deletes a card from a GitHub Project.
427//
428// GitHub API docs: https://developer.github.com/v3/projects/cards/#delete-a-project-card
429func (s *ProjectsService) DeleteProjectCard(ctx context.Context, cardID int64) (*Response, error) {
430	u := fmt.Sprintf("projects/columns/cards/%v", cardID)
431	req, err := s.client.NewRequest("DELETE", u, nil)
432	if err != nil {
433		return nil, err
434	}
435
436	// TODO: remove custom Accept header when this API fully launches.
437	req.Header.Set("Accept", mediaTypeProjectsPreview)
438
439	return s.client.Do(ctx, req, nil)
440}
441
442// ProjectCardMoveOptions specifies the parameters to the
443// ProjectsService.MoveProjectCard method.
444type ProjectCardMoveOptions struct {
445	// Position can be one of "top", "bottom", or "after:<card-id>", where
446	// <card-id> is the ID of a card in the same project.
447	Position string `json:"position"`
448	// ColumnID is the ID of a column in the same project. Note that ColumnID
449	// is required when using Position "after:<card-id>" when that card is in
450	// another column; otherwise it is optional.
451	ColumnID int64 `json:"column_id,omitempty"`
452}
453
454// MoveProjectCard moves a card within a GitHub Project.
455//
456// GitHub API docs: https://developer.github.com/v3/projects/cards/#move-a-project-card
457func (s *ProjectsService) MoveProjectCard(ctx context.Context, cardID int64, opts *ProjectCardMoveOptions) (*Response, error) {
458	u := fmt.Sprintf("projects/columns/cards/%v/moves", cardID)
459	req, err := s.client.NewRequest("POST", u, opts)
460	if err != nil {
461		return nil, err
462	}
463
464	// TODO: remove custom Accept header when this API fully launches.
465	req.Header.Set("Accept", mediaTypeProjectsPreview)
466
467	return s.client.Do(ctx, req, nil)
468}
469
470// ProjectCollaboratorOptions specifies the optional parameters to the
471// ProjectsService.AddProjectCollaborator method.
472type ProjectCollaboratorOptions struct {
473	// Permission specifies the permission to grant to the collaborator.
474	// Possible values are:
475	//     "read" - can read, but not write to or administer this project.
476	//     "write" - can read and write, but not administer this project.
477	//     "admin" - can read, write and administer this project.
478	//
479	// Default value is "write"
480	Permission *string `json:"permission,omitempty"`
481}
482
483// AddProjectCollaborator adds a collaborator to an organization project and sets
484// their permission level. You must be an organization owner or a project admin to add a collaborator.
485//
486// GitHub API docs: https://developer.github.com/v3/projects/collaborators/#add-project-collaborator
487func (s *ProjectsService) AddProjectCollaborator(ctx context.Context, id int64, username string, opts *ProjectCollaboratorOptions) (*Response, error) {
488	u := fmt.Sprintf("projects/%v/collaborators/%v", id, username)
489	req, err := s.client.NewRequest("PUT", u, opts)
490	if err != nil {
491		return nil, err
492	}
493
494	// TODO: remove custom Accept header when this API fully launches.
495	req.Header.Set("Accept", mediaTypeProjectsPreview)
496
497	return s.client.Do(ctx, req, nil)
498}
499
500// RemoveProjectCollaborator removes a collaborator from an organization project.
501// You must be an organization owner or a project admin to remove a collaborator.
502//
503// GitHub API docs: https://developer.github.com/v3/projects/collaborators/#remove-project-collaborator
504func (s *ProjectsService) RemoveProjectCollaborator(ctx context.Context, id int64, username string) (*Response, error) {
505	u := fmt.Sprintf("projects/%v/collaborators/%v", id, username)
506	req, err := s.client.NewRequest("DELETE", u, nil)
507	if err != nil {
508		return nil, err
509	}
510
511	// TODO: remove custom Accept header when this API fully launches.
512	req.Header.Set("Accept", mediaTypeProjectsPreview)
513
514	return s.client.Do(ctx, req, nil)
515}
516
517// ListCollaboratorOptions specifies the optional parameters to the
518// ProjectsService.ListProjectCollaborators method.
519type ListCollaboratorOptions struct {
520	// Affiliation specifies how collaborators should be filtered by their affiliation.
521	// Possible values are:
522	//     "outside" - All outside collaborators of an organization-owned repository
523	//     "direct" - All collaborators with permissions to an organization-owned repository,
524	//              regardless of organization membership status
525	//     "all" - All collaborators the authenticated user can see
526	//
527	// Default value is "all".
528	Affiliation *string `url:"affiliation,omitempty"`
529
530	ListOptions
531}
532
533// ListProjectCollaborators lists the collaborators for an organization project. For a project,
534// the list of collaborators includes outside collaborators, organization members that are direct
535// collaborators, organization members with access through team memberships, organization members
536// with access through default organization permissions, and organization owners. You must be an
537// organization owner or a project admin to list collaborators.
538//
539// GitHub API docs: https://developer.github.com/v3/projects/collaborators/#list-project-collaborators
540func (s *ProjectsService) ListProjectCollaborators(ctx context.Context, id int64, opts *ListCollaboratorOptions) ([]*User, *Response, error) {
541	u := fmt.Sprintf("projects/%v/collaborators", id)
542	u, err := addOptions(u, opts)
543	if err != nil {
544		return nil, nil, err
545	}
546
547	req, err := s.client.NewRequest("GET", u, nil)
548	if err != nil {
549		return nil, nil, err
550	}
551
552	// TODO: remove custom Accept header when this API fully launches.
553	req.Header.Set("Accept", mediaTypeProjectsPreview)
554
555	var users []*User
556	resp, err := s.client.Do(ctx, req, &users)
557	if err != nil {
558		return nil, resp, err
559	}
560
561	return users, resp, nil
562}
563
564// ProjectPermissionLevel represents the permission level an organization
565// member has for a given project.
566type ProjectPermissionLevel struct {
567	// Possible values: "admin", "write", "read", "none"
568	Permission *string `json:"permission,omitempty"`
569
570	User *User `json:"user,omitempty"`
571}
572
573// ReviewProjectCollaboratorPermission returns the collaborator's permission level for an organization
574// project. Possible values for the permission key: "admin", "write", "read", "none".
575// You must be an organization owner or a project admin to review a user's permission level.
576//
577// GitHub API docs: https://developer.github.com/v3/projects/collaborators/#get-project-permission-for-a-user
578func (s *ProjectsService) ReviewProjectCollaboratorPermission(ctx context.Context, id int64, username string) (*ProjectPermissionLevel, *Response, error) {
579	u := fmt.Sprintf("projects/%v/collaborators/%v/permission", id, username)
580	req, err := s.client.NewRequest("GET", u, nil)
581	if err != nil {
582		return nil, nil, err
583	}
584
585	// TODO: remove custom Accept header when this API fully launches.
586	req.Header.Set("Accept", mediaTypeProjectsPreview)
587
588	ppl := new(ProjectPermissionLevel)
589	resp, err := s.client.Do(ctx, req, ppl)
590	if err != nil {
591		return nil, resp, err
592	}
593	return ppl, resp, nil
594}
595