2016-03-13 23:20:22 -04:00
// Copyright 2016 The Gogs Authors. All rights reserved.
2018-06-19 17:15:11 +02:00
// Copyright 2018 The Gitea Authors. All rights reserved.
2016-03-13 23:20:22 -04:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package repo
import (
"fmt"
2018-07-17 23:23:58 +02:00
"net/http"
2020-04-30 06:15:39 +02:00
"strconv"
2016-03-13 23:20:22 -04:00
"strings"
2019-01-01 14:56:47 -03:00
"time"
2016-03-13 23:20:22 -04:00
2016-11-10 17:24:48 +01:00
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
2020-02-29 03:49:50 +01:00
"code.gitea.io/gitea/modules/convert"
2019-02-21 08:54:05 +08:00
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
2019-10-31 00:06:10 -05:00
"code.gitea.io/gitea/modules/log"
2020-05-16 22:05:19 +01:00
"code.gitea.io/gitea/modules/notification"
2016-11-10 17:24:48 +01:00
"code.gitea.io/gitea/modules/setting"
2019-08-23 09:40:30 -07:00
api "code.gitea.io/gitea/modules/structs"
2019-08-15 22:46:21 +08:00
"code.gitea.io/gitea/modules/timeutil"
2017-01-24 21:43:02 -05:00
"code.gitea.io/gitea/modules/util"
2020-01-24 19:00:29 +00:00
"code.gitea.io/gitea/routers/api/v1/utils"
2019-09-30 21:50:44 +08:00
issue_service "code.gitea.io/gitea/services/issue"
2016-03-13 23:20:22 -04:00
)
2019-10-31 00:06:10 -05:00
// SearchIssues searches for issues across the repositories that the user has access to
func SearchIssues ( ctx * context . APIContext ) {
// swagger:operation GET /repos/issues/search issue issueSearchIssues
// ---
// summary: Search for issues across the repositories that the user has access to
// produces:
// - application/json
// parameters:
// - name: state
// in: query
// description: whether issue is open or closed
// type: string
// - name: labels
// in: query
// description: comma separated list of labels. Fetch only issues that have any of this labels. Non existent labels are discarded
// type: string
// - name: q
// in: query
// description: search string
// type: string
// - name: priority_repo_id
// in: query
// description: repository to prioritize in the results
// type: integer
// format: int64
2020-01-19 14:43:38 +08:00
// - name: type
// in: query
// description: filter by type (issues / pulls) if set
// type: string
2020-01-24 19:00:29 +00:00
// - name: page
// in: query
// description: page number of requested issues
// type: integer
2019-10-31 00:06:10 -05:00
// responses:
// "200":
// "$ref": "#/responses/IssueList"
2019-12-20 18:07:12 +01:00
2019-10-31 00:06:10 -05:00
var isClosed util . OptionalBool
switch ctx . Query ( "state" ) {
case "closed" :
isClosed = util . OptionalBoolTrue
case "all" :
isClosed = util . OptionalBoolNone
default :
isClosed = util . OptionalBoolFalse
}
// find repos user can access (for issue search)
repoIDs := make ( [ ] int64 , 0 )
2020-01-12 07:35:11 +01:00
opts := & models . SearchRepoOptions {
2020-01-24 19:00:29 +00:00
ListOptions : models . ListOptions {
PageSize : 15 ,
} ,
2020-01-12 07:35:11 +01:00
Private : false ,
AllPublic : true ,
TopicOnly : false ,
Collaborate : util . OptionalBoolNone ,
2020-04-01 00:14:46 -04:00
// This needs to be a column that is not nil in fixtures or
// MySQL will return different results when sorting by null in some cases
OrderBy : models . SearchOrderByAlphabetically ,
Actor : ctx . User ,
2020-01-12 07:35:11 +01:00
}
if ctx . IsSigned {
opts . Private = true
opts . AllLimited = true
}
2020-03-30 13:30:39 +08:00
2019-10-31 00:06:10 -05:00
issueCount := 0
for page := 1 ; ; page ++ {
2020-01-12 07:35:11 +01:00
opts . Page = page
repos , count , err := models . SearchRepositoryByName ( opts )
2019-10-31 00:06:10 -05:00
if err != nil {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "SearchRepositoryByName" , err )
2019-10-31 00:06:10 -05:00
return
}
if len ( repos ) == 0 {
break
}
log . Trace ( "Processing next %d repos of %d" , len ( repos ) , count )
for _ , repo := range repos {
switch isClosed {
case util . OptionalBoolTrue :
issueCount += repo . NumClosedIssues
case util . OptionalBoolFalse :
issueCount += repo . NumOpenIssues
case util . OptionalBoolNone :
issueCount += repo . NumIssues
}
repoIDs = append ( repoIDs , repo . ID )
}
}
var issues [ ] * models . Issue
keyword := strings . Trim ( ctx . Query ( "q" ) , " " )
if strings . IndexByte ( keyword , 0 ) >= 0 {
keyword = ""
}
var issueIDs [ ] int64
var labelIDs [ ] int64
var err error
if len ( keyword ) > 0 && len ( repoIDs ) > 0 {
issueIDs , err = issue_indexer . SearchIssuesByKeyword ( repoIDs , keyword )
}
2020-01-19 14:43:38 +08:00
var isPull util . OptionalBool
switch ctx . Query ( "type" ) {
case "pulls" :
isPull = util . OptionalBoolTrue
case "issues" :
isPull = util . OptionalBoolFalse
default :
isPull = util . OptionalBoolNone
}
2020-03-30 13:30:39 +08:00
labels := strings . TrimSpace ( ctx . Query ( "labels" ) )
var includedLabelNames [ ] string
if len ( labels ) > 0 {
includedLabelNames = strings . Split ( labels , "," )
}
2019-10-31 00:06:10 -05:00
// Only fetch the issues if we either don't have a keyword or the search returned issues
// This would otherwise return all issues if no issues were found by the search.
if len ( keyword ) == 0 || len ( issueIDs ) > 0 || len ( labelIDs ) > 0 {
issues , err = models . Issues ( & models . IssuesOptions {
2020-01-24 19:00:29 +00:00
ListOptions : models . ListOptions {
Page : ctx . QueryInt ( "page" ) ,
PageSize : setting . UI . IssuePagingNum ,
} ,
2020-04-01 00:14:46 -04:00
2020-03-30 13:30:39 +08:00
RepoIDs : repoIDs ,
IsClosed : isClosed ,
IssueIDs : issueIDs ,
IncludedLabelNames : includedLabelNames ,
SortType : "priorityrepo" ,
PriorityRepoID : ctx . QueryInt64 ( "priority_repo_id" ) ,
IsPull : isPull ,
2019-10-31 00:06:10 -05:00
} )
}
if err != nil {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "Issues" , err )
2019-10-31 00:06:10 -05:00
return
}
ctx . SetLinkHeader ( issueCount , setting . UI . IssuePagingNum )
2020-02-29 03:49:50 +01:00
ctx . JSON ( http . StatusOK , convert . ToAPIIssueList ( issues ) )
2019-10-31 00:06:10 -05:00
}
2016-11-24 15:04:31 +08:00
// ListIssues list the issues of a repository
2016-03-13 23:20:22 -04:00
func ListIssues ( ctx * context . APIContext ) {
2017-11-12 23:02:25 -08:00
// swagger:operation GET /repos/{owner}/{repo}/issues issue issueListIssues
// ---
// summary: List a repository's issues
// 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: state
// in: query
// description: whether issue is open or closed
// type: string
2019-02-04 16:20:44 +01:00
// - name: labels
// in: query
// description: comma separated list of labels. Fetch only issues that have any of this labels. Non existent labels are discarded
// type: string
2018-03-07 11:00:56 +01:00
// - name: q
// in: query
// description: search string
// type: string
2020-01-20 20:00:32 +08:00
// - name: type
// in: query
// description: filter by type (issues / pulls) if set
// type: string
2020-04-30 06:15:39 +02:00
// - name: milestones
// in: query
// description: comma separated list of milestone names or ids. It uses names and fall back to ids. Fetch only issues that have any of this milestones. Non existent milestones are discarded
// type: string
2020-01-24 19:00:29 +00:00
// - name: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// - name: limit
// in: query
// description: page size of results, maximum page size is 50
// type: integer
2017-11-12 23:02:25 -08:00
// responses:
// "200":
// "$ref": "#/responses/IssueList"
2019-12-20 18:07:12 +01:00
2017-06-25 10:51:07 -04:00
var isClosed util . OptionalBool
switch ctx . Query ( "state" ) {
case "closed" :
isClosed = util . OptionalBoolTrue
case "all" :
isClosed = util . OptionalBoolNone
default :
isClosed = util . OptionalBoolFalse
2016-10-07 19:17:27 +02:00
}
2018-03-07 11:00:56 +01:00
var issues [ ] * models . Issue
keyword := strings . Trim ( ctx . Query ( "q" ) , " " )
if strings . IndexByte ( keyword , 0 ) >= 0 {
keyword = ""
}
var issueIDs [ ] int64
2019-02-04 16:20:44 +01:00
var labelIDs [ ] int64
2018-03-07 11:00:56 +01:00
var err error
if len ( keyword ) > 0 {
2019-10-31 00:06:10 -05:00
issueIDs , err = issue_indexer . SearchIssuesByKeyword ( [ ] int64 { ctx . Repo . Repository . ID } , keyword )
2018-03-07 11:00:56 +01:00
}
2019-02-04 16:20:44 +01:00
if splitted := strings . Split ( ctx . Query ( "labels" ) , "," ) ; len ( splitted ) > 0 {
labelIDs , err = models . GetLabelIDsInRepoByNames ( ctx . Repo . Repository . ID , splitted )
if err != nil {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "GetLabelIDsInRepoByNames" , err )
2019-02-04 16:20:44 +01:00
return
}
}
2020-04-30 06:15:39 +02:00
var mileIDs [ ] int64
if part := strings . Split ( ctx . Query ( "milestones" ) , "," ) ; len ( part ) > 0 {
for i := range part {
// uses names and fall back to ids
// non existent milestones are discarded
mile , err := models . GetMilestoneByRepoIDANDName ( ctx . Repo . Repository . ID , part [ i ] )
if err == nil {
mileIDs = append ( mileIDs , mile . ID )
continue
}
if ! models . IsErrMilestoneNotExist ( err ) {
ctx . Error ( http . StatusInternalServerError , "GetMilestoneByRepoIDANDName" , err )
return
}
id , err := strconv . ParseInt ( part [ i ] , 10 , 64 )
if err != nil {
continue
}
mile , err = models . GetMilestoneByRepoID ( ctx . Repo . Repository . ID , id )
if err == nil {
mileIDs = append ( mileIDs , mile . ID )
continue
}
if models . IsErrMilestoneNotExist ( err ) {
continue
}
ctx . Error ( http . StatusInternalServerError , "GetMilestoneByRepoID" , err )
}
}
2020-01-24 19:00:29 +00:00
listOptions := utils . GetListOptions ( ctx )
if ctx . QueryInt ( "limit" ) == 0 {
listOptions . PageSize = setting . UI . IssuePagingNum
}
2020-01-20 20:00:32 +08:00
var isPull util . OptionalBool
switch ctx . Query ( "type" ) {
case "pulls" :
isPull = util . OptionalBoolTrue
case "issues" :
isPull = util . OptionalBoolFalse
default :
isPull = util . OptionalBoolNone
}
2018-03-07 11:00:56 +01:00
// Only fetch the issues if we either don't have a keyword or the search returned issues
// This would otherwise return all issues if no issues were found by the search.
2019-02-04 16:20:44 +01:00
if len ( keyword ) == 0 || len ( issueIDs ) > 0 || len ( labelIDs ) > 0 {
2018-03-07 11:00:56 +01:00
issues , err = models . Issues ( & models . IssuesOptions {
2020-04-30 06:15:39 +02:00
ListOptions : listOptions ,
RepoIDs : [ ] int64 { ctx . Repo . Repository . ID } ,
IsClosed : isClosed ,
IssueIDs : issueIDs ,
LabelIDs : labelIDs ,
MilestoneIDs : mileIDs ,
IsPull : isPull ,
2018-03-07 11:00:56 +01:00
} )
}
2016-03-13 23:20:22 -04:00
if err != nil {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "Issues" , err )
2016-03-13 23:20:22 -04:00
return
}
2020-01-24 19:00:29 +00:00
ctx . SetLinkHeader ( ctx . Repo . Repository . NumIssues , listOptions . PageSize )
2020-02-29 03:49:50 +01:00
ctx . JSON ( http . StatusOK , convert . ToAPIIssueList ( issues ) )
2016-03-13 23:20:22 -04:00
}
2016-11-24 15:04:31 +08:00
// GetIssue get an issue of a repository
2016-03-13 23:20:22 -04:00
func GetIssue ( ctx * context . APIContext ) {
2018-01-04 01:31:40 -05:00
// swagger:operation GET /repos/{owner}/{repo}/issues/{index} issue issueGetIssue
2017-11-12 23:02:25 -08:00
// ---
2018-01-04 01:31:40 -05:00
// summary: Get an issue
2017-11-12 23:02:25 -08:00
// 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
2018-01-04 01:31:40 -05:00
// - name: index
2017-11-12 23:02:25 -08:00
// in: path
2018-01-04 01:31:40 -05:00
// description: index of the issue to get
2017-11-12 23:02:25 -08:00
// type: integer
2018-10-21 04:40:42 +01:00
// format: int64
2017-11-12 23:02:25 -08:00
// required: true
// responses:
// "200":
// "$ref": "#/responses/Issue"
2019-12-20 18:07:12 +01:00
// "404":
// "$ref": "#/responses/notFound"
2019-02-19 11:07:19 -06:00
issue , err := models . GetIssueWithAttrsByIndex ( ctx . Repo . Repository . ID , ctx . ParamsInt64 ( ":index" ) )
2016-03-13 23:20:22 -04:00
if err != nil {
if models . IsErrIssueNotExist ( err ) {
2019-03-18 21:29:43 -05:00
ctx . NotFound ( )
2016-03-13 23:20:22 -04:00
} else {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "GetIssueByIndex" , err )
2016-03-13 23:20:22 -04:00
}
return
}
2020-02-29 03:49:50 +01:00
ctx . JSON ( http . StatusOK , convert . ToAPIIssue ( issue ) )
2016-03-13 23:20:22 -04:00
}
2016-11-24 15:04:31 +08:00
// CreateIssue create an issue of a repository
2016-03-13 23:20:22 -04:00
func CreateIssue ( ctx * context . APIContext , form api . CreateIssueOption ) {
2017-11-12 23:02:25 -08:00
// swagger:operation POST /repos/{owner}/{repo}/issues issue issueCreateIssue
// ---
2019-01-01 14:56:47 -03:00
// summary: Create an issue. If using deadline only the date will be taken into account, and time of day ignored.
2017-11-12 23:02:25 -08:00
// consumes:
// - application/json
// 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: body
// in: body
// schema:
// "$ref": "#/definitions/CreateIssueOption"
// responses:
// "201":
// "$ref": "#/responses/Issue"
2019-12-20 18:07:12 +01:00
// "403":
// "$ref": "#/responses/forbidden"
// "412":
// "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"
2018-05-01 21:05:28 +02:00
2019-08-15 22:46:21 +08:00
var deadlineUnix timeutil . TimeStamp
2018-11-28 19:26:14 +08:00
if form . Deadline != nil && ctx . Repo . CanWrite ( models . UnitTypeIssues ) {
2019-08-15 22:46:21 +08:00
deadlineUnix = timeutil . TimeStamp ( form . Deadline . Unix ( ) )
2018-05-01 21:05:28 +02:00
}
2016-03-13 23:20:22 -04:00
issue := & models . Issue {
2018-05-01 21:05:28 +02:00
RepoID : ctx . Repo . Repository . ID ,
2018-12-13 23:55:43 +08:00
Repo : ctx . Repo . Repository ,
2018-05-01 21:05:28 +02:00
Title : form . Title ,
PosterID : ctx . User . ID ,
Poster : ctx . User ,
Content : form . Body ,
DeadlineUnix : deadlineUnix ,
2016-03-13 23:20:22 -04:00
}
2018-06-19 17:15:11 +02:00
var assigneeIDs = make ( [ ] int64 , 0 )
var err error
2018-11-28 19:26:14 +08:00
if ctx . Repo . CanWrite ( models . UnitTypeIssues ) {
2018-06-19 17:15:11 +02:00
issue . MilestoneID = form . Milestone
assigneeIDs , err = models . MakeIDsFromAPIAssigneesToAdd ( form . Assignee , form . Assignees )
if err != nil {
if models . IsErrUserNotExist ( err ) {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusUnprocessableEntity , "" , fmt . Sprintf ( "Assignee does not exist: [name: %s]" , err ) )
2018-06-19 17:15:11 +02:00
} else {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "AddAssigneeByName" , err )
2018-06-19 17:15:11 +02:00
}
return
2016-03-13 23:20:22 -04:00
}
2019-10-25 16:46:37 +02:00
// Check if the passed assignees is assignable
for _ , aID := range assigneeIDs {
assignee , err := models . GetUserByID ( aID )
if err != nil {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "GetUserByID" , err )
2019-10-25 16:46:37 +02:00
return
}
valid , err := models . CanBeAssigned ( assignee , ctx . Repo . Repository , false )
if err != nil {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "canBeAssigned" , err )
2019-10-25 16:46:37 +02:00
return
}
if ! valid {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusUnprocessableEntity , "canBeAssigned" , models . ErrUserDoesNotHaveAccessToRepo { UserID : aID , RepoName : ctx . Repo . Repository . Name } )
2019-10-25 16:46:37 +02:00
return
}
}
2018-06-19 17:15:11 +02:00
} else {
// setting labels is not allowed if user is not a writer
form . Labels = make ( [ ] int64 , 0 )
2016-03-13 23:20:22 -04:00
}
2019-10-29 00:45:43 +08:00
if err := issue_service . NewIssue ( ctx . Repo . Repository , issue , form . Labels , nil , assigneeIDs ) ; err != nil {
2018-05-09 18:29:04 +02:00
if models . IsErrUserDoesNotHaveAccessToRepo ( err ) {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusBadRequest , "UserDoesNotHaveAccessToRepo" , err )
2018-05-09 18:29:04 +02:00
return
}
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "NewIssue" , err )
2016-03-13 23:20:22 -04:00
return
}
2016-05-27 18:23:39 -07:00
if form . Closed {
2019-10-28 13:26:46 +08:00
if err := issue_service . ChangeStatus ( issue , ctx . User , true ) ; err != nil {
2018-07-17 23:23:58 +02:00
if models . IsErrDependenciesLeft ( err ) {
ctx . Error ( http . StatusPreconditionFailed , "DependenciesLeft" , "cannot close this issue because it still has open dependencies" )
return
}
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "ChangeStatus" , err )
2016-05-27 18:23:39 -07:00
return
}
}
2016-03-13 23:20:22 -04:00
// Refetch from database to assign some automatic values
issue , err = models . GetIssueByID ( issue . ID )
if err != nil {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "GetIssueByID" , err )
2016-03-13 23:20:22 -04:00
return
}
2020-02-29 03:49:50 +01:00
ctx . JSON ( http . StatusCreated , convert . ToAPIIssue ( issue ) )
2016-03-13 23:20:22 -04:00
}
2016-11-24 15:04:31 +08:00
// EditIssue modify an issue of a repository
2016-03-13 23:20:22 -04:00
func EditIssue ( ctx * context . APIContext , form api . EditIssueOption ) {
2018-01-04 01:31:40 -05:00
// swagger:operation PATCH /repos/{owner}/{repo}/issues/{index} issue issueEditIssue
2017-11-12 23:02:25 -08:00
// ---
2019-01-01 14:56:47 -03:00
// summary: Edit an issue. If using deadline only the date will be taken into account, and time of day ignored.
2017-11-12 23:02:25 -08:00
// consumes:
// - application/json
// 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
2018-01-04 01:31:40 -05:00
// - name: index
2017-11-12 23:02:25 -08:00
// in: path
2018-01-04 01:31:40 -05:00
// description: index of the issue to edit
2017-11-12 23:02:25 -08:00
// type: integer
2018-10-21 04:40:42 +01:00
// format: int64
2017-11-12 23:02:25 -08:00
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/EditIssueOption"
// responses:
// "201":
// "$ref": "#/responses/Issue"
2019-12-20 18:07:12 +01:00
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
// "412":
// "$ref": "#/responses/error"
2016-03-13 23:20:22 -04:00
issue , err := models . GetIssueByIndex ( ctx . Repo . Repository . ID , ctx . ParamsInt64 ( ":index" ) )
if err != nil {
if models . IsErrIssueNotExist ( err ) {
2019-03-18 21:29:43 -05:00
ctx . NotFound ( )
2016-03-13 23:20:22 -04:00
} else {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "GetIssueByIndex" , err )
2016-03-13 23:20:22 -04:00
}
return
}
2018-12-13 23:55:43 +08:00
issue . Repo = ctx . Repo . Repository
2020-01-20 20:00:32 +08:00
canWrite := ctx . Repo . CanWriteIssuesOrPulls ( issue . IsPull )
2016-03-13 23:20:22 -04:00
2019-04-23 12:07:12 -05:00
err = issue . LoadAttributes ( )
if err != nil {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "LoadAttributes" , err )
2019-04-23 12:07:12 -05:00
return
}
2020-01-20 20:00:32 +08:00
if ! issue . IsPoster ( ctx . User . ID ) && ! canWrite {
2019-12-20 18:07:12 +01:00
ctx . Status ( http . StatusForbidden )
2016-03-13 23:20:22 -04:00
return
}
2020-05-16 22:05:19 +01:00
oldTitle := issue . Title
2016-03-13 23:20:22 -04:00
if len ( form . Title ) > 0 {
2016-08-14 03:32:24 -07:00
issue . Title = form . Title
2016-03-13 23:20:22 -04:00
}
if form . Body != nil {
issue . Content = * form . Body
}
2019-11-03 15:46:32 +01:00
// Update or remove the deadline, only if set and allowed
2020-01-20 20:00:32 +08:00
if ( form . Deadline != nil || form . RemoveDeadline != nil ) && canWrite {
2019-11-03 15:46:32 +01:00
var deadlineUnix timeutil . TimeStamp
if ( form . RemoveDeadline == nil || ! * form . RemoveDeadline ) && ! form . Deadline . IsZero ( ) {
deadline := time . Date ( form . Deadline . Year ( ) , form . Deadline . Month ( ) , form . Deadline . Day ( ) ,
23 , 59 , 59 , 0 , form . Deadline . Location ( ) )
deadlineUnix = timeutil . TimeStamp ( deadline . Unix ( ) )
}
2019-10-28 00:35:20 +01:00
if err := models . UpdateIssueDeadline ( issue , deadlineUnix , ctx . User ) ; err != nil {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "UpdateIssueDeadline" , err )
2019-10-28 00:35:20 +01:00
return
}
issue . DeadlineUnix = deadlineUnix
2018-05-01 21:05:28 +02:00
}
2018-05-09 18:29:04 +02:00
// Add/delete assignees
2019-03-09 16:15:45 -05:00
// Deleting is done the GitHub way (quote from their api documentation):
2018-05-09 18:29:04 +02:00
// https://developer.github.com/v3/issues/#edit-an-issue
// "assignees" (array): Logins for Users to assign to this issue.
// Pass one or more user logins to replace the set of assignees on this Issue.
// Send an empty array ([]) to clear all assignees from the Issue.
2020-01-20 20:00:32 +08:00
if canWrite && ( form . Assignees != nil || form . Assignee != nil ) {
2018-05-09 18:29:04 +02:00
oneAssignee := ""
if form . Assignee != nil {
oneAssignee = * form . Assignee
2016-03-13 23:20:22 -04:00
}
2019-10-25 16:46:37 +02:00
err = issue_service . UpdateAssignees ( issue , oneAssignee , form . Assignees , ctx . User )
2018-05-09 18:29:04 +02:00
if err != nil {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "UpdateAssignees" , err )
2016-03-13 23:20:22 -04:00
return
}
}
2018-05-09 18:29:04 +02:00
2020-01-20 20:00:32 +08:00
if canWrite && form . Milestone != nil &&
2016-03-13 23:20:22 -04:00
issue . MilestoneID != * form . Milestone {
2016-08-15 18:40:32 -07:00
oldMilestoneID := issue . MilestoneID
2016-03-13 23:20:22 -04:00
issue . MilestoneID = * form . Milestone
2019-11-02 11:33:20 +08:00
if err = issue_service . ChangeMilestoneAssign ( issue , ctx . User , oldMilestoneID ) ; err != nil {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "ChangeMilestoneAssign" , err )
2016-03-13 23:20:22 -04:00
return
}
}
2016-08-23 18:09:32 +02:00
if form . State != nil {
2020-05-16 22:05:19 +01:00
issue . IsClosed = ( api . StateClosed == api . StateType ( * form . State ) )
}
statusChangeComment , titleChanged , err := models . UpdateIssueByAPI ( issue , ctx . User )
if err != nil {
if models . IsErrDependenciesLeft ( err ) {
ctx . Error ( http . StatusPreconditionFailed , "DependenciesLeft" , "cannot close this issue because it still has open dependencies" )
2016-08-23 18:09:32 +02:00
return
}
2020-05-16 22:05:19 +01:00
ctx . Error ( http . StatusInternalServerError , "UpdateIssueByAPI" , err )
return
}
if titleChanged {
notification . NotifyIssueChangeTitle ( ctx . User , issue , oldTitle )
}
if statusChangeComment != nil {
notification . NotifyIssueChangeStatus ( ctx . User , issue , statusChangeComment , issue . IsClosed )
2016-08-23 18:09:32 +02:00
}
2016-03-13 23:20:22 -04:00
// Refetch from database to assign some automatic values
issue , err = models . GetIssueByID ( issue . ID )
if err != nil {
2020-01-01 23:51:10 +01:00
ctx . InternalServerError ( err )
return
}
if err = issue . LoadMilestone ( ) ; err != nil {
ctx . InternalServerError ( err )
2016-03-13 23:20:22 -04:00
return
}
2020-02-29 03:49:50 +01:00
ctx . JSON ( http . StatusCreated , convert . ToAPIIssue ( issue ) )
2016-03-13 23:20:22 -04:00
}
2018-07-16 14:43:00 +02:00
// UpdateIssueDeadline updates an issue deadline
func UpdateIssueDeadline ( ctx * context . APIContext , form api . EditDeadlineOption ) {
// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/deadline issue issueEditIssueDeadline
// ---
2019-01-01 14:56:47 -03:00
// summary: Set an issue deadline. If set to null, the deadline is deleted. If using deadline only the date will be taken into account, and time of day ignored.
2018-07-16 14:43:00 +02:00
// consumes:
// - application/json
// 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 issue to create or update a deadline on
// type: integer
2018-10-21 04:40:42 +01:00
// format: int64
2018-07-16 14:43:00 +02:00
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/EditDeadlineOption"
// responses:
// "201":
// "$ref": "#/responses/IssueDeadline"
// "403":
2019-12-20 18:07:12 +01:00
// "$ref": "#/responses/forbidden"
2018-07-16 14:43:00 +02:00
// "404":
2019-12-20 18:07:12 +01:00
// "$ref": "#/responses/notFound"
2018-07-16 14:43:00 +02:00
issue , err := models . GetIssueByIndex ( ctx . Repo . Repository . ID , ctx . ParamsInt64 ( ":index" ) )
if err != nil {
if models . IsErrIssueNotExist ( err ) {
2019-03-18 21:29:43 -05:00
ctx . NotFound ( )
2018-07-16 14:43:00 +02:00
} else {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "GetIssueByIndex" , err )
2018-07-16 14:43:00 +02:00
}
return
}
2020-01-20 20:00:32 +08:00
if ! ctx . Repo . CanWriteIssuesOrPulls ( issue . IsPull ) {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusForbidden , "" , "Not repo writer" )
2018-07-16 14:43:00 +02:00
return
}
2019-08-15 22:46:21 +08:00
var deadlineUnix timeutil . TimeStamp
2019-01-01 14:56:47 -03:00
var deadline time . Time
2018-07-16 14:43:00 +02:00
if form . Deadline != nil && ! form . Deadline . IsZero ( ) {
2019-01-01 14:56:47 -03:00
deadline = time . Date ( form . Deadline . Year ( ) , form . Deadline . Month ( ) , form . Deadline . Day ( ) ,
23 , 59 , 59 , 0 , form . Deadline . Location ( ) )
2019-08-15 22:46:21 +08:00
deadlineUnix = timeutil . TimeStamp ( deadline . Unix ( ) )
2018-07-16 14:43:00 +02:00
}
if err := models . UpdateIssueDeadline ( issue , deadlineUnix , ctx . User ) ; err != nil {
2019-12-20 18:07:12 +01:00
ctx . Error ( http . StatusInternalServerError , "UpdateIssueDeadline" , err )
2018-07-16 14:43:00 +02:00
return
}
2019-12-20 18:07:12 +01:00
ctx . JSON ( http . StatusCreated , api . IssueDeadline { Deadline : & deadline } )
2018-07-16 14:43:00 +02:00
}