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.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2016-03-13 23:20:22 -04:00
package repo
import (
2024-03-04 09:16:03 +01:00
"errors"
2016-03-13 23:20:22 -04:00
"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
2021-09-24 19:32:56 +08:00
"code.gitea.io/gitea/models/db"
2022-04-08 17:11:15 +08:00
issues_model "code.gitea.io/gitea/models/issues"
2022-03-29 14:29:02 +08:00
"code.gitea.io/gitea/models/organization"
2022-05-11 18:09:36 +08:00
access_model "code.gitea.io/gitea/models/perm/access"
2022-06-06 16:01:49 +08:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-10 03:57:58 +08:00
"code.gitea.io/gitea/models/unit"
2021-11-24 17:49:20 +08:00
user_model "code.gitea.io/gitea/models/user"
2019-02-21 08:54:05 +08:00
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
2024-02-29 19:52:49 +01:00
"code.gitea.io/gitea/modules/optional"
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"
2025-12-21 08:57:41 +07:00
"code.gitea.io/gitea/modules/util"
2021-01-26 23:36:53 +08:00
"code.gitea.io/gitea/modules/web"
2020-01-24 19:00:29 +00:00
"code.gitea.io/gitea/routers/api/v1/utils"
2024-11-04 23:46:40 -08:00
"code.gitea.io/gitea/routers/common"
2024-02-27 15:12:22 +08:00
"code.gitea.io/gitea/services/context"
2022-12-29 03:57:15 +01:00
"code.gitea.io/gitea/services/convert"
2019-09-30 21:50:44 +08:00
issue_service "code.gitea.io/gitea/services/issue"
2016-03-13 23:20:22 -04:00
)
2025-12-21 08:57:41 +07:00
// buildSearchIssuesRepoIDs builds the list of repository IDs for issue search based on query parameters.
// It returns repoIDs, allPublic flag, and any error that occurred.
func buildSearchIssuesRepoIDs ( ctx * context . APIContext ) ( repoIDs [ ] int64 , allPublic bool , err error ) {
opts := repo_model . SearchRepoOptions {
Private : false ,
AllPublic : true ,
TopicOnly : false ,
Collaborate : optional . None [ bool ] ( ) ,
// 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 : db . SearchOrderByAlphabetically ,
Actor : ctx . Doer ,
}
if ctx . IsSigned {
2026-05-19 08:38:51 -07:00
opts . Private = true
2025-12-21 08:57:41 +07:00
opts . AllLimited = true
}
2026-05-19 08:38:51 -07:00
opts . ApplyPublicOnly ( ctx . PublicOnly )
2025-12-21 08:57:41 +07:00
if ctx . FormString ( "owner" ) != "" {
owner , err := user_model . GetUserByName ( ctx , ctx . FormString ( "owner" ) )
if err != nil {
return nil , false , err
}
opts . OwnerID = owner . ID
opts . AllLimited = false
opts . AllPublic = false
opts . Collaborate = optional . Some ( false )
}
if ctx . FormString ( "team" ) != "" {
if ctx . FormString ( "owner" ) == "" {
return nil , false , util . NewInvalidArgumentErrorf ( "owner organisation is required for filtering on team" )
}
team , err := organization . GetTeam ( ctx , opts . OwnerID , ctx . FormString ( "team" ) )
if err != nil {
return nil , false , err
}
opts . TeamID = team . ID
}
if opts . AllPublic {
allPublic = true
opts . AllPublic = false // set it false to avoid returning too many repos, we could filter by indexer
}
repoIDs , _ , err = repo_model . SearchRepositoryIDs ( ctx , opts )
if err != nil {
return nil , false , err
}
if len ( repoIDs ) == 0 {
// no repos found, don't let the indexer return all repos
repoIDs = [ ] int64 { 0 }
}
return repoIDs , allPublic , nil
}
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
2024-10-19 22:11:56 +02:00
// description: State of the issue
2019-10-31 00:06:10 -05:00
// type: string
2024-10-19 22:11:56 +02:00
// enum: [open, closed, all]
// default: open
2019-10-31 00:06:10 -05:00
// - name: labels
// in: query
2024-10-19 22:11:56 +02:00
// description: Comma-separated list of label names. Fetch only issues that have any of these labels. Non existent labels are discarded.
2019-10-31 00:06:10 -05:00
// type: string
2021-06-17 08:40:59 +02:00
// - name: milestones
// in: query
2024-10-19 22:11:56 +02:00
// description: Comma-separated list of milestone names. Fetch only issues that have any of these milestones. Non existent milestones are discarded.
2021-06-17 08:40:59 +02:00
// type: string
2019-10-31 00:06:10 -05:00
// - name: q
// in: query
2024-10-19 22:11:56 +02:00
// description: Search string
2019-10-31 00:06:10 -05:00
// type: string
2020-01-19 14:43:38 +08:00
// - name: type
// in: query
2024-10-19 22:11:56 +02:00
// description: Filter by issue type
2020-01-19 14:43:38 +08:00
// type: string
2024-10-19 22:11:56 +02:00
// enum: [issues, pulls]
2020-11-23 21:49:36 +01:00
// - name: since
// in: query
2024-10-19 22:11:56 +02:00
// description: Only show issues updated after the given time (RFC 3339 format)
2020-11-23 21:49:36 +01:00
// type: string
// format: date-time
// - name: before
// in: query
2024-10-19 22:11:56 +02:00
// description: Only show issues updated before the given time (RFC 3339 format)
2020-11-23 21:49:36 +01:00
// type: string
// format: date-time
// - name: assigned
// in: query
2024-10-19 22:11:56 +02:00
// description: Filter issues or pulls assigned to the authenticated user
2020-11-23 21:49:36 +01:00
// type: boolean
2024-10-19 22:11:56 +02:00
// default: false
2020-11-23 21:49:36 +01:00
// - name: created
// in: query
2024-10-19 22:11:56 +02:00
// description: Filter issues or pulls created by the authenticated user
2020-11-23 21:49:36 +01:00
// type: boolean
2024-10-19 22:11:56 +02:00
// default: false
2020-11-23 21:49:36 +01:00
// - name: mentioned
// in: query
2024-10-19 22:11:56 +02:00
// description: Filter issues or pulls mentioning the authenticated user
2020-11-23 21:49:36 +01:00
// type: boolean
2024-10-19 22:11:56 +02:00
// default: false
2021-01-17 17:34:19 +01:00
// - name: review_requested
// in: query
2024-10-19 22:11:56 +02:00
// description: Filter pull requests where the authenticated user's review was requested
2021-01-17 17:34:19 +01:00
// type: boolean
2024-10-19 22:11:56 +02:00
// default: false
2023-02-25 03:55:50 +01:00
// - name: reviewed
// in: query
2024-10-19 22:11:56 +02:00
// description: Filter pull requests reviewed by the authenticated user
2023-02-25 03:55:50 +01:00
// type: boolean
2024-10-19 22:11:56 +02:00
// default: false
2021-08-13 22:47:25 +02:00
// - name: owner
// in: query
2024-10-19 22:11:56 +02:00
// description: Filter by repository owner
2021-08-13 22:47:25 +02:00
// type: string
2026-02-26 12:56:02 +01:00
// - name: created_by
// in: query
// description: Only show items which were created by the given user
// type: string
2021-08-13 22:47:25 +02:00
// - name: team
// in: query
2024-10-19 22:11:56 +02:00
// description: Filter by team (requires organization owner parameter)
2021-08-13 22:47:25 +02:00
// type: string
2020-01-24 19:00:29 +00:00
// - name: page
// in: query
2024-10-19 22:11:56 +02:00
// description: Page number of results to return (1-based)
2020-11-23 21:49:36 +01:00
// type: integer
2024-10-19 22:11:56 +02:00
// minimum: 1
// default: 1
2020-11-23 21:49:36 +01:00
// - name: limit
// in: query
2024-10-19 22:11:56 +02:00
// description: Number of items per page
2020-01-24 19:00:29 +00:00
// type: integer
2024-10-19 22:11:56 +02:00
// minimum: 0
2019-10-31 00:06:10 -05:00
// responses:
// "200":
// "$ref": "#/responses/IssueList"
2024-10-19 22:11:56 +02:00
// "400":
// "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"
2019-12-20 18:07:12 +01:00
2023-05-21 09:50:53 +08:00
before , since , err := context . GetQueryBeforeSince ( ctx . Base )
2020-11-23 21:49:36 +01:00
if err != nil {
2025-02-17 14:13:17 +08:00
ctx . APIError ( http . StatusUnprocessableEntity , err )
2020-11-23 21:49:36 +01:00
return
}
2025-12-21 08:57:41 +07:00
isClosed := common . ParseIssueFilterStateIsClosed ( ctx . FormString ( "state" ) )
2023-07-31 14:28:53 +08:00
2025-12-21 08:57:41 +07:00
repoIDs , allPublic , err := buildSearchIssuesRepoIDs ( ctx )
if err != nil {
if errors . Is ( err , util . ErrNotExist ) || errors . Is ( err , util . ErrInvalidArgument ) {
ctx . APIError ( http . StatusBadRequest , err )
} else {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2023-08-18 01:42:17 +08:00
}
2025-12-21 08:57:41 +07:00
return
2021-08-13 22:47:25 +02:00
}
2020-03-30 13:30:39 +08:00
2021-08-11 17:08:52 +02:00
keyword := ctx . FormTrim ( "q" )
2019-10-31 00:06:10 -05:00
if strings . IndexByte ( keyword , 0 ) >= 0 {
keyword = ""
}
2025-12-21 08:57:41 +07:00
isPull := common . ParseIssueFilterTypeIsPull ( ctx . FormString ( "type" ) )
2020-01-19 14:43:38 +08:00
2023-07-31 14:28:53 +08:00
var includedAnyLabels [ ] int64
{
labels := ctx . FormTrim ( "labels" )
var includedLabelNames [ ] string
if len ( labels ) > 0 {
includedLabelNames = strings . Split ( labels , "," )
}
includedAnyLabels , err = issues_model . GetLabelIDsByNames ( ctx , includedLabelNames )
if err != nil {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2023-07-31 14:28:53 +08:00
return
}
2020-03-30 13:30:39 +08:00
}
2023-07-31 14:28:53 +08:00
var includedMilestones [ ] int64
{
milestones := ctx . FormTrim ( "milestones" )
var includedMilestoneNames [ ] string
if len ( milestones ) > 0 {
includedMilestoneNames = strings . Split ( milestones , "," )
}
includedMilestones , err = issues_model . GetMilestoneIDsByNames ( ctx , includedMilestoneNames )
if err != nil {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2023-07-31 14:28:53 +08:00
return
}
2021-06-17 08:40:59 +02:00
}
2025-12-21 08:57:41 +07:00
limit := util . IfZero ( ctx . FormInt ( "limit" ) , setting . API . DefaultPagingNum )
2020-11-23 21:49:36 +01:00
2023-07-31 14:28:53 +08:00
searchOpt := & issue_indexer . SearchOptions {
Paginator : & db . ListOptions {
PageSize : limit ,
Page : ctx . FormInt ( "page" ) ,
} ,
Keyword : keyword ,
RepoIDs : repoIDs ,
AllPublic : allPublic ,
IsPull : isPull ,
IsClosed : isClosed ,
IncludedAnyLabelIDs : includedAnyLabels ,
MilestoneIDs : includedMilestones ,
SortBy : issue_indexer . SortByCreatedDesc ,
}
2020-11-23 21:49:36 +01:00
2023-07-31 14:28:53 +08:00
if since != 0 {
2024-03-13 09:25:53 +01:00
searchOpt . UpdatedAfterUnix = optional . Some ( since )
2023-07-31 14:28:53 +08:00
}
if before != 0 {
2024-03-13 09:25:53 +01:00
searchOpt . UpdatedBeforeUnix = optional . Some ( before )
2023-07-31 14:28:53 +08:00
}
2022-03-20 21:04:51 +00:00
2026-02-26 12:56:02 +01:00
createdByID := getUserIDForFilter ( ctx , "created_by" )
if ctx . Written ( ) {
return
}
if createdByID > 0 {
searchOpt . PosterID = strconv . FormatInt ( createdByID , 10 )
}
2023-07-31 14:28:53 +08:00
if ctx . IsSigned {
ctxUserID := ctx . Doer . ID
2021-07-29 09:42:15 +08:00
if ctx . FormBool ( "created" ) {
2025-03-21 05:25:36 +01:00
searchOpt . PosterID = strconv . FormatInt ( ctxUserID , 10 )
2020-11-23 21:49:36 +01:00
}
2021-07-29 09:42:15 +08:00
if ctx . FormBool ( "assigned" ) {
2025-03-21 05:25:36 +01:00
searchOpt . AssigneeID = strconv . FormatInt ( ctxUserID , 10 )
2020-11-23 21:49:36 +01:00
}
2021-07-29 09:42:15 +08:00
if ctx . FormBool ( "mentioned" ) {
2024-03-13 09:25:53 +01:00
searchOpt . MentionID = optional . Some ( ctxUserID )
2020-09-25 08:30:40 +09:00
}
2021-07-29 09:42:15 +08:00
if ctx . FormBool ( "review_requested" ) {
2024-03-13 09:25:53 +01:00
searchOpt . ReviewRequestedID = optional . Some ( ctxUserID )
2021-01-17 17:34:19 +01:00
}
2023-02-25 03:55:50 +01:00
if ctx . FormBool ( "reviewed" ) {
2024-03-13 09:25:53 +01:00
searchOpt . ReviewedID = optional . Some ( ctxUserID )
2023-02-25 03:55:50 +01:00
}
2023-07-31 14:28:53 +08:00
}
2019-10-31 00:06:10 -05:00
2023-07-31 14:28:53 +08:00
ids , total , err := issue_indexer . SearchIssues ( ctx , searchOpt )
if err != nil {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2023-07-31 14:28:53 +08:00
return
}
issues , err := issues_model . GetIssuesByIDs ( ctx , ids , true )
if err != nil {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2023-07-31 14:28:53 +08:00
return
2019-10-31 00:06:10 -05:00
}
2026-03-08 15:35:50 +01:00
ctx . SetLinkHeader ( total , limit )
2023-07-31 14:28:53 +08:00
ctx . SetTotalCountHeader ( total )
2024-04-09 05:26:41 +08:00
ctx . JSON ( http . StatusOK , convert . ToAPIIssueList ( ctx , ctx . Doer , 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
2020-06-27 22:32:28 +02:00
// enum: [closed, open, all]
2019-02-04 16:20:44 +01:00
// - name: labels
// in: query
2025-09-25 18:56:49 +02:00
// description: comma separated list of label names. Fetch only issues that have any of this label names. Non existent labels are discarded.
2019-02-04 16:20:44 +01:00
// 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-06-27 22:32:28 +02:00
// enum: [issues, pulls]
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
2021-06-17 00:33:37 +02:00
// - name: since
// in: query
2021-09-18 03:01:50 +02:00
// description: Only show items updated after the given time. This is a timestamp in RFC 3339 format
2021-06-17 00:33:37 +02:00
// type: string
// format: date-time
// required: false
// - name: before
// in: query
2021-09-18 03:01:50 +02:00
// description: Only show items updated before the given time. This is a timestamp in RFC 3339 format
2021-06-17 00:33:37 +02:00
// type: string
// format: date-time
// required: false
// - name: created_by
// in: query
2024-03-11 17:24:23 +08:00
// description: Only show items which were created by the given user
2021-06-17 00:33:37 +02:00
// type: string
// - name: assigned_by
// in: query
2021-09-18 03:01:50 +02:00
// description: Only show items for which the given user is assigned
2021-06-17 00:33:37 +02:00
// type: string
// - name: mentioned_by
// in: query
2021-09-18 03:01:50 +02:00
// description: Only show items in which the given user was mentioned
2021-06-17 00:33:37 +02:00
// 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
2020-06-09 06:57:38 +02:00
// description: page size of results
2020-01-24 19:00:29 +00:00
// type: integer
2017-11-12 23:02:25 -08:00
// responses:
// "200":
// "$ref": "#/responses/IssueList"
2023-09-13 04:37:54 +02:00
// "404":
// "$ref": "#/responses/notFound"
2023-05-21 09:50:53 +08:00
before , since , err := context . GetQueryBeforeSince ( ctx . Base )
2021-06-17 00:33:37 +02:00
if err != nil {
2025-02-17 14:13:17 +08:00
ctx . APIError ( http . StatusUnprocessableEntity , err )
2021-06-17 00:33:37 +02:00
return
}
2019-12-20 18:07:12 +01:00
2025-12-21 08:57:41 +07:00
isClosed := common . ParseIssueFilterStateIsClosed ( ctx . FormString ( "state" ) )
2021-08-11 17:08:52 +02:00
keyword := ctx . FormTrim ( "q" )
2018-03-07 11:00:56 +01:00
if strings . IndexByte ( keyword , 0 ) >= 0 {
keyword = ""
}
2023-07-31 14:28:53 +08:00
var labelIDs [ ] int64
2021-08-11 02:31:13 +02:00
if splitted := strings . Split ( ctx . FormString ( "labels" ) , "," ) ; len ( splitted ) > 0 {
2023-09-16 16:39:12 +02:00
labelIDs , err = issues_model . GetLabelIDsInRepoByNames ( ctx , ctx . Repo . Repository . ID , splitted )
2019-02-04 16:20:44 +01:00
if err != nil {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2019-02-04 16:20:44 +01:00
return
}
}
2020-04-30 06:15:39 +02:00
var mileIDs [ ] int64
2021-08-11 02:31:13 +02:00
if part := strings . Split ( ctx . FormString ( "milestones" ) , "," ) ; len ( part ) > 0 {
2020-04-30 06:15:39 +02:00
for i := range part {
// uses names and fall back to ids
// non existent milestones are discarded
2023-09-16 16:39:12 +02:00
mile , err := issues_model . GetMilestoneByRepoIDANDName ( ctx , ctx . Repo . Repository . ID , part [ i ] )
2020-04-30 06:15:39 +02:00
if err == nil {
mileIDs = append ( mileIDs , mile . ID )
continue
}
2022-04-08 17:11:15 +08:00
if ! issues_model . IsErrMilestoneNotExist ( err ) {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2020-04-30 06:15:39 +02:00
return
}
id , err := strconv . ParseInt ( part [ i ] , 10 , 64 )
if err != nil {
continue
}
2022-04-08 17:11:15 +08:00
mile , err = issues_model . GetMilestoneByRepoID ( ctx , ctx . Repo . Repository . ID , id )
2020-04-30 06:15:39 +02:00
if err == nil {
mileIDs = append ( mileIDs , mile . ID )
continue
}
2022-04-08 17:11:15 +08:00
if issues_model . IsErrMilestoneNotExist ( err ) {
2020-04-30 06:15:39 +02:00
continue
}
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2020-04-30 06:15:39 +02:00
}
}
2020-01-24 19:00:29 +00:00
listOptions := utils . GetListOptions ( ctx )
2024-03-02 16:42:31 +01:00
isPull := optional . None [ bool ] ( )
2021-08-11 02:31:13 +02:00
switch ctx . FormString ( "type" ) {
2020-01-20 20:00:32 +08:00
case "pulls" :
2024-03-02 16:42:31 +01:00
isPull = optional . Some ( true )
2020-01-20 20:00:32 +08:00
case "issues" :
2024-03-02 16:42:31 +01:00
isPull = optional . Some ( false )
2020-01-20 20:00:32 +08:00
}
2024-03-02 16:42:31 +01:00
if isPull . Has ( ) && ! ctx . Repo . CanReadIssuesOrPulls ( isPull . Value ( ) ) {
2025-02-17 14:13:17 +08:00
ctx . APIErrorNotFound ( )
2023-11-26 01:21:21 +08:00
return
}
2024-03-02 16:42:31 +01:00
if ! isPull . Has ( ) {
2023-11-26 01:21:21 +08:00
canReadIssues := ctx . Repo . CanRead ( unit . TypeIssues )
canReadPulls := ctx . Repo . CanRead ( unit . TypePullRequests )
if ! canReadIssues && ! canReadPulls {
2025-02-17 14:13:17 +08:00
ctx . APIErrorNotFound ( )
2023-11-26 01:21:21 +08:00
return
} else if ! canReadIssues {
2024-03-02 16:42:31 +01:00
isPull = optional . Some ( true )
2023-11-26 01:21:21 +08:00
} else if ! canReadPulls {
2024-03-02 16:42:31 +01:00
isPull = optional . Some ( false )
2023-11-26 01:21:21 +08:00
}
}
2021-06-17 00:33:37 +02:00
// FIXME: we should be more efficient here
createdByID := getUserIDForFilter ( ctx , "created_by" )
if ctx . Written ( ) {
return
}
assignedByID := getUserIDForFilter ( ctx , "assigned_by" )
if ctx . Written ( ) {
return
}
mentionedByID := getUserIDForFilter ( ctx , "mentioned_by" )
if ctx . Written ( ) {
return
}
2023-07-31 14:28:53 +08:00
searchOpt := & issue_indexer . SearchOptions {
Paginator : & listOptions ,
Keyword : keyword ,
RepoIDs : [ ] int64 { ctx . Repo . Repository . ID } ,
IsPull : isPull ,
IsClosed : isClosed ,
SortBy : issue_indexer . SortByCreatedDesc ,
}
if since != 0 {
2024-03-13 09:25:53 +01:00
searchOpt . UpdatedAfterUnix = optional . Some ( since )
2023-07-31 14:28:53 +08:00
}
if before != 0 {
2024-03-13 09:25:53 +01:00
searchOpt . UpdatedBeforeUnix = optional . Some ( before )
2023-07-31 14:28:53 +08:00
}
if len ( labelIDs ) == 1 && labelIDs [ 0 ] == 0 {
searchOpt . NoLabelOnly = true
} else {
for _ , labelID := range labelIDs {
if labelID > 0 {
searchOpt . IncludedLabelIDs = append ( searchOpt . IncludedLabelIDs , labelID )
} else {
searchOpt . ExcludedLabelIDs = append ( searchOpt . ExcludedLabelIDs , - labelID )
}
2020-09-25 08:30:40 +09:00
}
2023-07-31 14:28:53 +08:00
}
2018-03-07 11:00:56 +01:00
2023-07-31 14:28:53 +08:00
if len ( mileIDs ) == 1 && mileIDs [ 0 ] == db . NoConditionID {
searchOpt . MilestoneIDs = [ ] int64 { 0 }
} else {
searchOpt . MilestoneIDs = mileIDs
}
2020-09-25 08:30:40 +09:00
2023-07-31 14:28:53 +08:00
if createdByID > 0 {
2025-03-21 05:25:36 +01:00
searchOpt . PosterID = strconv . FormatInt ( createdByID , 10 )
2023-07-31 14:28:53 +08:00
}
if assignedByID > 0 {
2025-03-21 05:25:36 +01:00
searchOpt . AssigneeID = strconv . FormatInt ( assignedByID , 10 )
2023-07-31 14:28:53 +08:00
}
if mentionedByID > 0 {
2024-03-13 09:25:53 +01:00
searchOpt . MentionID = optional . Some ( mentionedByID )
2023-07-31 14:28:53 +08:00
}
ids , total , err := issue_indexer . SearchIssues ( ctx , searchOpt )
if err != nil {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2023-07-31 14:28:53 +08:00
return
}
issues , err := issues_model . GetIssuesByIDs ( ctx , ids , true )
if err != nil {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2023-07-31 14:28:53 +08:00
return
2016-03-13 23:20:22 -04:00
}
2026-03-08 15:35:50 +01:00
ctx . SetLinkHeader ( total , listOptions . PageSize )
2023-07-31 14:28:53 +08:00
ctx . SetTotalCountHeader ( total )
2024-04-09 05:26:41 +08:00
ctx . JSON ( http . StatusOK , convert . ToAPIIssueList ( ctx , ctx . Doer , issues ) )
2016-03-13 23:20:22 -04:00
}
2021-06-17 00:33:37 +02:00
func getUserIDForFilter ( ctx * context . APIContext , queryName string ) int64 {
2021-08-11 02:31:13 +02:00
userName := ctx . FormString ( queryName )
2021-06-17 00:33:37 +02:00
if len ( userName ) == 0 {
return 0
}
2022-05-20 22:08:52 +08:00
user , err := user_model . GetUserByName ( ctx , userName )
2021-11-24 17:49:20 +08:00
if user_model . IsErrUserNotExist ( err ) {
2025-02-17 14:13:17 +08:00
ctx . APIErrorNotFound ( err )
2021-06-17 00:33:37 +02:00
return 0
}
if err != nil {
2025-02-17 14:13:17 +08:00
ctx . APIErrorInternal ( err )
2021-06-17 00:33:37 +02:00
return 0
}
return user . ID
}
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"
2024-12-24 21:47:45 +08:00
issue , err := issues_model . GetIssueWithAttrsByIndex ( ctx , ctx . Repo . Repository . ID , ctx . PathParamInt64 ( "index" ) )
2016-03-13 23:20:22 -04:00
if err != nil {
2022-06-13 17:37:59 +08:00
if issues_model . IsErrIssueNotExist ( err ) {
2025-02-17 14:13:17 +08:00
ctx . APIErrorNotFound ( )
2016-03-13 23:20:22 -04:00
} else {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2016-03-13 23:20:22 -04:00
}
return
}
2023-11-26 01:21:21 +08:00
if ! ctx . Repo . CanReadIssuesOrPulls ( issue . IsPull ) {
2025-02-17 14:13:17 +08:00
ctx . APIErrorNotFound ( )
2023-11-26 01:21:21 +08:00
return
}
2024-04-09 05:26:41 +08:00
ctx . JSON ( http . StatusOK , convert . ToAPIIssue ( ctx , ctx . Doer , issue ) )
2016-03-13 23:20:22 -04:00
}
2016-11-24 15:04:31 +08:00
// CreateIssue create an issue of a repository
2021-01-26 23:36:53 +08:00
func CreateIssue ( ctx * context . APIContext ) {
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"
2023-09-13 04:37:54 +02:00
// "404":
// "$ref": "#/responses/notFound"
2019-12-20 18:07:12 +01:00
// "412":
// "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"
2023-09-22 01:43:29 +02:00
// "423":
// "$ref": "#/responses/repoArchivedError"
2024-03-04 09:16:03 +01:00
2021-01-26 23:36:53 +08:00
form := web . GetForm ( ctx ) . ( * api . CreateIssueOption )
2019-08-15 22:46:21 +08:00
var deadlineUnix timeutil . TimeStamp
2021-11-10 03:57:58 +08:00
if form . Deadline != nil && ctx . Repo . CanWrite ( unit . TypeIssues ) {
2019-08-15 22:46:21 +08:00
deadlineUnix = timeutil . TimeStamp ( form . Deadline . Unix ( ) )
2018-05-01 21:05:28 +02:00
}
2022-06-13 17:37:59 +08:00
issue := & issues_model . 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 ,
2022-03-22 08:03:22 +01:00
PosterID : ctx . Doer . ID ,
Poster : ctx . Doer ,
2018-05-01 21:05:28 +02:00
Content : form . Body ,
2020-12-15 18:38:10 +00:00
Ref : form . Ref ,
2018-05-01 21:05:28 +02:00
DeadlineUnix : deadlineUnix ,
2016-03-13 23:20:22 -04:00
}
2022-01-20 18:46:10 +01:00
assigneeIDs := make ( [ ] int64 , 0 )
2018-06-19 17:15:11 +02:00
var err error
2021-11-10 03:57:58 +08:00
if ctx . Repo . CanWrite ( unit . TypeIssues ) {
2018-06-19 17:15:11 +02:00
issue . MilestoneID = form . Milestone
2022-11-19 09:12:33 +01:00
assigneeIDs , err = issues_model . MakeIDsFromAPIAssigneesToAdd ( ctx , form . Assignee , form . Assignees )
2018-06-19 17:15:11 +02:00
if err != nil {
2021-11-24 17:49:20 +08:00
if user_model . IsErrUserNotExist ( err ) {
2025-02-17 14:13:17 +08:00
ctx . APIError ( http . StatusUnprocessableEntity , fmt . Sprintf ( "Assignee does not exist: [name: %s]" , err ) )
2018-06-19 17:15:11 +02:00
} else {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( 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 {
2022-12-03 10:48:26 +08:00
assignee , err := user_model . GetUserByID ( ctx , aID )
2019-10-25 16:46:37 +02:00
if err != nil {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2019-10-25 16:46:37 +02:00
return
}
2022-05-11 18:09:36 +08:00
valid , err := access_model . CanBeAssigned ( ctx , assignee , ctx . Repo . Repository , false )
2019-10-25 16:46:37 +02:00
if err != nil {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2019-10-25 16:46:37 +02:00
return
}
if ! valid {
2025-02-17 14:13:17 +08:00
ctx . APIError ( http . StatusUnprocessableEntity , repo_model . 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
}
2024-03-04 19:24:02 +08:00
if err := issue_service . NewIssue ( ctx , ctx . Repo . Repository , issue , form . Labels , nil , assigneeIDs , 0 ) ; err != nil {
2022-06-13 17:37:59 +08:00
if repo_model . IsErrUserDoesNotHaveAccessToRepo ( err ) {
2025-02-17 14:13:17 +08:00
ctx . APIError ( http . StatusBadRequest , err )
2024-03-04 09:16:03 +01:00
} else if errors . Is ( err , user_model . ErrBlockedUser ) {
2025-02-17 14:13:17 +08:00
ctx . APIError ( http . StatusForbidden , err )
2024-03-04 09:16:03 +01:00
} else {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2018-05-09 18:29:04 +02:00
}
2016-03-13 23:20:22 -04:00
return
}
2016-05-27 18:23:39 -07:00
if form . Closed {
2024-12-24 23:38:30 -08:00
if err := issue_service . CloseIssue ( ctx , issue , ctx . Doer , "" ) ; err != nil {
2022-06-13 17:37:59 +08:00
if issues_model . IsErrDependenciesLeft ( err ) {
2025-02-17 14:13:17 +08:00
ctx . APIError ( http . StatusPreconditionFailed , "cannot close this issue because it still has open dependencies" )
2018-07-17 23:23:58 +02:00
return
}
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( 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
2022-06-13 17:37:59 +08:00
issue , err = issues_model . GetIssueByID ( ctx , issue . ID )
2016-03-13 23:20:22 -04:00
if err != nil {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2016-03-13 23:20:22 -04:00
return
}
2024-04-09 05:26:41 +08:00
ctx . JSON ( http . StatusCreated , convert . ToAPIIssue ( ctx , ctx . Doer , issue ) )
2016-03-13 23:20:22 -04:00
}
2016-11-24 15:04:31 +08:00
// EditIssue modify an issue of a repository
2021-01-26 23:36:53 +08:00
func EditIssue ( ctx * context . APIContext ) {
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.
2026-03-30 09:44:32 -04:00
// description: |
// Pass `content_version` to enable optimistic locking on body edits.
// If the version doesn't match the current value, the request fails with 409 Conflict.
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"
2021-01-26 23:36:53 +08:00
form := web . GetForm ( ctx ) . ( * api . EditIssueOption )
2024-12-24 21:47:45 +08:00
issue , err := issues_model . GetIssueByIndex ( ctx , ctx . Repo . Repository . ID , ctx . PathParamInt64 ( "index" ) )
2016-03-13 23:20:22 -04:00
if err != nil {
2022-06-13 17:37:59 +08:00
if issues_model . IsErrIssueNotExist ( err ) {
2025-02-17 14:13:17 +08:00
ctx . APIErrorNotFound ( )
2016-03-13 23:20:22 -04:00
} else {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( 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
2022-06-13 17:37:59 +08:00
err = issue . LoadAttributes ( ctx )
2019-04-23 12:07:12 -05:00
if err != nil {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2019-04-23 12:07:12 -05:00
return
}
2022-03-22 08:03:22 +01:00
if ! issue . IsPoster ( ctx . Doer . ID ) && ! canWrite {
2019-12-20 18:07:12 +01:00
ctx . Status ( http . StatusForbidden )
2016-03-13 23:20:22 -04:00
return
}
2026-03-30 09:44:32 -04:00
// Fail fast: if content_version is provided and already stale, reject
// before any mutations. The DB-level check in ChangeContent still
// handles concurrent requests.
// TODO: wrap all mutations in a transaction to fully prevent partial writes.
if form . ContentVersion != nil && * form . ContentVersion != issue . ContentVersion {
ctx . APIError ( http . StatusConflict , issues_model . ErrIssueAlreadyChanged )
return
}
2016-03-13 23:20:22 -04:00
if len ( form . Title ) > 0 {
2024-05-03 15:11:51 +09:00
err = issue_service . ChangeTitle ( ctx , issue , ctx . Doer , form . Title )
if err != nil {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2024-05-03 15:11:51 +09:00
return
}
2016-03-13 23:20:22 -04:00
}
if form . Body != nil {
2026-03-30 09:44:32 -04:00
contentVersion := issue . ContentVersion
if form . ContentVersion != nil {
contentVersion = * form . ContentVersion
}
err = issue_service . ChangeContent ( ctx , issue , ctx . Doer , * form . Body , contentVersion )
2024-05-03 15:11:51 +09:00
if err != nil {
2024-05-27 18:34:18 +03:00
if errors . Is ( err , issues_model . ErrIssueAlreadyChanged ) {
2026-03-30 09:44:32 -04:00
ctx . APIError ( http . StatusConflict , err )
2024-05-27 18:34:18 +03:00
return
}
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2024-05-03 15:11:51 +09:00
return
}
2016-03-13 23:20:22 -04:00
}
2020-12-15 18:38:10 +00:00
if form . Ref != nil {
2023-04-15 02:18:28 +08:00
err = issue_service . ChangeIssueRef ( ctx , issue , ctx . Doer , * form . Ref )
2020-12-15 18:38:10 +00:00
if err != nil {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2020-12-15 18:38:10 +00:00
return
}
}
2016-03-13 23:20:22 -04:00
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
2024-09-15 00:40:36 +08:00
if form . RemoveDeadline == nil || ! * form . RemoveDeadline {
if form . Deadline == nil {
2025-02-17 14:13:17 +08:00
ctx . APIError ( http . StatusBadRequest , "The due_date cannot be empty" )
2024-09-15 00:40:36 +08:00
return
}
if ! 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-11-03 15:46:32 +01:00
}
2023-09-29 14:12:54 +02:00
if err := issues_model . UpdateIssueDeadline ( ctx , issue , deadlineUnix , ctx . Doer ) ; err != nil {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( 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
}
2023-04-15 02:18:28 +08:00
err = issue_service . UpdateAssignees ( ctx , issue , oneAssignee , form . Assignees , ctx . Doer )
2018-05-09 18:29:04 +02:00
if err != nil {
2024-03-04 09:16:03 +01:00
if errors . Is ( err , user_model . ErrBlockedUser ) {
2025-02-17 14:13:17 +08:00
ctx . APIError ( http . StatusForbidden , err )
2024-03-04 09:16:03 +01:00
} else {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2024-03-04 09:16:03 +01:00
}
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
2025-05-11 16:56:24 -07:00
if issue . MilestoneID > 0 {
issue . Milestone , err = issues_model . GetMilestoneByRepoID ( ctx , ctx . Repo . Repository . ID , * form . Milestone )
if err != nil {
ctx . APIErrorInternal ( err )
return
}
} else {
issue . Milestone = nil
}
2023-10-11 06:24:07 +02:00
if err = issue_service . ChangeMilestoneAssign ( ctx , issue , ctx . Doer , oldMilestoneID ) ; err != nil {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2016-03-13 23:20:22 -04:00
return
}
}
2016-08-23 18:09:32 +02:00
if form . State != nil {
2021-10-03 05:11:17 +02:00
if issue . IsPull {
2024-03-21 21:13:08 +08:00
if err := issue . LoadPullRequest ( ctx ) ; err != nil {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2021-10-03 05:11:17 +02:00
return
2024-03-21 21:13:08 +08:00
}
if issue . PullRequest . HasMerged {
2025-02-17 14:13:17 +08:00
ctx . APIError ( http . StatusPreconditionFailed , "cannot change state of this pull request, it was already merged" )
2021-10-03 05:11:17 +02:00
return
}
}
2024-09-02 03:08:27 +09:00
2024-12-24 23:38:30 -08:00
state := api . StateType ( * form . State )
closeOrReopenIssue ( ctx , issue , state )
if ctx . Written ( ) {
2024-09-02 03:08:27 +09:00
return
}
2016-08-23 18:09:32 +02:00
}
2016-03-13 23:20:22 -04:00
// Refetch from database to assign some automatic values
2022-06-13 17:37:59 +08:00
issue , err = issues_model . GetIssueByID ( ctx , issue . ID )
2016-03-13 23:20:22 -04:00
if err != nil {
2025-02-17 14:13:17 +08:00
ctx . APIErrorInternal ( err )
2020-01-01 23:51:10 +01:00
return
}
2022-11-19 09:12:33 +01:00
if err = issue . LoadMilestone ( ctx ) ; err != nil {
2025-02-17 14:13:17 +08:00
ctx . APIErrorInternal ( err )
2016-03-13 23:20:22 -04:00
return
}
2024-04-09 05:26:41 +08:00
ctx . JSON ( http . StatusCreated , convert . ToAPIIssue ( ctx , ctx . Doer , issue ) )
2016-03-13 23:20:22 -04:00
}
2018-07-16 14:43:00 +02:00
2022-03-01 01:20:15 +01:00
func DeleteIssue ( ctx * context . APIContext ) {
// swagger:operation DELETE /repos/{owner}/{repo}/issues/{index} issue issueDelete
// ---
// summary: Delete an issue
// 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 issue to delete
// type: integer
// format: int64
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
2024-12-24 21:47:45 +08:00
issue , err := issues_model . GetIssueByIndex ( ctx , ctx . Repo . Repository . ID , ctx . PathParamInt64 ( "index" ) )
2022-03-01 01:20:15 +01:00
if err != nil {
2022-06-13 17:37:59 +08:00
if issues_model . IsErrIssueNotExist ( err ) {
2025-02-17 14:13:17 +08:00
ctx . APIErrorNotFound ( err )
2022-03-01 01:20:15 +01:00
} else {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2022-03-01 01:20:15 +01:00
}
return
}
2025-09-19 08:04:18 -07:00
if err = issue_service . DeleteIssue ( ctx , ctx . Doer , issue ) ; err != nil {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2022-03-01 01:20:15 +01:00
return
}
ctx . Status ( http . StatusNoContent )
}
2018-07-16 14:43:00 +02:00
// UpdateIssueDeadline updates an issue deadline
2021-01-26 23:36:53 +08:00
func UpdateIssueDeadline ( ctx * context . APIContext ) {
2018-07-16 14:43:00 +02:00
// 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"
2021-01-26 23:36:53 +08:00
form := web . GetForm ( ctx ) . ( * api . EditDeadlineOption )
2024-12-24 21:47:45 +08:00
issue , err := issues_model . GetIssueByIndex ( ctx , ctx . Repo . Repository . ID , ctx . PathParamInt64 ( "index" ) )
2018-07-16 14:43:00 +02:00
if err != nil {
2022-06-13 17:37:59 +08:00
if issues_model . IsErrIssueNotExist ( err ) {
2025-02-17 14:13:17 +08:00
ctx . APIErrorNotFound ( )
2018-07-16 14:43:00 +02:00
} else {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2018-07-16 14:43:00 +02:00
}
return
}
2020-01-20 20:00:32 +08:00
if ! ctx . Repo . CanWriteIssuesOrPulls ( issue . IsPull ) {
2025-02-17 14:13:17 +08:00
ctx . APIError ( http . StatusForbidden , "Not repo writer" )
2018-07-16 14:43:00 +02:00
return
}
2024-11-04 23:46:40 -08:00
deadlineUnix , _ := common . ParseAPIDeadlineToEndOfDay ( form . Deadline )
2023-09-29 14:12:54 +02:00
if err := issues_model . UpdateIssueDeadline ( ctx , issue , deadlineUnix , ctx . Doer ) ; err != nil {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2018-07-16 14:43:00 +02:00
return
}
2024-11-04 23:46:40 -08:00
ctx . JSON ( http . StatusCreated , api . IssueDeadline { Deadline : deadlineUnix . AsTimePtr ( ) } )
2018-07-16 14:43:00 +02:00
}
2024-12-24 23:38:30 -08:00
func closeOrReopenIssue ( ctx * context . APIContext , issue * issues_model . Issue , state api . StateType ) {
if state != api . StateOpen && state != api . StateClosed {
2025-02-17 14:13:17 +08:00
ctx . APIError ( http . StatusPreconditionFailed , fmt . Sprintf ( "unknown state: %s" , state ) )
2024-12-24 23:38:30 -08:00
return
}
if state == api . StateClosed && ! issue . IsClosed {
if err := issue_service . CloseIssue ( ctx , issue , ctx . Doer , "" ) ; err != nil {
if issues_model . IsErrDependenciesLeft ( err ) {
2025-02-17 14:13:17 +08:00
ctx . APIError ( http . StatusPreconditionFailed , "cannot close this issue or pull request because it still has open dependencies" )
2024-12-24 23:38:30 -08:00
return
}
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2024-12-24 23:38:30 -08:00
return
}
} else if state == api . StateOpen && issue . IsClosed {
if err := issue_service . ReopenIssue ( ctx , issue , ctx . Doer , "" ) ; err != nil {
2025-02-18 04:41:03 +08:00
ctx . APIErrorInternal ( err )
2024-12-24 23:38:30 -08:00
return
}
}
}