summaryrefslogtreecommitdiffstats
path: root/routers/api
diff options
context:
space:
mode:
author6543 <6543@obermui.de>2019-12-07 23:04:19 +0100
committertechknowlogick <techknowlogick@gitea.io>2019-12-07 17:04:19 -0500
commit37e10d4543c1e516e1a721d72c0054fefceb9499 (patch)
treef824b014a135d3bcf243d5bb3e87cab3de225c90 /routers/api
parentee7df7ba8c5e6a4b32b0c4048d2b535d8df3cbe9 (diff)
downloadgitea-37e10d4543c1e516e1a721d72c0054fefceb9499.tar.gz
gitea-37e10d4543c1e516e1a721d72c0054fefceb9499.zip
[API] Add Reactions (#9220)
* reject reactions wich ar not allowed * dont duble check CreateReaction now throw ErrForbiddenIssueReaction * add /repos/{owner}/{repo}/issues/comments/{id}/reactions endpoint * add Find Functions * fix some swagger stuff + add issue reaction endpoints + GET ReactionList now use FindReactions... * explicite Issue Only Reaction for FindReactionsOptions with "-1" commentID * load issue; load user ... * return error again * swagger def canged after LINT * check if user has ben loaded * add Tests * better way of comparing results * add suggestion * use different issue for test (dont interfear with integration test) * test dont compare Location on timeCompare * TEST: add forbidden dubble add * add comments in code to explain * add settings.UI.ReactionsMap so if !setting.UI.ReactionsMap[opts.Type] works
Diffstat (limited to 'routers/api')
-rw-r--r--routers/api/v1/api.go20
-rw-r--r--routers/api/v1/repo/issue_reaction.go394
-rw-r--r--routers/api/v1/swagger/issue.go21
3 files changed, 428 insertions, 7 deletions
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 47c9c95c7f..cd5fc1f3eb 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -657,21 +657,25 @@ func RegisterRoutes(m *macaron.Macaron) {
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), repo.CreateIssue)
m.Group("/comments", func() {
m.Get("", repo.ListRepoIssueComments)
- m.Combo("/:id", reqToken()).
- Patch(mustNotBeArchived, bind(api.EditIssueCommentOption{}), repo.EditIssueComment).
- Delete(repo.DeleteIssueComment)
+ m.Group("/:id", func() {
+ m.Combo("", reqToken()).
+ Patch(mustNotBeArchived, bind(api.EditIssueCommentOption{}), repo.EditIssueComment).
+ Delete(repo.DeleteIssueComment)
+ m.Combo("/reactions", reqToken()).
+ Get(repo.GetIssueCommentReactions).
+ Post(bind(api.EditReactionOption{}), repo.PostIssueCommentReaction).
+ Delete(bind(api.EditReactionOption{}), repo.DeleteIssueCommentReaction)
+ })
})
m.Group("/:index", func() {
m.Combo("").Get(repo.GetIssue).
Patch(reqToken(), bind(api.EditIssueOption{}), repo.EditIssue)
-
m.Group("/comments", func() {
m.Combo("").Get(repo.ListIssueComments).
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
m.Combo("/:id", reqToken()).Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated).
Delete(repo.DeleteIssueCommentDeprecated)
})
-
m.Group("/labels", func() {
m.Combo("").Get(repo.ListIssueLabels).
Post(reqToken(), bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
@@ -679,12 +683,10 @@ func RegisterRoutes(m *macaron.Macaron) {
Delete(reqToken(), repo.ClearIssueLabels)
m.Delete("/:id", reqToken(), repo.DeleteIssueLabel)
})
-
m.Group("/times", func() {
m.Combo("").Get(repo.ListTrackedTimes).
Post(reqToken(), bind(api.AddTimeOption{}), repo.AddTime)
})
-
m.Combo("/deadline").Post(reqToken(), bind(api.EditDeadlineOption{}), repo.UpdateIssueDeadline)
m.Group("/stopwatch", func() {
m.Post("/start", reqToken(), repo.StartIssueStopwatch)
@@ -695,6 +697,10 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Put("/:user", reqToken(), repo.AddIssueSubscription)
m.Delete("/:user", reqToken(), repo.DelIssueSubscription)
})
+ m.Combo("/reactions", reqToken()).
+ Get(repo.GetIssueReactions).
+ Post(bind(api.EditReactionOption{}), repo.PostIssueReaction).
+ Delete(bind(api.EditReactionOption{}), repo.DeleteIssueReaction)
})
}, mustEnableIssuesOrPulls)
m.Group("/labels", func() {
diff --git a/routers/api/v1/repo/issue_reaction.go b/routers/api/v1/repo/issue_reaction.go
new file mode 100644
index 0000000000..56e12ccdcc
--- /dev/null
+++ b/routers/api/v1/repo/issue_reaction.go
@@ -0,0 +1,394 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package repo
+
+import (
+ "errors"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/context"
+ api "code.gitea.io/gitea/modules/structs"
+)
+
+// GetIssueCommentReactions list reactions of a issue comment
+func GetIssueCommentReactions(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issueGetCommentReactions
+ // ---
+ // summary: Get a list reactions of a issue comment
+ // 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: id
+ // in: path
+ // description: id of the comment to edit
+ // type: integer
+ // format: int64
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/ReactionResponseList"
+ comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
+ if err != nil {
+ if models.IsErrCommentNotExist(err) {
+ ctx.NotFound(err)
+ } else {
+ ctx.Error(500, "GetCommentByID", err)
+ }
+ return
+ }
+
+ if !ctx.Repo.CanRead(models.UnitTypeIssues) && !ctx.User.IsAdmin {
+ ctx.Error(403, "GetIssueCommentReactions", errors.New("no permission to get reactions"))
+ return
+ }
+
+ reactions, err := models.FindCommentReactions(comment)
+ if err != nil {
+ ctx.Error(500, "FindIssueReactions", err)
+ return
+ }
+ _, err = reactions.LoadUsers()
+ if err != nil {
+ ctx.Error(500, "ReactionList.LoadUsers()", err)
+ return
+ }
+
+ var result []api.ReactionResponse
+ for _, r := range reactions {
+ result = append(result, api.ReactionResponse{
+ User: r.User.APIFormat(),
+ Reaction: r.Type,
+ Created: r.CreatedUnix.AsTime(),
+ })
+ }
+
+ ctx.JSON(200, result)
+}
+
+// PostIssueCommentReaction add a reaction to a comment of a issue
+func PostIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption) {
+ // swagger:operation POST /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issuePostCommentReaction
+ // ---
+ // summary: Add a reaction to a comment of a issue comment
+ // 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: id
+ // in: path
+ // description: id of the comment to edit
+ // type: integer
+ // format: int64
+ // required: true
+ // - name: content
+ // in: body
+ // schema:
+ // "$ref": "#/definitions/EditReactionOption"
+ // responses:
+ // "201":
+ // "$ref": "#/responses/ReactionResponse"
+ changeIssueCommentReaction(ctx, form, true)
+}
+
+// DeleteIssueCommentReaction list reactions of a issue comment
+func DeleteIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption) {
+ // swagger:operation DELETE /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issueDeleteCommentReaction
+ // ---
+ // summary: Remove a reaction from a comment of a issue comment
+ // 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: id
+ // in: path
+ // description: id of the comment to edit
+ // type: integer
+ // format: int64
+ // required: true
+ // - name: content
+ // in: body
+ // schema:
+ // "$ref": "#/definitions/EditReactionOption"
+ // responses:
+ // "200":
+ // "$ref": "#/responses/empty"
+ changeIssueCommentReaction(ctx, form, false)
+}
+
+func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {
+ comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
+ if err != nil {
+ if models.IsErrCommentNotExist(err) {
+ ctx.NotFound(err)
+ } else {
+ ctx.Error(500, "GetCommentByID", err)
+ }
+ return
+ }
+
+ err = comment.LoadIssue()
+ if err != nil {
+ ctx.Error(500, "comment.LoadIssue() failed", err)
+ }
+
+ if comment.Issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin {
+ ctx.Error(403, "ChangeIssueCommentReaction", errors.New("no permission to change reaction"))
+ return
+ }
+
+ if isCreateType {
+ // PostIssueCommentReaction part
+ reaction, err := models.CreateCommentReaction(ctx.User, comment.Issue, comment, form.Reaction)
+ if err != nil {
+ if models.IsErrForbiddenIssueReaction(err) {
+ ctx.Error(403, err.Error(), err)
+ } else {
+ ctx.Error(500, "CreateCommentReaction", err)
+ }
+ return
+ }
+ _, err = reaction.LoadUser()
+ if err != nil {
+ ctx.Error(500, "Reaction.LoadUser()", err)
+ return
+ }
+
+ ctx.JSON(201, api.ReactionResponse{
+ User: reaction.User.APIFormat(),
+ Reaction: reaction.Type,
+ Created: reaction.CreatedUnix.AsTime(),
+ })
+ } else {
+ // DeleteIssueCommentReaction part
+ err = models.DeleteCommentReaction(ctx.User, comment.Issue, comment, form.Reaction)
+ if err != nil {
+ ctx.Error(500, "DeleteCommentReaction", err)
+ return
+ }
+ ctx.Status(200)
+ }
+}
+
+// GetIssueReactions list reactions of a issue comment
+func GetIssueReactions(ctx *context.APIContext) {
+ // swagger:operation GET /repos/{owner}/{repo}/issues/{index}/reactions issue issueGetIssueReactions
+ // ---
+ // summary: Get a list reactions of a issue
+ // 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
+ // type: integer
+ // format: int64
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/ReactionResponseList"
+ issue, err := models.GetIssueWithAttrsByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
+ if err != nil {
+ if models.IsErrIssueNotExist(err) {
+ ctx.NotFound()
+ } else {
+ ctx.Error(500, "GetIssueByIndex", err)
+ }
+ return
+ }
+
+ if !ctx.Repo.CanRead(models.UnitTypeIssues) && !ctx.User.IsAdmin {
+ ctx.Error(403, "GetIssueReactions", errors.New("no permission to get reactions"))
+ return
+ }
+
+ reactions, err := models.FindIssueReactions(issue)
+ if err != nil {
+ ctx.Error(500, "FindIssueReactions", err)
+ return
+ }
+ _, err = reactions.LoadUsers()
+ if err != nil {
+ ctx.Error(500, "ReactionList.LoadUsers()", err)
+ return
+ }
+
+ var result []api.ReactionResponse
+ for _, r := range reactions {
+ result = append(result, api.ReactionResponse{
+ User: r.User.APIFormat(),
+ Reaction: r.Type,
+ Created: r.CreatedUnix.AsTime(),
+ })
+ }
+
+ ctx.JSON(200, result)
+}
+
+// PostIssueReaction add a reaction to a comment of a issue
+func PostIssueReaction(ctx *context.APIContext, form api.EditReactionOption) {
+ // swagger:operation POST /repos/{owner}/{repo}/issues/{index}/reactions issue issuePostIssueReaction
+ // ---
+ // summary: Add a reaction to a comment of a issue
+ // 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
+ // type: integer
+ // format: int64
+ // required: true
+ // - name: content
+ // in: body
+ // schema:
+ // "$ref": "#/definitions/EditReactionOption"
+ // responses:
+ // "201":
+ // "$ref": "#/responses/ReactionResponse"
+ changeIssueReaction(ctx, form, true)
+}
+
+// DeleteIssueReaction list reactions of a issue comment
+func DeleteIssueReaction(ctx *context.APIContext, form api.EditReactionOption) {
+ // swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/reactions issue issueDeleteIssueReaction
+ // ---
+ // summary: Remove a reaction from a comment of a issue
+ // 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
+ // type: integer
+ // format: int64
+ // required: true
+ // - name: content
+ // in: body
+ // schema:
+ // "$ref": "#/definitions/EditReactionOption"
+ // responses:
+ // "200":
+ // "$ref": "#/responses/empty"
+ changeIssueReaction(ctx, form, false)
+}
+
+func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {
+ issue, err := models.GetIssueWithAttrsByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
+ if err != nil {
+ if models.IsErrIssueNotExist(err) {
+ ctx.NotFound()
+ } else {
+ ctx.Error(500, "GetIssueByIndex", err)
+ }
+ return
+ }
+
+ if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin {
+ ctx.Error(403, "ChangeIssueCommentReaction", errors.New("no permission to change reaction"))
+ return
+ }
+
+ if isCreateType {
+ // PostIssueReaction part
+ reaction, err := models.CreateIssueReaction(ctx.User, issue, form.Reaction)
+ if err != nil {
+ if models.IsErrForbiddenIssueReaction(err) {
+ ctx.Error(403, err.Error(), err)
+ } else {
+ ctx.Error(500, "CreateCommentReaction", err)
+ }
+ return
+ }
+ _, err = reaction.LoadUser()
+ if err != nil {
+ ctx.Error(500, "Reaction.LoadUser()", err)
+ return
+ }
+
+ ctx.JSON(201, api.ReactionResponse{
+ User: reaction.User.APIFormat(),
+ Reaction: reaction.Type,
+ Created: reaction.CreatedUnix.AsTime(),
+ })
+ } else {
+ // DeleteIssueReaction part
+ err = models.DeleteIssueReaction(ctx.User, issue, form.Reaction)
+ if err != nil {
+ ctx.Error(500, "DeleteIssueReaction", err)
+ return
+ }
+ ctx.Status(200)
+ }
+}
diff --git a/routers/api/v1/swagger/issue.go b/routers/api/v1/swagger/issue.go
index c06186bf6c..a78c2982fd 100644
--- a/routers/api/v1/swagger/issue.go
+++ b/routers/api/v1/swagger/issue.go
@@ -84,3 +84,24 @@ type swaggerIssueDeadline struct {
// in:body
Body api.IssueDeadline `json:"body"`
}
+
+// EditReactionOption
+// swagger:response EditReactionOption
+type swaggerEditReactionOption struct {
+ // in:body
+ Body api.EditReactionOption `json:"body"`
+}
+
+// ReactionResponse
+// swagger:response ReactionResponse
+type swaggerReactionResponse struct {
+ // in:body
+ Body api.ReactionResponse `json:"body"`
+}
+
+// ReactionResponseList
+// swagger:response ReactionResponseList
+type swaggerReactionResponseList struct {
+ // in:body
+ Body []api.ReactionResponse `json:"body"`
+}