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