mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Add review request api (#11355)
* Add review request api
* add : POST /repos/{owner}/{repo}/pulls/{index}/requested_reviewers
* Remove : DELET /repos/{owner}/{repo}/pulls/{index}/requested_reviewers
* fix some request review bug
* block delet request review by models/DeleteReview()
Signed-off-by: a1012112796 <1012112796@qq.com>
* make fmt
* fix bug
* fix test code
* fix typo
* Apply suggestion from code review @jonasfranz
* fix swagger ref
* fix typo
Co-authored-by: Lauris BH <lauris@nix.lv>
* fix comment
* Change response message
* chang response so some simplfy
* Add ErrIllLegalReviewRequest
fix some nits
* make fmt
* Apply suggestions from code review
Co-authored-by: silverwind <me@silverwind.io>
* * Add team support
* fix test
* fix an known bug
* fix nit
* fix test
* Apply suggestions from code review
Co-authored-by: zeripath <art27@cantab.net>
* update get api and add test
Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: zeripath <art27@cantab.net>
			
			
This commit is contained in:
		@@ -153,7 +153,7 @@ func TestAPISearchIssues(t *testing.T) {
 | 
				
			|||||||
	var apiIssues []*api.Issue
 | 
						var apiIssues []*api.Issue
 | 
				
			||||||
	DecodeJSON(t, resp, &apiIssues)
 | 
						DecodeJSON(t, resp, &apiIssues)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert.Len(t, apiIssues, 9)
 | 
						assert.Len(t, apiIssues, 10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	query := url.Values{}
 | 
						query := url.Values{}
 | 
				
			||||||
	query.Add("token", token)
 | 
						query.Add("token", token)
 | 
				
			||||||
@@ -161,7 +161,7 @@ func TestAPISearchIssues(t *testing.T) {
 | 
				
			|||||||
	req = NewRequest(t, "GET", link.String())
 | 
						req = NewRequest(t, "GET", link.String())
 | 
				
			||||||
	resp = session.MakeRequest(t, req, http.StatusOK)
 | 
						resp = session.MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
	DecodeJSON(t, resp, &apiIssues)
 | 
						DecodeJSON(t, resp, &apiIssues)
 | 
				
			||||||
	assert.Len(t, apiIssues, 9)
 | 
						assert.Len(t, apiIssues, 10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	query.Add("state", "closed")
 | 
						query.Add("state", "closed")
 | 
				
			||||||
	link.RawQuery = query.Encode()
 | 
						link.RawQuery = query.Encode()
 | 
				
			||||||
@@ -182,7 +182,7 @@ func TestAPISearchIssues(t *testing.T) {
 | 
				
			|||||||
	req = NewRequest(t, "GET", link.String())
 | 
						req = NewRequest(t, "GET", link.String())
 | 
				
			||||||
	resp = session.MakeRequest(t, req, http.StatusOK)
 | 
						resp = session.MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
	DecodeJSON(t, resp, &apiIssues)
 | 
						DecodeJSON(t, resp, &apiIssues)
 | 
				
			||||||
	assert.Len(t, apiIssues, 1)
 | 
						assert.Len(t, apiIssues, 2)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestAPISearchIssuesWithLabels(t *testing.T) {
 | 
					func TestAPISearchIssuesWithLabels(t *testing.T) {
 | 
				
			||||||
@@ -197,7 +197,7 @@ func TestAPISearchIssuesWithLabels(t *testing.T) {
 | 
				
			|||||||
	var apiIssues []*api.Issue
 | 
						var apiIssues []*api.Issue
 | 
				
			||||||
	DecodeJSON(t, resp, &apiIssues)
 | 
						DecodeJSON(t, resp, &apiIssues)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert.Len(t, apiIssues, 9)
 | 
						assert.Len(t, apiIssues, 10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	query := url.Values{}
 | 
						query := url.Values{}
 | 
				
			||||||
	query.Add("token", token)
 | 
						query.Add("token", token)
 | 
				
			||||||
@@ -205,7 +205,7 @@ func TestAPISearchIssuesWithLabels(t *testing.T) {
 | 
				
			|||||||
	req = NewRequest(t, "GET", link.String())
 | 
						req = NewRequest(t, "GET", link.String())
 | 
				
			||||||
	resp = session.MakeRequest(t, req, http.StatusOK)
 | 
						resp = session.MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
	DecodeJSON(t, resp, &apiIssues)
 | 
						DecodeJSON(t, resp, &apiIssues)
 | 
				
			||||||
	assert.Len(t, apiIssues, 9)
 | 
						assert.Len(t, apiIssues, 10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	query.Add("labels", "label1")
 | 
						query.Add("labels", "label1")
 | 
				
			||||||
	link.RawQuery = query.Encode()
 | 
						link.RawQuery = query.Encode()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -122,4 +122,110 @@ func TestAPIPullReview(t *testing.T) {
 | 
				
			|||||||
	assert.EqualValues(t, 0, review.CodeCommentsCount)
 | 
						assert.EqualValues(t, 0, review.CodeCommentsCount)
 | 
				
			||||||
	req = NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, review.ID, token)
 | 
						req = NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, review.ID, token)
 | 
				
			||||||
	resp = session.MakeRequest(t, req, http.StatusNoContent)
 | 
						resp = session.MakeRequest(t, req, http.StatusNoContent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// test get review requests
 | 
				
			||||||
 | 
						// to make it simple, use same api with get review
 | 
				
			||||||
 | 
						pullIssue12 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 12}).(*models.Issue)
 | 
				
			||||||
 | 
						assert.NoError(t, pullIssue12.LoadAttributes())
 | 
				
			||||||
 | 
						repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue12.RepoID}).(*models.Repository)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token)
 | 
				
			||||||
 | 
						resp = session.MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
						DecodeJSON(t, resp, &reviews)
 | 
				
			||||||
 | 
						assert.EqualValues(t, 11, reviews[0].ID)
 | 
				
			||||||
 | 
						assert.EqualValues(t, "REQUEST_REVIEW", reviews[0].State)
 | 
				
			||||||
 | 
						assert.EqualValues(t, 0, reviews[0].CodeCommentsCount)
 | 
				
			||||||
 | 
						assert.EqualValues(t, false, reviews[0].Stale)
 | 
				
			||||||
 | 
						assert.EqualValues(t, true, reviews[0].Official)
 | 
				
			||||||
 | 
						assert.EqualValues(t, "test_team", reviews[0].ReviewerTeam.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.EqualValues(t, 12, reviews[1].ID)
 | 
				
			||||||
 | 
						assert.EqualValues(t, "REQUEST_REVIEW", reviews[1].State)
 | 
				
			||||||
 | 
						assert.EqualValues(t, 0, reviews[0].CodeCommentsCount)
 | 
				
			||||||
 | 
						assert.EqualValues(t, false, reviews[1].Stale)
 | 
				
			||||||
 | 
						assert.EqualValues(t, true, reviews[1].Official)
 | 
				
			||||||
 | 
						assert.EqualValues(t, 1, reviews[1].Reviewer.ID)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestAPIPullReviewRequest(t *testing.T) {
 | 
				
			||||||
 | 
						defer prepareTestEnv(t)()
 | 
				
			||||||
 | 
						pullIssue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 3}).(*models.Issue)
 | 
				
			||||||
 | 
						assert.NoError(t, pullIssue.LoadAttributes())
 | 
				
			||||||
 | 
						repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue.RepoID}).(*models.Repository)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test add Review Request
 | 
				
			||||||
 | 
						session := loginUser(t, "user2")
 | 
				
			||||||
 | 
						token := getTokenForLoggedInUser(t, session)
 | 
				
			||||||
 | 
						req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.PullReviewRequestOptions{
 | 
				
			||||||
 | 
							Reviewers: []string{"user4@example.com", "user8"},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						session.MakeRequest(t, req, http.StatusCreated)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// poster of pr can't be reviewer
 | 
				
			||||||
 | 
						req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.PullReviewRequestOptions{
 | 
				
			||||||
 | 
							Reviewers: []string{"user1"},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						session.MakeRequest(t, req, http.StatusUnprocessableEntity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// test user not exist
 | 
				
			||||||
 | 
						req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.PullReviewRequestOptions{
 | 
				
			||||||
 | 
							Reviewers: []string{"testOther"},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						session.MakeRequest(t, req, http.StatusNotFound)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test Remove Review Request
 | 
				
			||||||
 | 
						session2 := loginUser(t, "user4")
 | 
				
			||||||
 | 
						token2 := getTokenForLoggedInUser(t, session2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token2), &api.PullReviewRequestOptions{
 | 
				
			||||||
 | 
							Reviewers: []string{"user4"},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						session.MakeRequest(t, req, http.StatusNoContent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// doer is not admin
 | 
				
			||||||
 | 
						req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token2), &api.PullReviewRequestOptions{
 | 
				
			||||||
 | 
							Reviewers: []string{"user8"},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						session.MakeRequest(t, req, http.StatusUnprocessableEntity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.PullReviewRequestOptions{
 | 
				
			||||||
 | 
							Reviewers: []string{"user8"},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						session.MakeRequest(t, req, http.StatusNoContent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test team review request
 | 
				
			||||||
 | 
						pullIssue12 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 12}).(*models.Issue)
 | 
				
			||||||
 | 
						assert.NoError(t, pullIssue12.LoadAttributes())
 | 
				
			||||||
 | 
						repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue12.RepoID}).(*models.Repository)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test add Team Review Request
 | 
				
			||||||
 | 
						req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{
 | 
				
			||||||
 | 
							TeamReviewers: []string{"team1", "owners"},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						session.MakeRequest(t, req, http.StatusCreated)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test add Team Review Request to not allowned
 | 
				
			||||||
 | 
						req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{
 | 
				
			||||||
 | 
							TeamReviewers: []string{"test_team"},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						session.MakeRequest(t, req, http.StatusUnprocessableEntity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test add Team Review Request to not exist
 | 
				
			||||||
 | 
						req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{
 | 
				
			||||||
 | 
							TeamReviewers: []string{"not_exist_team"},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						session.MakeRequest(t, req, http.StatusNotFound)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test Remove team Review Request
 | 
				
			||||||
 | 
						req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{
 | 
				
			||||||
 | 
							TeamReviewers: []string{"team1"},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						session.MakeRequest(t, req, http.StatusNoContent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// empty request test
 | 
				
			||||||
 | 
						req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{})
 | 
				
			||||||
 | 
						session.MakeRequest(t, req, http.StatusCreated)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{})
 | 
				
			||||||
 | 
						session.MakeRequest(t, req, http.StatusNoContent)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					d22b4d4daa5be07329fcef6ed458f00cf3392da0
 | 
				
			||||||
@@ -2003,7 +2003,7 @@ type ErrNotValidReviewRequest struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// IsErrNotValidReviewRequest checks if an error is a ErrNotValidReviewRequest.
 | 
					// IsErrNotValidReviewRequest checks if an error is a ErrNotValidReviewRequest.
 | 
				
			||||||
func IsErrNotValidReviewRequest(err error) bool {
 | 
					func IsErrNotValidReviewRequest(err error) bool {
 | 
				
			||||||
	_, ok := err.(ErrReviewNotExist)
 | 
						_, ok := err.(ErrNotValidReviewRequest)
 | 
				
			||||||
	return ok
 | 
						return ok
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -135,3 +135,15 @@
 | 
				
			|||||||
  is_pull: true
 | 
					  is_pull: true
 | 
				
			||||||
  created_unix: 1579194806
 | 
					  created_unix: 1579194806
 | 
				
			||||||
  updated_unix: 1579194806
 | 
					  updated_unix: 1579194806
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-
 | 
				
			||||||
 | 
					  id: 12
 | 
				
			||||||
 | 
					  repo_id: 3
 | 
				
			||||||
 | 
					  index: 2
 | 
				
			||||||
 | 
					  poster_id: 2
 | 
				
			||||||
 | 
					  name: pull6
 | 
				
			||||||
 | 
					  content: content for the a pull request
 | 
				
			||||||
 | 
					  is_closed: false
 | 
				
			||||||
 | 
					  is_pull: true
 | 
				
			||||||
 | 
					  created_unix: 1602935696
 | 
				
			||||||
 | 
					  updated_unix: 1602935696
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -63,3 +63,16 @@
 | 
				
			|||||||
  base_branch: branch1
 | 
					  base_branch: branch1
 | 
				
			||||||
  merge_base: 1234567890abcdef
 | 
					  merge_base: 1234567890abcdef
 | 
				
			||||||
  has_merged: false
 | 
					  has_merged: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-
 | 
				
			||||||
 | 
					  id: 6
 | 
				
			||||||
 | 
					  type: 0 # gitea pull request
 | 
				
			||||||
 | 
					  status: 2 # mergable
 | 
				
			||||||
 | 
					  issue_id: 12
 | 
				
			||||||
 | 
					  index: 2
 | 
				
			||||||
 | 
					  head_repo_id: 3
 | 
				
			||||||
 | 
					  base_repo_id: 3
 | 
				
			||||||
 | 
					  head_branch: test_branch
 | 
				
			||||||
 | 
					  base_branch: master
 | 
				
			||||||
 | 
					  merge_base: 2a47ca4b614a9f5a
 | 
				
			||||||
 | 
					  has_merged: false
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,7 +41,7 @@
 | 
				
			|||||||
  is_private: true
 | 
					  is_private: true
 | 
				
			||||||
  num_issues: 1
 | 
					  num_issues: 1
 | 
				
			||||||
  num_closed_issues: 0
 | 
					  num_closed_issues: 0
 | 
				
			||||||
  num_pulls: 0
 | 
					  num_pulls: 1
 | 
				
			||||||
  num_closed_pulls: 0
 | 
					  num_closed_pulls: 0
 | 
				
			||||||
  num_watches: 0
 | 
					  num_watches: 0
 | 
				
			||||||
  num_projects: 1
 | 
					  num_projects: 1
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -86,3 +86,22 @@
 | 
				
			|||||||
  official: true
 | 
					  official: true
 | 
				
			||||||
  updated_unix: 946684815
 | 
					  updated_unix: 946684815
 | 
				
			||||||
  created_unix: 946684815
 | 
					  created_unix: 946684815
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-
 | 
				
			||||||
 | 
					  id: 11
 | 
				
			||||||
 | 
					  type: 4
 | 
				
			||||||
 | 
					  reviewer_id: 0
 | 
				
			||||||
 | 
					  reviewer_team_id: 7
 | 
				
			||||||
 | 
					  issue_id: 12
 | 
				
			||||||
 | 
					  official: true
 | 
				
			||||||
 | 
					  updated_unix: 1602936509
 | 
				
			||||||
 | 
					  created_unix: 1602936509
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-
 | 
				
			||||||
 | 
					  id: 12
 | 
				
			||||||
 | 
					  type: 4
 | 
				
			||||||
 | 
					  reviewer_id: 1
 | 
				
			||||||
 | 
					  issue_id: 12
 | 
				
			||||||
 | 
					  official: true
 | 
				
			||||||
 | 
					  updated_unix: 1603196749
 | 
				
			||||||
 | 
					  created_unix: 1603196749
 | 
				
			||||||
@@ -627,13 +627,14 @@ func AddReviewRequest(issue *Issue, reviewer, doer *User) (*Comment, error) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, err = createReview(sess, CreateReviewOptions{
 | 
						review, err = createReview(sess, CreateReviewOptions{
 | 
				
			||||||
		Type:     ReviewTypeRequest,
 | 
							Type:     ReviewTypeRequest,
 | 
				
			||||||
		Issue:    issue,
 | 
							Issue:    issue,
 | 
				
			||||||
		Reviewer: reviewer,
 | 
							Reviewer: reviewer,
 | 
				
			||||||
		Official: official,
 | 
							Official: official,
 | 
				
			||||||
		Stale:    false,
 | 
							Stale:    false,
 | 
				
			||||||
	}); err != nil {
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -644,6 +645,7 @@ func AddReviewRequest(issue *Issue, reviewer, doer *User) (*Comment, error) {
 | 
				
			|||||||
		Issue:           issue,
 | 
							Issue:           issue,
 | 
				
			||||||
		RemovedAssignee: false,       // Use RemovedAssignee as !isRequest
 | 
							RemovedAssignee: false,       // Use RemovedAssignee as !isRequest
 | 
				
			||||||
		AssigneeID:      reviewer.ID, // Use AssigneeID as reviewer ID
 | 
							AssigneeID:      reviewer.ID, // Use AssigneeID as reviewer ID
 | 
				
			||||||
 | 
							ReviewID:        review.ID,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
@@ -732,7 +734,7 @@ func AddTeamReviewRequest(issue *Issue, reviewer *Team, doer *User) (*Comment, e
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, err = createReview(sess, CreateReviewOptions{
 | 
						if review, err = createReview(sess, CreateReviewOptions{
 | 
				
			||||||
		Type:         ReviewTypeRequest,
 | 
							Type:         ReviewTypeRequest,
 | 
				
			||||||
		Issue:        issue,
 | 
							Issue:        issue,
 | 
				
			||||||
		ReviewerTeam: reviewer,
 | 
							ReviewerTeam: reviewer,
 | 
				
			||||||
@@ -755,6 +757,7 @@ func AddTeamReviewRequest(issue *Issue, reviewer *Team, doer *User) (*Comment, e
 | 
				
			|||||||
		Issue:           issue,
 | 
							Issue:           issue,
 | 
				
			||||||
		RemovedAssignee: false,       // Use RemovedAssignee as !isRequest
 | 
							RemovedAssignee: false,       // Use RemovedAssignee as !isRequest
 | 
				
			||||||
		AssigneeTeamID:  reviewer.ID, // Use AssigneeTeamID as reviewer team ID
 | 
							AssigneeTeamID:  reviewer.ID, // Use AssigneeTeamID as reviewer team ID
 | 
				
			||||||
 | 
							ReviewID:        review.ID,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, fmt.Errorf("createComment(): %v", err)
 | 
							return nil, fmt.Errorf("createComment(): %v", err)
 | 
				
			||||||
@@ -894,6 +897,10 @@ func DeleteReview(r *Review) error {
 | 
				
			|||||||
		return fmt.Errorf("review is not allowed to be 0")
 | 
							return fmt.Errorf("review is not allowed to be 0")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if r.Type == ReviewTypeRequest {
 | 
				
			||||||
 | 
							return fmt.Errorf("review request can not be deleted using this method")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	opts := FindCommentsOptions{
 | 
						opts := FindCommentsOptions{
 | 
				
			||||||
		Type:     CommentTypeCode,
 | 
							Type:     CommentTypeCode,
 | 
				
			||||||
		IssueID:  r.IssueID,
 | 
							IssueID:  r.IssueID,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -284,6 +284,10 @@ func ToOrganization(org *models.User) *api.Organization {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// ToTeam convert models.Team to api.Team
 | 
					// ToTeam convert models.Team to api.Team
 | 
				
			||||||
func ToTeam(team *models.Team) *api.Team {
 | 
					func ToTeam(team *models.Team) *api.Team {
 | 
				
			||||||
 | 
						if team == nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return &api.Team{
 | 
						return &api.Team{
 | 
				
			||||||
		ID:                      team.ID,
 | 
							ID:                      team.ID,
 | 
				
			||||||
		Name:                    team.Name,
 | 
							Name:                    team.Name,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,6 +28,7 @@ func ToPullReview(r *models.Review, doer *models.User) (*api.PullReview, error)
 | 
				
			|||||||
	result := &api.PullReview{
 | 
						result := &api.PullReview{
 | 
				
			||||||
		ID:                r.ID,
 | 
							ID:                r.ID,
 | 
				
			||||||
		Reviewer:          ToUser(r.Reviewer, doer != nil, auth),
 | 
							Reviewer:          ToUser(r.Reviewer, doer != nil, auth),
 | 
				
			||||||
 | 
							ReviewerTeam:      ToTeam(r.ReviewerTeam),
 | 
				
			||||||
		State:             api.ReviewStateUnknown,
 | 
							State:             api.ReviewStateUnknown,
 | 
				
			||||||
		Body:              r.Content,
 | 
							Body:              r.Content,
 | 
				
			||||||
		CommitID:          r.CommitID,
 | 
							CommitID:          r.CommitID,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,7 @@ const (
 | 
				
			|||||||
type PullReview struct {
 | 
					type PullReview struct {
 | 
				
			||||||
	ID                int64           `json:"id"`
 | 
						ID                int64           `json:"id"`
 | 
				
			||||||
	Reviewer          *User           `json:"user"`
 | 
						Reviewer          *User           `json:"user"`
 | 
				
			||||||
 | 
						ReviewerTeam      *Team           `json:"team"`
 | 
				
			||||||
	State             ReviewStateType `json:"state"`
 | 
						State             ReviewStateType `json:"state"`
 | 
				
			||||||
	Body              string          `json:"body"`
 | 
						Body              string          `json:"body"`
 | 
				
			||||||
	CommitID          string          `json:"commit_id"`
 | 
						CommitID          string          `json:"commit_id"`
 | 
				
			||||||
@@ -90,3 +91,9 @@ type SubmitPullReviewOptions struct {
 | 
				
			|||||||
	Event ReviewStateType `json:"event"`
 | 
						Event ReviewStateType `json:"event"`
 | 
				
			||||||
	Body  string          `json:"body"`
 | 
						Body  string          `json:"body"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// PullReviewRequestOptions are options to add or remove pull review requests
 | 
				
			||||||
 | 
					type PullReviewRequestOptions struct {
 | 
				
			||||||
 | 
						Reviewers     []string `json:"reviewers"`
 | 
				
			||||||
 | 
						TeamReviewers []string `json:"team_reviewers"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -827,7 +827,9 @@ func RegisterRoutes(m *macaron.Macaron) {
 | 
				
			|||||||
									Get(repo.GetPullReviewComments)
 | 
														Get(repo.GetPullReviewComments)
 | 
				
			||||||
							})
 | 
												})
 | 
				
			||||||
						})
 | 
											})
 | 
				
			||||||
 | 
											m.Combo("/requested_reviewers").
 | 
				
			||||||
 | 
												Delete(reqToken(), bind(api.PullReviewRequestOptions{}), repo.DeleteReviewRequests).
 | 
				
			||||||
 | 
												Post(reqToken(), bind(api.PullReviewRequestOptions{}), repo.CreateReviewRequests)
 | 
				
			||||||
					})
 | 
										})
 | 
				
			||||||
				}, mustAllowPulls, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(false))
 | 
									}, mustAllowPulls, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(false))
 | 
				
			||||||
				m.Group("/statuses", func() {
 | 
									m.Group("/statuses", func() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,6 +15,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
	api "code.gitea.io/gitea/modules/structs"
 | 
						api "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
	"code.gitea.io/gitea/routers/api/v1/utils"
 | 
						"code.gitea.io/gitea/routers/api/v1/utils"
 | 
				
			||||||
 | 
						issue_service "code.gitea.io/gitea/services/issue"
 | 
				
			||||||
	pull_service "code.gitea.io/gitea/services/pull"
 | 
						pull_service "code.gitea.io/gitea/services/pull"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -539,3 +540,214 @@ func prepareSingleReview(ctx *context.APIContext) (*models.Review, *models.PullR
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return review, pr, false
 | 
						return review, pr, false
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CreateReviewRequests create review requests to an pull request
 | 
				
			||||||
 | 
					func CreateReviewRequests(ctx *context.APIContext, opts api.PullReviewRequestOptions) {
 | 
				
			||||||
 | 
						// swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/requested_reviewers repository repoCreatePullReviewRequests
 | 
				
			||||||
 | 
						// ---
 | 
				
			||||||
 | 
						// summary: create review requests for a pull request
 | 
				
			||||||
 | 
						// produces:
 | 
				
			||||||
 | 
						// - application/json
 | 
				
			||||||
 | 
						// parameters:
 | 
				
			||||||
 | 
						// - name: owner
 | 
				
			||||||
 | 
						//   in: path
 | 
				
			||||||
 | 
						//   description: owner of the repo
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   required: true
 | 
				
			||||||
 | 
						// - name: repo
 | 
				
			||||||
 | 
						//   in: path
 | 
				
			||||||
 | 
						//   description: name of the repo
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   required: true
 | 
				
			||||||
 | 
						// - name: index
 | 
				
			||||||
 | 
						//   in: path
 | 
				
			||||||
 | 
						//   description: index of the pull request
 | 
				
			||||||
 | 
						//   type: integer
 | 
				
			||||||
 | 
						//   format: int64
 | 
				
			||||||
 | 
						//   required: true
 | 
				
			||||||
 | 
						// - name: body
 | 
				
			||||||
 | 
						//   in: body
 | 
				
			||||||
 | 
						//   required: true
 | 
				
			||||||
 | 
						//   schema:
 | 
				
			||||||
 | 
						//     "$ref": "#/definitions/PullReviewRequestOptions"
 | 
				
			||||||
 | 
						// responses:
 | 
				
			||||||
 | 
						//   "201":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/PullReviewList"
 | 
				
			||||||
 | 
						//   "422":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/validationError"
 | 
				
			||||||
 | 
						//   "404":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/notFound"
 | 
				
			||||||
 | 
						apiReviewRequest(ctx, opts, true)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeleteReviewRequests delete review requests to an pull request
 | 
				
			||||||
 | 
					func DeleteReviewRequests(ctx *context.APIContext, opts api.PullReviewRequestOptions) {
 | 
				
			||||||
 | 
						// swagger:operation DELETE /repos/{owner}/{repo}/pulls/{index}/requested_reviewers repository repoDeletePullReviewRequests
 | 
				
			||||||
 | 
						// ---
 | 
				
			||||||
 | 
						// summary: cancel review requests for a pull request
 | 
				
			||||||
 | 
						// produces:
 | 
				
			||||||
 | 
						// - application/json
 | 
				
			||||||
 | 
						// parameters:
 | 
				
			||||||
 | 
						// - name: owner
 | 
				
			||||||
 | 
						//   in: path
 | 
				
			||||||
 | 
						//   description: owner of the repo
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   required: true
 | 
				
			||||||
 | 
						// - name: repo
 | 
				
			||||||
 | 
						//   in: path
 | 
				
			||||||
 | 
						//   description: name of the repo
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   required: true
 | 
				
			||||||
 | 
						// - name: index
 | 
				
			||||||
 | 
						//   in: path
 | 
				
			||||||
 | 
						//   description: index of the pull request
 | 
				
			||||||
 | 
						//   type: integer
 | 
				
			||||||
 | 
						//   format: int64
 | 
				
			||||||
 | 
						//   required: true
 | 
				
			||||||
 | 
						// - name: body
 | 
				
			||||||
 | 
						//   in: body
 | 
				
			||||||
 | 
						//   required: true
 | 
				
			||||||
 | 
						//   schema:
 | 
				
			||||||
 | 
						//     "$ref": "#/definitions/PullReviewRequestOptions"
 | 
				
			||||||
 | 
						// responses:
 | 
				
			||||||
 | 
						//   "204":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/empty"
 | 
				
			||||||
 | 
						//   "422":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/validationError"
 | 
				
			||||||
 | 
						//   "404":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/notFound"
 | 
				
			||||||
 | 
						apiReviewRequest(ctx, opts, false)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions, isAdd bool) {
 | 
				
			||||||
 | 
						pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if models.IsErrPullRequestNotExist(err) {
 | 
				
			||||||
 | 
								ctx.NotFound("GetPullRequestByIndex", err)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := pr.Issue.LoadRepo(); err != nil {
 | 
				
			||||||
 | 
							ctx.Error(http.StatusInternalServerError, "pr.Issue.LoadRepo", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						reviewers := make([]*models.User, 0, len(opts.Reviewers))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						permDoer, err := models.GetUserRepoPermission(pr.Issue.Repo, ctx.User)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, r := range opts.Reviewers {
 | 
				
			||||||
 | 
							var reviewer *models.User
 | 
				
			||||||
 | 
							if strings.Contains(r, "@") {
 | 
				
			||||||
 | 
								reviewer, err = models.GetUserByEmail(r)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								reviewer, err = models.GetUserByName(r)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								if models.IsErrUserNotExist(err) {
 | 
				
			||||||
 | 
									ctx.NotFound("UserNotExist", fmt.Sprintf("User '%s' not exist", r))
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								ctx.Error(http.StatusInternalServerError, "GetUser", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err = issue_service.IsValidReviewRequest(reviewer, ctx.User, isAdd, pr.Issue, &permDoer)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								if models.IsErrNotValidReviewRequest(err) {
 | 
				
			||||||
 | 
									ctx.Error(http.StatusUnprocessableEntity, "NotValidReviewRequest", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								ctx.Error(http.StatusInternalServerError, "IsValidReviewRequest", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							reviewers = append(reviewers, reviewer)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var reviews []*models.Review
 | 
				
			||||||
 | 
						if isAdd {
 | 
				
			||||||
 | 
							reviews = make([]*models.Review, 0, len(reviewers))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, reviewer := range reviewers {
 | 
				
			||||||
 | 
							comment, err := issue_service.ReviewRequest(pr.Issue, ctx.User, reviewer, isAdd)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								ctx.Error(http.StatusInternalServerError, "ReviewRequest", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if comment != nil && isAdd {
 | 
				
			||||||
 | 
								if err = comment.LoadReview(); err != nil {
 | 
				
			||||||
 | 
									ctx.ServerError("ReviewRequest", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								reviews = append(reviews, comment.Review)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ctx.Repo.Repository.Owner.IsOrganization() && len(opts.TeamReviewers) > 0 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							teamReviewers := make([]*models.Team, 0, len(opts.TeamReviewers))
 | 
				
			||||||
 | 
							for _, t := range opts.TeamReviewers {
 | 
				
			||||||
 | 
								var teamReviewer *models.Team
 | 
				
			||||||
 | 
								teamReviewer, err = models.GetTeam(ctx.Repo.Owner.ID, t)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									if models.IsErrTeamNotExist(err) {
 | 
				
			||||||
 | 
										ctx.NotFound("TeamNotExist", fmt.Sprintf("Team '%s' not exist", t))
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									ctx.Error(http.StatusInternalServerError, "ReviewRequest", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								err = issue_service.IsValidTeamReviewRequest(teamReviewer, ctx.User, isAdd, pr.Issue)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									if models.IsErrNotValidReviewRequest(err) {
 | 
				
			||||||
 | 
										ctx.Error(http.StatusUnprocessableEntity, "NotValidReviewRequest", err)
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									ctx.Error(http.StatusInternalServerError, "IsValidTeamReviewRequest", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								teamReviewers = append(teamReviewers, teamReviewer)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for _, teamReviewer := range teamReviewers {
 | 
				
			||||||
 | 
								comment, err := issue_service.TeamReviewRequest(pr.Issue, ctx.User, teamReviewer, isAdd)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									ctx.ServerError("TeamReviewRequest", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if comment != nil && isAdd {
 | 
				
			||||||
 | 
									if err = comment.LoadReview(); err != nil {
 | 
				
			||||||
 | 
										ctx.ServerError("ReviewRequest", err)
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									reviews = append(reviews, comment.Review)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if isAdd {
 | 
				
			||||||
 | 
							apiReviews, err := convert.ToPullReviewList(reviews, ctx.User)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								ctx.Error(http.StatusInternalServerError, "convertToPullReviewList", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ctx.JSON(http.StatusCreated, apiReviews)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							ctx.Status(http.StatusNoContent)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -152,4 +152,7 @@ type swaggerParameterBodies struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// in:body
 | 
						// in:body
 | 
				
			||||||
	MigrateRepoOptions api.MigrateRepoOptions
 | 
						MigrateRepoOptions api.MigrateRepoOptions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// in:body
 | 
				
			||||||
 | 
						PullReviewRequestOptions api.PullReviewRequestOptions
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1699,154 +1699,6 @@ func UpdateIssueAssignee(ctx *context.Context) {
 | 
				
			|||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func isValidReviewRequest(reviewer, doer *models.User, isAdd bool, issue *models.Issue) error {
 | 
					 | 
				
			||||||
	if reviewer.IsOrganization() {
 | 
					 | 
				
			||||||
		return models.ErrNotValidReviewRequest{
 | 
					 | 
				
			||||||
			Reason: "Organization can't be added as reviewer",
 | 
					 | 
				
			||||||
			UserID: doer.ID,
 | 
					 | 
				
			||||||
			RepoID: issue.Repo.ID,
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if doer.IsOrganization() {
 | 
					 | 
				
			||||||
		return models.ErrNotValidReviewRequest{
 | 
					 | 
				
			||||||
			Reason: "Organization can't be doer to add reviewer",
 | 
					 | 
				
			||||||
			UserID: doer.ID,
 | 
					 | 
				
			||||||
			RepoID: issue.Repo.ID,
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	permReviewer, err := models.GetUserRepoPermission(issue.Repo, reviewer)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	permDoer, err := models.GetUserRepoPermission(issue.Repo, doer)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	lastreview, err := models.GetReviewByIssueIDAndUserID(issue.ID, reviewer.ID)
 | 
					 | 
				
			||||||
	if err != nil && !models.IsErrReviewNotExist(err) {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var pemResult bool
 | 
					 | 
				
			||||||
	if isAdd {
 | 
					 | 
				
			||||||
		pemResult = permReviewer.CanAccessAny(models.AccessModeRead, models.UnitTypePullRequests)
 | 
					 | 
				
			||||||
		if !pemResult {
 | 
					 | 
				
			||||||
			return models.ErrNotValidReviewRequest{
 | 
					 | 
				
			||||||
				Reason: "Reviewer can't read",
 | 
					 | 
				
			||||||
				UserID: doer.ID,
 | 
					 | 
				
			||||||
				RepoID: issue.Repo.ID,
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if doer.ID == issue.PosterID && issue.OriginalAuthorID == 0 && lastreview != nil && lastreview.Type != models.ReviewTypeRequest {
 | 
					 | 
				
			||||||
			return nil
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pemResult = permDoer.CanAccessAny(models.AccessModeWrite, models.UnitTypePullRequests)
 | 
					 | 
				
			||||||
		if !pemResult {
 | 
					 | 
				
			||||||
			pemResult, err = models.IsOfficialReviewer(issue, doer)
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				return err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if !pemResult {
 | 
					 | 
				
			||||||
				return models.ErrNotValidReviewRequest{
 | 
					 | 
				
			||||||
					Reason: "Doer can't choose reviewer",
 | 
					 | 
				
			||||||
					UserID: doer.ID,
 | 
					 | 
				
			||||||
					RepoID: issue.Repo.ID,
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if doer.ID == reviewer.ID {
 | 
					 | 
				
			||||||
			return models.ErrNotValidReviewRequest{
 | 
					 | 
				
			||||||
				Reason: "doer can't be reviewer",
 | 
					 | 
				
			||||||
				UserID: doer.ID,
 | 
					 | 
				
			||||||
				RepoID: issue.Repo.ID,
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if reviewer.ID == issue.PosterID && issue.OriginalAuthorID == 0 {
 | 
					 | 
				
			||||||
			return models.ErrNotValidReviewRequest{
 | 
					 | 
				
			||||||
				Reason: "poster of pr can't be reviewer",
 | 
					 | 
				
			||||||
				UserID: doer.ID,
 | 
					 | 
				
			||||||
				RepoID: issue.Repo.ID,
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		if lastreview != nil && lastreview.Type == models.ReviewTypeRequest && lastreview.ReviewerID == doer.ID {
 | 
					 | 
				
			||||||
			return nil
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pemResult = permDoer.IsAdmin()
 | 
					 | 
				
			||||||
		if !pemResult {
 | 
					 | 
				
			||||||
			return models.ErrNotValidReviewRequest{
 | 
					 | 
				
			||||||
				Reason: "Doer is not admin",
 | 
					 | 
				
			||||||
				UserID: doer.ID,
 | 
					 | 
				
			||||||
				RepoID: issue.Repo.ID,
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func isValidTeamReviewRequest(reviewer *models.Team, doer *models.User, isAdd bool, issue *models.Issue) error {
 | 
					 | 
				
			||||||
	if doer.IsOrganization() {
 | 
					 | 
				
			||||||
		return models.ErrNotValidReviewRequest{
 | 
					 | 
				
			||||||
			Reason: "Organization can't be doer to add reviewer",
 | 
					 | 
				
			||||||
			UserID: doer.ID,
 | 
					 | 
				
			||||||
			RepoID: issue.Repo.ID,
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	permission, err := models.GetUserRepoPermission(issue.Repo, doer)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Error("Unable to GetUserRepoPermission for %-v in %-v#%d", doer, issue.Repo, issue.Index)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if isAdd {
 | 
					 | 
				
			||||||
		if issue.Repo.IsPrivate {
 | 
					 | 
				
			||||||
			hasTeam := models.HasTeamRepo(reviewer.OrgID, reviewer.ID, issue.RepoID)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if !hasTeam {
 | 
					 | 
				
			||||||
				return models.ErrNotValidReviewRequest{
 | 
					 | 
				
			||||||
					Reason: "Reviewing team can't read repo",
 | 
					 | 
				
			||||||
					UserID: doer.ID,
 | 
					 | 
				
			||||||
					RepoID: issue.Repo.ID,
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		doerCanWrite := permission.CanAccessAny(models.AccessModeWrite, models.UnitTypePullRequests)
 | 
					 | 
				
			||||||
		if !doerCanWrite {
 | 
					 | 
				
			||||||
			official, err := models.IsOfficialReviewer(issue, doer)
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				log.Error("Unable to Check if IsOfficialReviewer for %-v in %-v#%d", doer, issue.Repo, issue.Index)
 | 
					 | 
				
			||||||
				return err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if !official {
 | 
					 | 
				
			||||||
				return models.ErrNotValidReviewRequest{
 | 
					 | 
				
			||||||
					Reason: "Doer can't choose reviewer",
 | 
					 | 
				
			||||||
					UserID: doer.ID,
 | 
					 | 
				
			||||||
					RepoID: issue.Repo.ID,
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else if !permission.IsAdmin() {
 | 
					 | 
				
			||||||
		return models.ErrNotValidReviewRequest{
 | 
					 | 
				
			||||||
			Reason: "Only admin users can remove team requests. Doer is not admin",
 | 
					 | 
				
			||||||
			UserID: doer.ID,
 | 
					 | 
				
			||||||
			RepoID: issue.Repo.ID,
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// UpdatePullReviewRequest add or remove review request
 | 
					// UpdatePullReviewRequest add or remove review request
 | 
				
			||||||
func UpdatePullReviewRequest(ctx *context.Context) {
 | 
					func UpdatePullReviewRequest(ctx *context.Context) {
 | 
				
			||||||
	issues := getActionIssues(ctx)
 | 
						issues := getActionIssues(ctx)
 | 
				
			||||||
@@ -1907,7 +1759,7 @@ func UpdatePullReviewRequest(ctx *context.Context) {
 | 
				
			|||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			err = isValidTeamReviewRequest(team, ctx.User, action == "attach", issue)
 | 
								err = issue_service.IsValidTeamReviewRequest(team, ctx.User, action == "attach", issue)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				if models.IsErrNotValidReviewRequest(err) {
 | 
									if models.IsErrNotValidReviewRequest(err) {
 | 
				
			||||||
					log.Warn(
 | 
										log.Warn(
 | 
				
			||||||
@@ -1918,11 +1770,11 @@ func UpdatePullReviewRequest(ctx *context.Context) {
 | 
				
			|||||||
					ctx.Status(403)
 | 
										ctx.Status(403)
 | 
				
			||||||
					return
 | 
										return
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				ctx.ServerError("isValidTeamReviewRequest", err)
 | 
									ctx.ServerError("IsValidTeamReviewRequest", err)
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			err = issue_service.TeamReviewRequest(issue, ctx.User, team, action == "attach")
 | 
								_, err = issue_service.TeamReviewRequest(issue, ctx.User, team, action == "attach")
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				ctx.ServerError("TeamReviewRequest", err)
 | 
									ctx.ServerError("TeamReviewRequest", err)
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
@@ -1945,7 +1797,7 @@ func UpdatePullReviewRequest(ctx *context.Context) {
 | 
				
			|||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		err = isValidReviewRequest(reviewer, ctx.User, action == "attach", issue)
 | 
							err = issue_service.IsValidReviewRequest(reviewer, ctx.User, action == "attach", issue, nil)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			if models.IsErrNotValidReviewRequest(err) {
 | 
								if models.IsErrNotValidReviewRequest(err) {
 | 
				
			||||||
				log.Warn(
 | 
									log.Warn(
 | 
				
			||||||
@@ -1960,7 +1812,7 @@ func UpdatePullReviewRequest(ctx *context.Context) {
 | 
				
			|||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		err = issue_service.ReviewRequest(issue, ctx.User, reviewer, action == "attach")
 | 
							_, err = issue_service.ReviewRequest(issue, ctx.User, reviewer, action == "attach")
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			ctx.ServerError("ReviewRequest", err)
 | 
								ctx.ServerError("ReviewRequest", err)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ package issue
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/notification"
 | 
						"code.gitea.io/gitea/modules/notification"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -53,8 +54,7 @@ func ToggleAssignee(issue *models.Issue, doer *models.User, assigneeID int64) (r
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ReviewRequest add or remove a review request from a user for this PR, and make comment for it.
 | 
					// ReviewRequest add or remove a review request from a user for this PR, and make comment for it.
 | 
				
			||||||
func ReviewRequest(issue *models.Issue, doer *models.User, reviewer *models.User, isAdd bool) (err error) {
 | 
					func ReviewRequest(issue *models.Issue, doer *models.User, reviewer *models.User, isAdd bool) (comment *models.Comment, err error) {
 | 
				
			||||||
	var comment *models.Comment
 | 
					 | 
				
			||||||
	if isAdd {
 | 
						if isAdd {
 | 
				
			||||||
		comment, err = models.AddReviewRequest(issue, reviewer, doer)
 | 
							comment, err = models.AddReviewRequest(issue, reviewer, doer)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
@@ -62,19 +62,171 @@ func ReviewRequest(issue *models.Issue, doer *models.User, reviewer *models.User
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if comment != nil {
 | 
						if comment != nil {
 | 
				
			||||||
		notification.NotifyPullReviewRequest(doer, issue, reviewer, isAdd, comment)
 | 
							notification.NotifyPullReviewRequest(doer, issue, reviewer, isAdd, comment)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsValidReviewRequest Check permission for ReviewRequest
 | 
				
			||||||
 | 
					func IsValidReviewRequest(reviewer, doer *models.User, isAdd bool, issue *models.Issue, permDoer *models.Permission) error {
 | 
				
			||||||
 | 
						if reviewer.IsOrganization() {
 | 
				
			||||||
 | 
							return models.ErrNotValidReviewRequest{
 | 
				
			||||||
 | 
								Reason: "Organization can't be added as reviewer",
 | 
				
			||||||
 | 
								UserID: doer.ID,
 | 
				
			||||||
 | 
								RepoID: issue.Repo.ID,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if doer.IsOrganization() {
 | 
				
			||||||
 | 
							return models.ErrNotValidReviewRequest{
 | 
				
			||||||
 | 
								Reason: "Organization can't be doer to add reviewer",
 | 
				
			||||||
 | 
								UserID: doer.ID,
 | 
				
			||||||
 | 
								RepoID: issue.Repo.ID,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						permReviewer, err := models.GetUserRepoPermission(issue.Repo, reviewer)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if permDoer == nil {
 | 
				
			||||||
 | 
							permDoer = new(models.Permission)
 | 
				
			||||||
 | 
							*permDoer, err = models.GetUserRepoPermission(issue.Repo, doer)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lastreview, err := models.GetReviewByIssueIDAndUserID(issue.ID, reviewer.ID)
 | 
				
			||||||
 | 
						if err != nil && !models.IsErrReviewNotExist(err) {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var pemResult bool
 | 
				
			||||||
 | 
						if isAdd {
 | 
				
			||||||
 | 
							pemResult = permReviewer.CanAccessAny(models.AccessModeRead, models.UnitTypePullRequests)
 | 
				
			||||||
 | 
							if !pemResult {
 | 
				
			||||||
 | 
								return models.ErrNotValidReviewRequest{
 | 
				
			||||||
 | 
									Reason: "Reviewer can't read",
 | 
				
			||||||
 | 
									UserID: doer.ID,
 | 
				
			||||||
 | 
									RepoID: issue.Repo.ID,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if doer.ID == issue.PosterID && issue.OriginalAuthorID == 0 && lastreview != nil && lastreview.Type != models.ReviewTypeRequest {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pemResult = permDoer.CanAccessAny(models.AccessModeWrite, models.UnitTypePullRequests)
 | 
				
			||||||
 | 
							if !pemResult {
 | 
				
			||||||
 | 
								pemResult, err = models.IsOfficialReviewer(issue, doer)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !pemResult {
 | 
				
			||||||
 | 
									return models.ErrNotValidReviewRequest{
 | 
				
			||||||
 | 
										Reason: "Doer can't choose reviewer",
 | 
				
			||||||
 | 
										UserID: doer.ID,
 | 
				
			||||||
 | 
										RepoID: issue.Repo.ID,
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if doer.ID == reviewer.ID {
 | 
				
			||||||
 | 
								return models.ErrNotValidReviewRequest{
 | 
				
			||||||
 | 
									Reason: "doer can't be reviewer",
 | 
				
			||||||
 | 
									UserID: doer.ID,
 | 
				
			||||||
 | 
									RepoID: issue.Repo.ID,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if reviewer.ID == issue.PosterID && issue.OriginalAuthorID == 0 {
 | 
				
			||||||
 | 
								return models.ErrNotValidReviewRequest{
 | 
				
			||||||
 | 
									Reason: "poster of pr can't be reviewer",
 | 
				
			||||||
 | 
									UserID: doer.ID,
 | 
				
			||||||
 | 
									RepoID: issue.Repo.ID,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							if lastreview != nil && lastreview.Type == models.ReviewTypeRequest && lastreview.ReviewerID == doer.ID {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pemResult = permDoer.IsAdmin()
 | 
				
			||||||
 | 
							if !pemResult {
 | 
				
			||||||
 | 
								return models.ErrNotValidReviewRequest{
 | 
				
			||||||
 | 
									Reason: "Doer is not admin",
 | 
				
			||||||
 | 
									UserID: doer.ID,
 | 
				
			||||||
 | 
									RepoID: issue.Repo.ID,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsValidTeamReviewRequest Check permission for ReviewRequest Team
 | 
				
			||||||
 | 
					func IsValidTeamReviewRequest(reviewer *models.Team, doer *models.User, isAdd bool, issue *models.Issue) error {
 | 
				
			||||||
 | 
						if doer.IsOrganization() {
 | 
				
			||||||
 | 
							return models.ErrNotValidReviewRequest{
 | 
				
			||||||
 | 
								Reason: "Organization can't be doer to add reviewer",
 | 
				
			||||||
 | 
								UserID: doer.ID,
 | 
				
			||||||
 | 
								RepoID: issue.Repo.ID,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						permission, err := models.GetUserRepoPermission(issue.Repo, doer)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error("Unable to GetUserRepoPermission for %-v in %-v#%d", doer, issue.Repo, issue.Index)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if isAdd {
 | 
				
			||||||
 | 
							if issue.Repo.IsPrivate {
 | 
				
			||||||
 | 
								hasTeam := models.HasTeamRepo(reviewer.OrgID, reviewer.ID, issue.RepoID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if !hasTeam {
 | 
				
			||||||
 | 
									return models.ErrNotValidReviewRequest{
 | 
				
			||||||
 | 
										Reason: "Reviewing team can't read repo",
 | 
				
			||||||
 | 
										UserID: doer.ID,
 | 
				
			||||||
 | 
										RepoID: issue.Repo.ID,
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							doerCanWrite := permission.CanAccessAny(models.AccessModeWrite, models.UnitTypePullRequests)
 | 
				
			||||||
 | 
							if !doerCanWrite {
 | 
				
			||||||
 | 
								official, err := models.IsOfficialReviewer(issue, doer)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.Error("Unable to Check if IsOfficialReviewer for %-v in %-v#%d", doer, issue.Repo, issue.Index)
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !official {
 | 
				
			||||||
 | 
									return models.ErrNotValidReviewRequest{
 | 
				
			||||||
 | 
										Reason: "Doer can't choose reviewer",
 | 
				
			||||||
 | 
										UserID: doer.ID,
 | 
				
			||||||
 | 
										RepoID: issue.Repo.ID,
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else if !permission.IsAdmin() {
 | 
				
			||||||
 | 
							return models.ErrNotValidReviewRequest{
 | 
				
			||||||
 | 
								Reason: "Only admin users can remove team requests. Doer is not admin",
 | 
				
			||||||
 | 
								UserID: doer.ID,
 | 
				
			||||||
 | 
								RepoID: issue.Repo.ID,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TeamReviewRequest add or remove a review request from a team for this PR, and make comment for it.
 | 
					// TeamReviewRequest add or remove a review request from a team for this PR, and make comment for it.
 | 
				
			||||||
func TeamReviewRequest(issue *models.Issue, doer *models.User, reviewer *models.Team, isAdd bool) (err error) {
 | 
					func TeamReviewRequest(issue *models.Issue, doer *models.User, reviewer *models.Team, isAdd bool) (comment *models.Comment, err error) {
 | 
				
			||||||
	var comment *models.Comment
 | 
					 | 
				
			||||||
	if isAdd {
 | 
						if isAdd {
 | 
				
			||||||
		comment, err = models.AddTeamReviewRequest(issue, reviewer, doer)
 | 
							comment, err = models.AddTeamReviewRequest(issue, reviewer, doer)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
@@ -106,5 +258,5 @@ func TeamReviewRequest(issue *models.Issue, doer *models.User, reviewer *models.
 | 
				
			|||||||
		notification.NotifyPullReviewRequest(doer, issue, member, isAdd, comment)
 | 
							notification.NotifyPullReviewRequest(doer, issue, member, isAdd, comment)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7164,6 +7164,114 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "/repos/{owner}/{repo}/pulls/{index}/requested_reviewers": {
 | 
				
			||||||
 | 
					      "post": {
 | 
				
			||||||
 | 
					        "produces": [
 | 
				
			||||||
 | 
					          "application/json"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "tags": [
 | 
				
			||||||
 | 
					          "repository"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "summary": "create review requests for a pull request",
 | 
				
			||||||
 | 
					        "operationId": "repoCreatePullReviewRequests",
 | 
				
			||||||
 | 
					        "parameters": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "owner of the repo",
 | 
				
			||||||
 | 
					            "name": "owner",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "name of the repo",
 | 
				
			||||||
 | 
					            "name": "repo",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "integer",
 | 
				
			||||||
 | 
					            "format": "int64",
 | 
				
			||||||
 | 
					            "description": "index of the pull request",
 | 
				
			||||||
 | 
					            "name": "index",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "name": "body",
 | 
				
			||||||
 | 
					            "in": "body",
 | 
				
			||||||
 | 
					            "required": true,
 | 
				
			||||||
 | 
					            "schema": {
 | 
				
			||||||
 | 
					              "$ref": "#/definitions/PullReviewRequestOptions"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "responses": {
 | 
				
			||||||
 | 
					          "201": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/PullReviewList"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "404": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/notFound"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "422": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/validationError"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "delete": {
 | 
				
			||||||
 | 
					        "produces": [
 | 
				
			||||||
 | 
					          "application/json"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "tags": [
 | 
				
			||||||
 | 
					          "repository"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "summary": "cancel review requests for a pull request",
 | 
				
			||||||
 | 
					        "operationId": "repoDeletePullReviewRequests",
 | 
				
			||||||
 | 
					        "parameters": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "owner of the repo",
 | 
				
			||||||
 | 
					            "name": "owner",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "name of the repo",
 | 
				
			||||||
 | 
					            "name": "repo",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "integer",
 | 
				
			||||||
 | 
					            "format": "int64",
 | 
				
			||||||
 | 
					            "description": "index of the pull request",
 | 
				
			||||||
 | 
					            "name": "index",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "name": "body",
 | 
				
			||||||
 | 
					            "in": "body",
 | 
				
			||||||
 | 
					            "required": true,
 | 
				
			||||||
 | 
					            "schema": {
 | 
				
			||||||
 | 
					              "$ref": "#/definitions/PullReviewRequestOptions"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "responses": {
 | 
				
			||||||
 | 
					          "204": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/empty"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "404": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/notFound"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "422": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/validationError"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "/repos/{owner}/{repo}/pulls/{index}/reviews": {
 | 
					    "/repos/{owner}/{repo}/pulls/{index}/reviews": {
 | 
				
			||||||
      "get": {
 | 
					      "get": {
 | 
				
			||||||
        "produces": [
 | 
					        "produces": [
 | 
				
			||||||
@@ -14540,6 +14648,9 @@
 | 
				
			|||||||
          "format": "date-time",
 | 
					          "format": "date-time",
 | 
				
			||||||
          "x-go-name": "Submitted"
 | 
					          "x-go-name": "Submitted"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "team": {
 | 
				
			||||||
 | 
					          "$ref": "#/definitions/Team"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "user": {
 | 
					        "user": {
 | 
				
			||||||
          "$ref": "#/definitions/User"
 | 
					          "$ref": "#/definitions/User"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -14614,6 +14725,27 @@
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
					      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "PullReviewRequestOptions": {
 | 
				
			||||||
 | 
					      "description": "PullReviewRequestOptions are options to add or remove pull review requests",
 | 
				
			||||||
 | 
					      "type": "object",
 | 
				
			||||||
 | 
					      "properties": {
 | 
				
			||||||
 | 
					        "reviewers": {
 | 
				
			||||||
 | 
					          "type": "array",
 | 
				
			||||||
 | 
					          "items": {
 | 
				
			||||||
 | 
					            "type": "string"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "x-go-name": "Reviewers"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "team_reviewers": {
 | 
				
			||||||
 | 
					          "type": "array",
 | 
				
			||||||
 | 
					          "items": {
 | 
				
			||||||
 | 
					            "type": "string"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "x-go-name": "TeamReviewers"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Reaction": {
 | 
					    "Reaction": {
 | 
				
			||||||
      "description": "Reaction contain one reaction",
 | 
					      "description": "Reaction contain one reaction",
 | 
				
			||||||
      "type": "object",
 | 
					      "type": "object",
 | 
				
			||||||
@@ -16162,7 +16294,7 @@
 | 
				
			|||||||
    "parameterBodies": {
 | 
					    "parameterBodies": {
 | 
				
			||||||
      "description": "parameterBodies",
 | 
					      "description": "parameterBodies",
 | 
				
			||||||
      "schema": {
 | 
					      "schema": {
 | 
				
			||||||
        "$ref": "#/definitions/MigrateRepoOptions"
 | 
					        "$ref": "#/definitions/PullReviewRequestOptions"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "redirect": {
 | 
					    "redirect": {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user