code.gitea.io/gitea@v1.22.3/routers/api/v1/repo/issue_label.go (about)

     1  // Copyright 2016 The Gogs Authors. All rights reserved.
     2  // Copyright 2018 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package repo
     6  
     7  import (
     8  	"fmt"
     9  	"net/http"
    10  	"reflect"
    11  
    12  	issues_model "code.gitea.io/gitea/models/issues"
    13  	api "code.gitea.io/gitea/modules/structs"
    14  	"code.gitea.io/gitea/modules/web"
    15  	"code.gitea.io/gitea/services/context"
    16  	"code.gitea.io/gitea/services/convert"
    17  	issue_service "code.gitea.io/gitea/services/issue"
    18  )
    19  
    20  // ListIssueLabels list all the labels of an issue
    21  func ListIssueLabels(ctx *context.APIContext) {
    22  	// swagger:operation GET /repos/{owner}/{repo}/issues/{index}/labels issue issueGetLabels
    23  	// ---
    24  	// summary: Get an issue's labels
    25  	// produces:
    26  	// - application/json
    27  	// parameters:
    28  	// - name: owner
    29  	//   in: path
    30  	//   description: owner of the repo
    31  	//   type: string
    32  	//   required: true
    33  	// - name: repo
    34  	//   in: path
    35  	//   description: name of the repo
    36  	//   type: string
    37  	//   required: true
    38  	// - name: index
    39  	//   in: path
    40  	//   description: index of the issue
    41  	//   type: integer
    42  	//   format: int64
    43  	//   required: true
    44  	// responses:
    45  	//   "200":
    46  	//     "$ref": "#/responses/LabelList"
    47  	//   "404":
    48  	//     "$ref": "#/responses/notFound"
    49  
    50  	issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
    51  	if err != nil {
    52  		if issues_model.IsErrIssueNotExist(err) {
    53  			ctx.NotFound()
    54  		} else {
    55  			ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
    56  		}
    57  		return
    58  	}
    59  
    60  	if err := issue.LoadAttributes(ctx); err != nil {
    61  		ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
    62  		return
    63  	}
    64  
    65  	ctx.JSON(http.StatusOK, convert.ToLabelList(issue.Labels, ctx.Repo.Repository, ctx.Repo.Owner))
    66  }
    67  
    68  // AddIssueLabels add labels for an issue
    69  func AddIssueLabels(ctx *context.APIContext) {
    70  	// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/labels issue issueAddLabel
    71  	// ---
    72  	// summary: Add a label to an issue
    73  	// consumes:
    74  	// - application/json
    75  	// produces:
    76  	// - application/json
    77  	// parameters:
    78  	// - name: owner
    79  	//   in: path
    80  	//   description: owner of the repo
    81  	//   type: string
    82  	//   required: true
    83  	// - name: repo
    84  	//   in: path
    85  	//   description: name of the repo
    86  	//   type: string
    87  	//   required: true
    88  	// - name: index
    89  	//   in: path
    90  	//   description: index of the issue
    91  	//   type: integer
    92  	//   format: int64
    93  	//   required: true
    94  	// - name: body
    95  	//   in: body
    96  	//   schema:
    97  	//     "$ref": "#/definitions/IssueLabelsOption"
    98  	// responses:
    99  	//   "200":
   100  	//     "$ref": "#/responses/LabelList"
   101  	//   "403":
   102  	//     "$ref": "#/responses/forbidden"
   103  	//   "404":
   104  	//     "$ref": "#/responses/notFound"
   105  
   106  	form := web.GetForm(ctx).(*api.IssueLabelsOption)
   107  	issue, labels, err := prepareForReplaceOrAdd(ctx, *form)
   108  	if err != nil {
   109  		return
   110  	}
   111  
   112  	if err = issue_service.AddLabels(ctx, issue, ctx.Doer, labels); err != nil {
   113  		ctx.Error(http.StatusInternalServerError, "AddLabels", err)
   114  		return
   115  	}
   116  
   117  	labels, err = issues_model.GetLabelsByIssueID(ctx, issue.ID)
   118  	if err != nil {
   119  		ctx.Error(http.StatusInternalServerError, "GetLabelsByIssueID", err)
   120  		return
   121  	}
   122  
   123  	ctx.JSON(http.StatusOK, convert.ToLabelList(labels, ctx.Repo.Repository, ctx.Repo.Owner))
   124  }
   125  
   126  // DeleteIssueLabel delete a label for an issue
   127  func DeleteIssueLabel(ctx *context.APIContext) {
   128  	// swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/labels/{id} issue issueRemoveLabel
   129  	// ---
   130  	// summary: Remove a label from an issue
   131  	// produces:
   132  	// - application/json
   133  	// parameters:
   134  	// - name: owner
   135  	//   in: path
   136  	//   description: owner of the repo
   137  	//   type: string
   138  	//   required: true
   139  	// - name: repo
   140  	//   in: path
   141  	//   description: name of the repo
   142  	//   type: string
   143  	//   required: true
   144  	// - name: index
   145  	//   in: path
   146  	//   description: index of the issue
   147  	//   type: integer
   148  	//   format: int64
   149  	//   required: true
   150  	// - name: id
   151  	//   in: path
   152  	//   description: id of the label to remove
   153  	//   type: integer
   154  	//   format: int64
   155  	//   required: true
   156  	// responses:
   157  	//   "204":
   158  	//     "$ref": "#/responses/empty"
   159  	//   "403":
   160  	//     "$ref": "#/responses/forbidden"
   161  	//   "404":
   162  	//     "$ref": "#/responses/notFound"
   163  	//   "422":
   164  	//     "$ref": "#/responses/validationError"
   165  
   166  	issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
   167  	if err != nil {
   168  		if issues_model.IsErrIssueNotExist(err) {
   169  			ctx.NotFound()
   170  		} else {
   171  			ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
   172  		}
   173  		return
   174  	}
   175  
   176  	if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
   177  		ctx.Status(http.StatusForbidden)
   178  		return
   179  	}
   180  
   181  	label, err := issues_model.GetLabelByID(ctx, ctx.ParamsInt64(":id"))
   182  	if err != nil {
   183  		if issues_model.IsErrLabelNotExist(err) {
   184  			ctx.Error(http.StatusUnprocessableEntity, "", err)
   185  		} else {
   186  			ctx.Error(http.StatusInternalServerError, "GetLabelByID", err)
   187  		}
   188  		return
   189  	}
   190  
   191  	if err := issue_service.RemoveLabel(ctx, issue, ctx.Doer, label); err != nil {
   192  		ctx.Error(http.StatusInternalServerError, "DeleteIssueLabel", err)
   193  		return
   194  	}
   195  
   196  	ctx.Status(http.StatusNoContent)
   197  }
   198  
   199  // ReplaceIssueLabels replace labels for an issue
   200  func ReplaceIssueLabels(ctx *context.APIContext) {
   201  	// swagger:operation PUT /repos/{owner}/{repo}/issues/{index}/labels issue issueReplaceLabels
   202  	// ---
   203  	// summary: Replace an issue's labels
   204  	// consumes:
   205  	// - application/json
   206  	// produces:
   207  	// - application/json
   208  	// parameters:
   209  	// - name: owner
   210  	//   in: path
   211  	//   description: owner of the repo
   212  	//   type: string
   213  	//   required: true
   214  	// - name: repo
   215  	//   in: path
   216  	//   description: name of the repo
   217  	//   type: string
   218  	//   required: true
   219  	// - name: index
   220  	//   in: path
   221  	//   description: index of the issue
   222  	//   type: integer
   223  	//   format: int64
   224  	//   required: true
   225  	// - name: body
   226  	//   in: body
   227  	//   schema:
   228  	//     "$ref": "#/definitions/IssueLabelsOption"
   229  	// responses:
   230  	//   "200":
   231  	//     "$ref": "#/responses/LabelList"
   232  	//   "403":
   233  	//     "$ref": "#/responses/forbidden"
   234  	//   "404":
   235  	//     "$ref": "#/responses/notFound"
   236  	form := web.GetForm(ctx).(*api.IssueLabelsOption)
   237  	issue, labels, err := prepareForReplaceOrAdd(ctx, *form)
   238  	if err != nil {
   239  		return
   240  	}
   241  
   242  	if err := issue_service.ReplaceLabels(ctx, issue, ctx.Doer, labels); err != nil {
   243  		ctx.Error(http.StatusInternalServerError, "ReplaceLabels", err)
   244  		return
   245  	}
   246  
   247  	labels, err = issues_model.GetLabelsByIssueID(ctx, issue.ID)
   248  	if err != nil {
   249  		ctx.Error(http.StatusInternalServerError, "GetLabelsByIssueID", err)
   250  		return
   251  	}
   252  
   253  	ctx.JSON(http.StatusOK, convert.ToLabelList(labels, ctx.Repo.Repository, ctx.Repo.Owner))
   254  }
   255  
   256  // ClearIssueLabels delete all the labels for an issue
   257  func ClearIssueLabels(ctx *context.APIContext) {
   258  	// swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/labels issue issueClearLabels
   259  	// ---
   260  	// summary: Remove all labels from an issue
   261  	// produces:
   262  	// - application/json
   263  	// parameters:
   264  	// - name: owner
   265  	//   in: path
   266  	//   description: owner of the repo
   267  	//   type: string
   268  	//   required: true
   269  	// - name: repo
   270  	//   in: path
   271  	//   description: name of the repo
   272  	//   type: string
   273  	//   required: true
   274  	// - name: index
   275  	//   in: path
   276  	//   description: index of the issue
   277  	//   type: integer
   278  	//   format: int64
   279  	//   required: true
   280  	// responses:
   281  	//   "204":
   282  	//     "$ref": "#/responses/empty"
   283  	//   "403":
   284  	//     "$ref": "#/responses/forbidden"
   285  	//   "404":
   286  	//     "$ref": "#/responses/notFound"
   287  
   288  	issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
   289  	if err != nil {
   290  		if issues_model.IsErrIssueNotExist(err) {
   291  			ctx.NotFound()
   292  		} else {
   293  			ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
   294  		}
   295  		return
   296  	}
   297  
   298  	if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
   299  		ctx.Status(http.StatusForbidden)
   300  		return
   301  	}
   302  
   303  	if err := issue_service.ClearLabels(ctx, issue, ctx.Doer); err != nil {
   304  		ctx.Error(http.StatusInternalServerError, "ClearLabels", err)
   305  		return
   306  	}
   307  
   308  	ctx.Status(http.StatusNoContent)
   309  }
   310  
   311  func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption) (*issues_model.Issue, []*issues_model.Label, error) {
   312  	issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
   313  	if err != nil {
   314  		if issues_model.IsErrIssueNotExist(err) {
   315  			ctx.NotFound()
   316  		} else {
   317  			ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
   318  		}
   319  		return nil, nil, err
   320  	}
   321  
   322  	var (
   323  		labelIDs   []int64
   324  		labelNames []string
   325  	)
   326  	for _, label := range form.Labels {
   327  		rv := reflect.ValueOf(label)
   328  		switch rv.Kind() {
   329  		case reflect.Float64:
   330  			labelIDs = append(labelIDs, int64(rv.Float()))
   331  		case reflect.String:
   332  			labelNames = append(labelNames, rv.String())
   333  		}
   334  	}
   335  	if len(labelIDs) > 0 && len(labelNames) > 0 {
   336  		ctx.Error(http.StatusBadRequest, "InvalidLabels", "labels should be an array of strings or integers")
   337  		return nil, nil, fmt.Errorf("invalid labels")
   338  	}
   339  	if len(labelNames) > 0 {
   340  		labelIDs, err = issues_model.GetLabelIDsInRepoByNames(ctx, ctx.Repo.Repository.ID, labelNames)
   341  		if err != nil {
   342  			ctx.Error(http.StatusInternalServerError, "GetLabelIDsInRepoByNames", err)
   343  			return nil, nil, err
   344  		}
   345  	}
   346  
   347  	labels, err := issues_model.GetLabelsByIDs(ctx, labelIDs, "id", "repo_id", "org_id", "name", "exclusive")
   348  	if err != nil {
   349  		ctx.Error(http.StatusInternalServerError, "GetLabelsByIDs", err)
   350  		return nil, nil, err
   351  	}
   352  
   353  	if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
   354  		ctx.Status(http.StatusForbidden)
   355  		return nil, nil, nil
   356  	}
   357  
   358  	return issue, labels, err
   359  }