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 }