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// PullRequestComment represents a comment left on a pull request.
16type PullRequestComment struct {
17	ID                  *int64     `json:"id,omitempty"`
18	NodeID              *string    `json:"node_id,omitempty"`
19	InReplyTo           *int64     `json:"in_reply_to_id,omitempty"`
20	Body                *string    `json:"body,omitempty"`
21	Path                *string    `json:"path,omitempty"`
22	DiffHunk            *string    `json:"diff_hunk,omitempty"`
23	PullRequestReviewID *int64     `json:"pull_request_review_id,omitempty"`
24	Position            *int       `json:"position,omitempty"`
25	OriginalPosition    *int       `json:"original_position,omitempty"`
26	StartLine           *int       `json:"start_line,omitempty"`
27	Line                *int       `json:"line,omitempty"`
28	OriginalLine        *int       `json:"original_line,omitempty"`
29	OriginalStartLine   *int       `json:"original_start_line,omitempty"`
30	Side                *string    `json:"side,omitempty"`
31	StartSide           *string    `json:"start_side,omitempty"`
32	CommitID            *string    `json:"commit_id,omitempty"`
33	OriginalCommitID    *string    `json:"original_commit_id,omitempty"`
34	User                *User      `json:"user,omitempty"`
35	Reactions           *Reactions `json:"reactions,omitempty"`
36	CreatedAt           *time.Time `json:"created_at,omitempty"`
37	UpdatedAt           *time.Time `json:"updated_at,omitempty"`
38	// AuthorAssociation is the comment author's relationship to the pull request's repository.
39	// Possible values are "COLLABORATOR", "CONTRIBUTOR", "FIRST_TIMER", "FIRST_TIME_CONTRIBUTOR", "MEMBER", "OWNER", or "NONE".
40	AuthorAssociation *string `json:"author_association,omitempty"`
41	URL               *string `json:"url,omitempty"`
42	HTMLURL           *string `json:"html_url,omitempty"`
43	PullRequestURL    *string `json:"pull_request_url,omitempty"`
44}
45
46func (p PullRequestComment) String() string {
47	return Stringify(p)
48}
49
50// PullRequestListCommentsOptions specifies the optional parameters to the
51// PullRequestsService.ListComments method.
52type PullRequestListCommentsOptions struct {
53	// Sort specifies how to sort comments. Possible values are: created, updated.
54	Sort string `url:"sort,omitempty"`
55
56	// Direction in which to sort comments. Possible values are: asc, desc.
57	Direction string `url:"direction,omitempty"`
58
59	// Since filters comments by time.
60	Since time.Time `url:"since,omitempty"`
61
62	ListOptions
63}
64
65// ListComments lists all comments on the specified pull request. Specifying a
66// pull request number of 0 will return all comments on all pull requests for
67// the repository.
68//
69// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#list-review-comments-on-a-pull-request
70// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#list-review-comments-in-a-repository
71func (s *PullRequestsService) ListComments(ctx context.Context, owner, repo string, number int, opts *PullRequestListCommentsOptions) ([]*PullRequestComment, *Response, error) {
72	var u string
73	if number == 0 {
74		u = fmt.Sprintf("repos/%v/%v/pulls/comments", owner, repo)
75	} else {
76		u = fmt.Sprintf("repos/%v/%v/pulls/%d/comments", owner, repo, number)
77	}
78	u, err := addOptions(u, opts)
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	acceptHeaders := []string{mediaTypeReactionsPreview, mediaTypeMultiLineCommentsPreview}
90	req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
91
92	var comments []*PullRequestComment
93	resp, err := s.client.Do(ctx, req, &comments)
94	if err != nil {
95		return nil, resp, err
96	}
97
98	return comments, resp, nil
99}
100
101// GetComment fetches the specified pull request comment.
102//
103// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#get-a-review-comment-for-a-pull-request
104func (s *PullRequestsService) GetComment(ctx context.Context, owner, repo string, commentID int64) (*PullRequestComment, *Response, error) {
105	u := fmt.Sprintf("repos/%v/%v/pulls/comments/%d", owner, repo, commentID)
106	req, err := s.client.NewRequest("GET", u, nil)
107	if err != nil {
108		return nil, nil, err
109	}
110
111	// TODO: remove custom Accept header when this API fully launches.
112	acceptHeaders := []string{mediaTypeReactionsPreview, mediaTypeMultiLineCommentsPreview}
113	req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
114
115	comment := new(PullRequestComment)
116	resp, err := s.client.Do(ctx, req, comment)
117	if err != nil {
118		return nil, resp, err
119	}
120
121	return comment, resp, nil
122}
123
124// CreateComment creates a new comment on the specified pull request.
125//
126// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#create-a-review-comment-for-a-pull-request
127func (s *PullRequestsService) CreateComment(ctx context.Context, owner, repo string, number int, comment *PullRequestComment) (*PullRequestComment, *Response, error) {
128	u := fmt.Sprintf("repos/%v/%v/pulls/%d/comments", owner, repo, number)
129	req, err := s.client.NewRequest("POST", u, comment)
130	if err != nil {
131		return nil, nil, err
132	}
133	// TODO: remove custom Accept headers when their respective API fully launches.
134	acceptHeaders := []string{mediaTypeReactionsPreview, mediaTypeMultiLineCommentsPreview}
135	req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
136
137	c := new(PullRequestComment)
138	resp, err := s.client.Do(ctx, req, c)
139	if err != nil {
140		return nil, resp, err
141	}
142
143	return c, resp, nil
144}
145
146// CreateCommentInReplyTo creates a new comment as a reply to an existing pull request comment.
147//
148// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#create-a-review-comment-for-a-pull-request
149func (s *PullRequestsService) CreateCommentInReplyTo(ctx context.Context, owner, repo string, number int, body string, commentID int64) (*PullRequestComment, *Response, error) {
150	comment := &struct {
151		Body      string `json:"body,omitempty"`
152		InReplyTo int64  `json:"in_reply_to,omitempty"`
153	}{
154		Body:      body,
155		InReplyTo: commentID,
156	}
157	u := fmt.Sprintf("repos/%v/%v/pulls/%d/comments", owner, repo, number)
158	req, err := s.client.NewRequest("POST", u, comment)
159	if err != nil {
160		return nil, nil, err
161	}
162
163	c := new(PullRequestComment)
164	resp, err := s.client.Do(ctx, req, c)
165	if err != nil {
166		return nil, resp, err
167	}
168
169	return c, resp, nil
170}
171
172// EditComment updates a pull request comment.
173// A non-nil comment.Body must be provided. Other comment fields should be left nil.
174//
175// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#update-a-review-comment-for-a-pull-request
176func (s *PullRequestsService) EditComment(ctx context.Context, owner, repo string, commentID int64, comment *PullRequestComment) (*PullRequestComment, *Response, error) {
177	u := fmt.Sprintf("repos/%v/%v/pulls/comments/%d", owner, repo, commentID)
178	req, err := s.client.NewRequest("PATCH", u, comment)
179	if err != nil {
180		return nil, nil, err
181	}
182
183	c := new(PullRequestComment)
184	resp, err := s.client.Do(ctx, req, c)
185	if err != nil {
186		return nil, resp, err
187	}
188
189	return c, resp, nil
190}
191
192// DeleteComment deletes a pull request comment.
193//
194// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls/#delete-a-review-comment-for-a-pull-request
195func (s *PullRequestsService) DeleteComment(ctx context.Context, owner, repo string, commentID int64) (*Response, error) {
196	u := fmt.Sprintf("repos/%v/%v/pulls/comments/%d", owner, repo, commentID)
197	req, err := s.client.NewRequest("DELETE", u, nil)
198	if err != nil {
199		return nil, err
200	}
201	return s.client.Do(ctx, req, nil)
202}
203