Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add API for comment reactions #8823

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 44 additions & 5 deletions models/issue_reaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"

"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"

"xorm.io/builder"
Expand All @@ -26,6 +27,9 @@ type Reaction struct {
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
}

// ReactionList represents list of reactions
type ReactionList []*Reaction

// FindReactionsOptions describes the conditions to Find reactions
type FindReactionsOptions struct {
IssueID int64
Expand All @@ -43,8 +47,15 @@ func (opts *FindReactionsOptions) toConds() builder.Cond {
return cond
}

func findReactions(e Engine, opts FindReactionsOptions) ([]*Reaction, error) {
reactions := make([]*Reaction, 0, 10)
//FindReactions returns Reactions based
func FindReactions(comment *Comment) (ReactionList, error) {
return findReactions(x, FindReactionsOptions{
IssueID: comment.IssueID,
CommentID: comment.ID})
}

func findReactions(e Engine, opts FindReactionsOptions) (ReactionList, error) {
reactions := make(ReactionList, 0, 10)
sess := e.Where(opts.toConds())
return reactions, sess.
Asc("reaction.issue_id", "reaction.comment_id", "reaction.created_unix", "reaction.id").
Expand Down Expand Up @@ -160,9 +171,6 @@ func DeleteCommentReaction(doer *User, issue *Issue, comment *Comment, content s
})
}

// ReactionList represents list of reactions
type ReactionList []*Reaction

// HasUser check if user has reacted
func (list ReactionList) HasUser(userID int64) bool {
if userID == 0 {
Expand Down Expand Up @@ -247,3 +255,34 @@ func (list ReactionList) GetMoreUserCount() int {
}
return len(list) - setting.UI.ReactionMaxUserNum
}

// APIFormat returns Raction in api Format
func (list ReactionList) APIFormat() []*api.CommentReaction {
var result []*api.CommentReaction
users := make(map[string][]*string)
counts := make(map[string]int64)

for _, r := range list {
u := r.User
t := r.Type
if t == "" {
_ = fmt.Errorf("Key is empty!")
continue
}
if u == nil {
_ = fmt.Errorf("Key: '" + t + "', User is Nil!")
continue
}
users[t] = append(users[t], &u.LoginName)
counts[t]++
}

for k, v := range users {
result = append(result, &api.CommentReaction{
Reaction: k,
Users: v,
Count: counts[k],
})
}
return result
}
9 changes: 9 additions & 0 deletions modules/structs/issue_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,12 @@ type EditIssueCommentOption struct {
// required: true
Body string `json:"body" binding:"Required"`
}

// CommentReaction represent comment reactions
type CommentReaction struct {
// required: true
Reaction string `json:"reaction"`
// required: true
Users []*string `json:"users"`
Count int64 `json:"count"`
}
12 changes: 9 additions & 3 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -657,9 +657,15 @@ 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").
Get(repo.GetCommentReactions).
Put(reqToken(), bind(api.CommentReaction{}), repo.AddCommentReaction).
Delete(reqToken(), bind(api.CommentReaction{}), repo.DelCommentReaction)
})
})
m.Group("/:index", func() {
m.Combo("").Get(repo.GetIssue).
Expand Down
180 changes: 180 additions & 0 deletions routers/api/v1/repo/issue_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,3 +393,183 @@ func deleteIssueComment(ctx *context.APIContext) {

ctx.Status(204)
}

//GetCommentReactions return all reactions of a specific comment
func GetCommentReactions(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issueGetCommentReactions
// ---
// summary: Return all reactions of a specific comment
// 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
// type: integer
// format: int64
// required: true
// responses:
// "200":
// "$ref": "#/responses/CommentReactionList"
comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
if err != nil {
if models.IsErrCommentNotExist(err) {
ctx.NotFound(err)
} else {
ctx.Error(500, "GetCommentByID", err)
}
return
}

rl, err := models.FindReactions(comment)
if err != nil {
ctx.Error(500, "FindReactionsOptions", err)
return
} else if rl == nil {
ctx.NotFound("No Reactions Found")
return
}

ctx.JSON(200, rl.APIFormat())

}

// AddCommentReaction create a reaction to a comment
func AddCommentReaction(ctx *context.APIContext, form api.CommentReaction) {
// swagger:operation PUT /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issueAddCommentReaction
// ---
// summary: Create reaction to a 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
// type: integer
// format: int64
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/CommentReaction"
// responses:
// "201":
// "$ref": "#/responses/empty"
// "304":
// description: User can only create reactions for itself if he is no admin
// "404":
// description: Comment not found
setCommentReaction(ctx, form, true)
}

// DelCommentReaction delete a reaction to a comment
func DelCommentReaction(ctx *context.APIContext, form api.CommentReaction) {
// swagger:operation DELETE /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issueDelCommentReaction
// ---
// summary: Delete reaction to a 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
// type: integer
// format: int64
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/CommentReaction"
// responses:
// "201":
// "$ref": "#/responses/empty"
// "304":
// description: User can only delete reactions for itself if he is no admin
// "404":
// description: Comment not found
setCommentReaction(ctx, form, false)
}

func setCommentReaction(ctx *context.APIContext, form api.CommentReaction, create 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
}
issue, err := models.GetIssueByID(comment.IssueID)
if err != nil {
if models.IsErrIssueNotExist(err) {
ctx.NotFound()
} else {
ctx.Error(500, "GetIssueByID", err)
}

return
}

for _, u := range form.Users {
user, err := models.GetUserByName(*u)
if err != nil {
ctx.Error(500, "GetUserByName", err)
}

if ctx.User.ID != user.ID && !ctx.Repo.IsAdmin() {
ctx.Status(403)
return
}

if create {
// Create Reaction
_, err = models.CreateCommentReaction(user, issue, comment, form.Reaction)
if err != nil {
ctx.Error(500, "CreateCommentReaction", err)
}
} else {
// Delete Reaction
err = models.DeleteCommentReaction(user, issue, comment, form.Reaction)
if err != nil {
ctx.Error(500, "DeleteCommentReaction", err)
}
}
}

ctx.Status(201)
}
14 changes: 14 additions & 0 deletions routers/api/v1/swagger/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,17 @@ type swaggerIssueDeadline struct {
// in:body
Body api.IssueDeadline `json:"body"`
}

// CommentReaction
// swagger:response CommentReaction
type swaggerCommentReaction struct {
// in:body
Body api.CommentReaction `json:"body"`
}

// CommentReactionList
// swagger:response CommentReactionList
type swaggerResponseCommentReactionList struct {
// in:body
Body []api.CommentReaction `json:"body"`
}
Loading