code.gitea.io/gitea@v1.21.7/routers/api/v1/repo/issue_reaction.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package repo
     5  
     6  import (
     7  	"errors"
     8  	"net/http"
     9  
    10  	issues_model "code.gitea.io/gitea/models/issues"
    11  	"code.gitea.io/gitea/modules/context"
    12  	api "code.gitea.io/gitea/modules/structs"
    13  	"code.gitea.io/gitea/modules/web"
    14  	"code.gitea.io/gitea/routers/api/v1/utils"
    15  	"code.gitea.io/gitea/services/convert"
    16  )
    17  
    18  // GetIssueCommentReactions list reactions of a comment from an issue
    19  func GetIssueCommentReactions(ctx *context.APIContext) {
    20  	// swagger:operation GET /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issueGetCommentReactions
    21  	// ---
    22  	// summary: Get a list of reactions from a comment of an issue
    23  	// consumes:
    24  	// - application/json
    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: id
    39  	//   in: path
    40  	//   description: id of the comment to edit
    41  	//   type: integer
    42  	//   format: int64
    43  	//   required: true
    44  	// responses:
    45  	//   "200":
    46  	//     "$ref": "#/responses/ReactionList"
    47  	//   "403":
    48  	//     "$ref": "#/responses/forbidden"
    49  	//   "404":
    50  	//     "$ref": "#/responses/notFound"
    51  
    52  	comment, err := issues_model.GetCommentByID(ctx, ctx.ParamsInt64(":id"))
    53  	if err != nil {
    54  		if issues_model.IsErrCommentNotExist(err) {
    55  			ctx.NotFound(err)
    56  		} else {
    57  			ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
    58  		}
    59  		return
    60  	}
    61  
    62  	if err := comment.LoadIssue(ctx); err != nil {
    63  		ctx.Error(http.StatusInternalServerError, "comment.LoadIssue", err)
    64  		return
    65  	}
    66  
    67  	if comment.Issue.RepoID != ctx.Repo.Repository.ID {
    68  		ctx.NotFound()
    69  		return
    70  	}
    71  
    72  	if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
    73  		ctx.Error(http.StatusForbidden, "GetIssueCommentReactions", errors.New("no permission to get reactions"))
    74  		return
    75  	}
    76  
    77  	reactions, _, err := issues_model.FindCommentReactions(ctx, comment.IssueID, comment.ID)
    78  	if err != nil {
    79  		ctx.Error(http.StatusInternalServerError, "FindCommentReactions", err)
    80  		return
    81  	}
    82  	_, err = reactions.LoadUsers(ctx, ctx.Repo.Repository)
    83  	if err != nil {
    84  		ctx.Error(http.StatusInternalServerError, "ReactionList.LoadUsers()", err)
    85  		return
    86  	}
    87  
    88  	var result []api.Reaction
    89  	for _, r := range reactions {
    90  		result = append(result, api.Reaction{
    91  			User:     convert.ToUser(ctx, r.User, ctx.Doer),
    92  			Reaction: r.Type,
    93  			Created:  r.CreatedUnix.AsTime(),
    94  		})
    95  	}
    96  
    97  	ctx.JSON(http.StatusOK, result)
    98  }
    99  
   100  // PostIssueCommentReaction add a reaction to a comment of an issue
   101  func PostIssueCommentReaction(ctx *context.APIContext) {
   102  	// swagger:operation POST /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issuePostCommentReaction
   103  	// ---
   104  	// summary: Add a reaction to a comment of an issue
   105  	// consumes:
   106  	// - application/json
   107  	// produces:
   108  	// - application/json
   109  	// parameters:
   110  	// - name: owner
   111  	//   in: path
   112  	//   description: owner of the repo
   113  	//   type: string
   114  	//   required: true
   115  	// - name: repo
   116  	//   in: path
   117  	//   description: name of the repo
   118  	//   type: string
   119  	//   required: true
   120  	// - name: id
   121  	//   in: path
   122  	//   description: id of the comment to edit
   123  	//   type: integer
   124  	//   format: int64
   125  	//   required: true
   126  	// - name: content
   127  	//   in: body
   128  	//   schema:
   129  	//     "$ref": "#/definitions/EditReactionOption"
   130  	// responses:
   131  	//   "200":
   132  	//     "$ref": "#/responses/Reaction"
   133  	//   "201":
   134  	//     "$ref": "#/responses/Reaction"
   135  	//   "403":
   136  	//     "$ref": "#/responses/forbidden"
   137  	//   "404":
   138  	//     "$ref": "#/responses/notFound"
   139  
   140  	form := web.GetForm(ctx).(*api.EditReactionOption)
   141  
   142  	changeIssueCommentReaction(ctx, *form, true)
   143  }
   144  
   145  // DeleteIssueCommentReaction remove a reaction from a comment of an issue
   146  func DeleteIssueCommentReaction(ctx *context.APIContext) {
   147  	// swagger:operation DELETE /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issueDeleteCommentReaction
   148  	// ---
   149  	// summary: Remove a reaction from a comment of an issue
   150  	// consumes:
   151  	// - application/json
   152  	// produces:
   153  	// - application/json
   154  	// parameters:
   155  	// - name: owner
   156  	//   in: path
   157  	//   description: owner of the repo
   158  	//   type: string
   159  	//   required: true
   160  	// - name: repo
   161  	//   in: path
   162  	//   description: name of the repo
   163  	//   type: string
   164  	//   required: true
   165  	// - name: id
   166  	//   in: path
   167  	//   description: id of the comment to edit
   168  	//   type: integer
   169  	//   format: int64
   170  	//   required: true
   171  	// - name: content
   172  	//   in: body
   173  	//   schema:
   174  	//     "$ref": "#/definitions/EditReactionOption"
   175  	// responses:
   176  	//   "200":
   177  	//     "$ref": "#/responses/empty"
   178  	//   "403":
   179  	//     "$ref": "#/responses/forbidden"
   180  	//   "404":
   181  	//     "$ref": "#/responses/notFound"
   182  
   183  	form := web.GetForm(ctx).(*api.EditReactionOption)
   184  
   185  	changeIssueCommentReaction(ctx, *form, false)
   186  }
   187  
   188  func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {
   189  	comment, err := issues_model.GetCommentByID(ctx, ctx.ParamsInt64(":id"))
   190  	if err != nil {
   191  		if issues_model.IsErrCommentNotExist(err) {
   192  			ctx.NotFound(err)
   193  		} else {
   194  			ctx.Error(http.StatusInternalServerError, "GetCommentByID", err)
   195  		}
   196  		return
   197  	}
   198  
   199  	if err = comment.LoadIssue(ctx); err != nil {
   200  		ctx.Error(http.StatusInternalServerError, "comment.LoadIssue() failed", err)
   201  		return
   202  	}
   203  
   204  	if comment.Issue.RepoID != ctx.Repo.Repository.ID {
   205  		ctx.NotFound()
   206  		return
   207  	}
   208  
   209  	if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
   210  		ctx.NotFound()
   211  		return
   212  	}
   213  
   214  	if comment.Issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull) {
   215  		ctx.Error(http.StatusForbidden, "ChangeIssueCommentReaction", errors.New("no permission to change reaction"))
   216  		return
   217  	}
   218  
   219  	if isCreateType {
   220  		// PostIssueCommentReaction part
   221  		reaction, err := issues_model.CreateCommentReaction(ctx, ctx.Doer.ID, comment.Issue.ID, comment.ID, form.Reaction)
   222  		if err != nil {
   223  			if issues_model.IsErrForbiddenIssueReaction(err) {
   224  				ctx.Error(http.StatusForbidden, err.Error(), err)
   225  			} else if issues_model.IsErrReactionAlreadyExist(err) {
   226  				ctx.JSON(http.StatusOK, api.Reaction{
   227  					User:     convert.ToUser(ctx, ctx.Doer, ctx.Doer),
   228  					Reaction: reaction.Type,
   229  					Created:  reaction.CreatedUnix.AsTime(),
   230  				})
   231  			} else {
   232  				ctx.Error(http.StatusInternalServerError, "CreateCommentReaction", err)
   233  			}
   234  			return
   235  		}
   236  
   237  		ctx.JSON(http.StatusCreated, api.Reaction{
   238  			User:     convert.ToUser(ctx, ctx.Doer, ctx.Doer),
   239  			Reaction: reaction.Type,
   240  			Created:  reaction.CreatedUnix.AsTime(),
   241  		})
   242  	} else {
   243  		// DeleteIssueCommentReaction part
   244  		err = issues_model.DeleteCommentReaction(ctx, ctx.Doer.ID, comment.Issue.ID, comment.ID, form.Reaction)
   245  		if err != nil {
   246  			ctx.Error(http.StatusInternalServerError, "DeleteCommentReaction", err)
   247  			return
   248  		}
   249  		// ToDo respond 204
   250  		ctx.Status(http.StatusOK)
   251  	}
   252  }
   253  
   254  // GetIssueReactions list reactions of an issue
   255  func GetIssueReactions(ctx *context.APIContext) {
   256  	// swagger:operation GET /repos/{owner}/{repo}/issues/{index}/reactions issue issueGetIssueReactions
   257  	// ---
   258  	// summary: Get a list reactions of an issue
   259  	// consumes:
   260  	// - application/json
   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  	// - name: page
   281  	//   in: query
   282  	//   description: page number of results to return (1-based)
   283  	//   type: integer
   284  	// - name: limit
   285  	//   in: query
   286  	//   description: page size of results
   287  	//   type: integer
   288  	// responses:
   289  	//   "200":
   290  	//     "$ref": "#/responses/ReactionList"
   291  	//   "403":
   292  	//     "$ref": "#/responses/forbidden"
   293  	//   "404":
   294  	//     "$ref": "#/responses/notFound"
   295  
   296  	issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
   297  	if err != nil {
   298  		if issues_model.IsErrIssueNotExist(err) {
   299  			ctx.NotFound()
   300  		} else {
   301  			ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
   302  		}
   303  		return
   304  	}
   305  
   306  	if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
   307  		ctx.Error(http.StatusForbidden, "GetIssueReactions", errors.New("no permission to get reactions"))
   308  		return
   309  	}
   310  
   311  	reactions, count, err := issues_model.FindIssueReactions(ctx, issue.ID, utils.GetListOptions(ctx))
   312  	if err != nil {
   313  		ctx.Error(http.StatusInternalServerError, "FindIssueReactions", err)
   314  		return
   315  	}
   316  	_, err = reactions.LoadUsers(ctx, ctx.Repo.Repository)
   317  	if err != nil {
   318  		ctx.Error(http.StatusInternalServerError, "ReactionList.LoadUsers()", err)
   319  		return
   320  	}
   321  
   322  	var result []api.Reaction
   323  	for _, r := range reactions {
   324  		result = append(result, api.Reaction{
   325  			User:     convert.ToUser(ctx, r.User, ctx.Doer),
   326  			Reaction: r.Type,
   327  			Created:  r.CreatedUnix.AsTime(),
   328  		})
   329  	}
   330  
   331  	ctx.SetTotalCountHeader(count)
   332  	ctx.JSON(http.StatusOK, result)
   333  }
   334  
   335  // PostIssueReaction add a reaction to an issue
   336  func PostIssueReaction(ctx *context.APIContext) {
   337  	// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/reactions issue issuePostIssueReaction
   338  	// ---
   339  	// summary: Add a reaction to an issue
   340  	// consumes:
   341  	// - application/json
   342  	// produces:
   343  	// - application/json
   344  	// parameters:
   345  	// - name: owner
   346  	//   in: path
   347  	//   description: owner of the repo
   348  	//   type: string
   349  	//   required: true
   350  	// - name: repo
   351  	//   in: path
   352  	//   description: name of the repo
   353  	//   type: string
   354  	//   required: true
   355  	// - name: index
   356  	//   in: path
   357  	//   description: index of the issue
   358  	//   type: integer
   359  	//   format: int64
   360  	//   required: true
   361  	// - name: content
   362  	//   in: body
   363  	//   schema:
   364  	//     "$ref": "#/definitions/EditReactionOption"
   365  	// responses:
   366  	//   "200":
   367  	//     "$ref": "#/responses/Reaction"
   368  	//   "201":
   369  	//     "$ref": "#/responses/Reaction"
   370  	//   "403":
   371  	//     "$ref": "#/responses/forbidden"
   372  	//   "404":
   373  	//     "$ref": "#/responses/notFound"
   374  	form := web.GetForm(ctx).(*api.EditReactionOption)
   375  	changeIssueReaction(ctx, *form, true)
   376  }
   377  
   378  // DeleteIssueReaction remove a reaction from an issue
   379  func DeleteIssueReaction(ctx *context.APIContext) {
   380  	// swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/reactions issue issueDeleteIssueReaction
   381  	// ---
   382  	// summary: Remove a reaction from an issue
   383  	// consumes:
   384  	// - application/json
   385  	// produces:
   386  	// - application/json
   387  	// parameters:
   388  	// - name: owner
   389  	//   in: path
   390  	//   description: owner of the repo
   391  	//   type: string
   392  	//   required: true
   393  	// - name: repo
   394  	//   in: path
   395  	//   description: name of the repo
   396  	//   type: string
   397  	//   required: true
   398  	// - name: index
   399  	//   in: path
   400  	//   description: index of the issue
   401  	//   type: integer
   402  	//   format: int64
   403  	//   required: true
   404  	// - name: content
   405  	//   in: body
   406  	//   schema:
   407  	//     "$ref": "#/definitions/EditReactionOption"
   408  	// responses:
   409  	//   "200":
   410  	//     "$ref": "#/responses/empty"
   411  	//   "403":
   412  	//     "$ref": "#/responses/forbidden"
   413  	//   "404":
   414  	//     "$ref": "#/responses/notFound"
   415  	form := web.GetForm(ctx).(*api.EditReactionOption)
   416  	changeIssueReaction(ctx, *form, false)
   417  }
   418  
   419  func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {
   420  	issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
   421  	if err != nil {
   422  		if issues_model.IsErrIssueNotExist(err) {
   423  			ctx.NotFound()
   424  		} else {
   425  			ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
   426  		}
   427  		return
   428  	}
   429  
   430  	if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
   431  		ctx.Error(http.StatusForbidden, "ChangeIssueCommentReaction", errors.New("no permission to change reaction"))
   432  		return
   433  	}
   434  
   435  	if isCreateType {
   436  		// PostIssueReaction part
   437  		reaction, err := issues_model.CreateIssueReaction(ctx, ctx.Doer.ID, issue.ID, form.Reaction)
   438  		if err != nil {
   439  			if issues_model.IsErrForbiddenIssueReaction(err) {
   440  				ctx.Error(http.StatusForbidden, err.Error(), err)
   441  			} else if issues_model.IsErrReactionAlreadyExist(err) {
   442  				ctx.JSON(http.StatusOK, api.Reaction{
   443  					User:     convert.ToUser(ctx, ctx.Doer, ctx.Doer),
   444  					Reaction: reaction.Type,
   445  					Created:  reaction.CreatedUnix.AsTime(),
   446  				})
   447  			} else {
   448  				ctx.Error(http.StatusInternalServerError, "CreateCommentReaction", err)
   449  			}
   450  			return
   451  		}
   452  
   453  		ctx.JSON(http.StatusCreated, api.Reaction{
   454  			User:     convert.ToUser(ctx, ctx.Doer, ctx.Doer),
   455  			Reaction: reaction.Type,
   456  			Created:  reaction.CreatedUnix.AsTime(),
   457  		})
   458  	} else {
   459  		// DeleteIssueReaction part
   460  		err = issues_model.DeleteIssueReaction(ctx, ctx.Doer.ID, issue.ID, form.Reaction)
   461  		if err != nil {
   462  			ctx.Error(http.StatusInternalServerError, "DeleteIssueReaction", err)
   463  			return
   464  		}
   465  		// ToDo respond 204
   466  		ctx.Status(http.StatusOK)
   467  	}
   468  }