diff options
author | 6543 <6543@obermui.de> | 2019-12-07 23:04:19 +0100 |
---|---|---|
committer | techknowlogick <techknowlogick@gitea.io> | 2019-12-07 17:04:19 -0500 |
commit | 37e10d4543c1e516e1a721d72c0054fefceb9499 (patch) | |
tree | f824b014a135d3bcf243d5bb3e87cab3de225c90 /routers/api | |
parent | ee7df7ba8c5e6a4b32b0c4048d2b535d8df3cbe9 (diff) | |
download | gitea-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.go | 20 | ||||
-rw-r--r-- | routers/api/v1/repo/issue_reaction.go | 394 | ||||
-rw-r--r-- | routers/api/v1/swagger/issue.go | 21 |
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"` +} |