mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 01:34:27 +00:00 
			
		
		
		
	Add issue subscription check to API (#10967)
close #10962 Adds `GET /api/v1/repos/{owner}/{repo}/issues/{index}/subscriptions/check` -> return a `WachInfo`
This commit is contained in:
		
							
								
								
									
										66
									
								
								integrations/api_issue_subscription_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								integrations/api_issue_subscription_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					// Copyright 2020 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 integrations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						api "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestAPIIssueSubscriptions(t *testing.T) {
 | 
				
			||||||
 | 
						defer prepareTestEnv(t)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						issue1 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 1}).(*models.Issue)
 | 
				
			||||||
 | 
						issue2 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue)
 | 
				
			||||||
 | 
						issue3 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 3}).(*models.Issue)
 | 
				
			||||||
 | 
						issue4 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 4}).(*models.Issue)
 | 
				
			||||||
 | 
						issue5 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 8}).(*models.Issue)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						owner := models.AssertExistsAndLoadBean(t, &models.User{ID: issue1.PosterID}).(*models.User)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						session := loginUser(t, owner.Name)
 | 
				
			||||||
 | 
						token := getTokenForLoggedInUser(t, session)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						testSubscription := func(issue *models.Issue, isWatching bool) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							issueRepo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: issue.RepoID}).(*models.Repository)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/subscriptions/check?token=%s", issueRepo.OwnerName, issueRepo.Name, issue.Index, token)
 | 
				
			||||||
 | 
							req := NewRequest(t, "GET", urlStr)
 | 
				
			||||||
 | 
							resp := session.MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
							wi := new(api.WatchInfo)
 | 
				
			||||||
 | 
							DecodeJSON(t, resp, wi)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.EqualValues(t, isWatching, wi.Subscribed)
 | 
				
			||||||
 | 
							assert.EqualValues(t, !isWatching, wi.Ignored)
 | 
				
			||||||
 | 
							assert.EqualValues(t, issue.APIURL()+"/subscriptions", wi.URL)
 | 
				
			||||||
 | 
							assert.EqualValues(t, issue.CreatedUnix, wi.CreatedAt.Unix())
 | 
				
			||||||
 | 
							assert.EqualValues(t, issueRepo.APIURL(), wi.RepositoryURL)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						testSubscription(issue1, true)
 | 
				
			||||||
 | 
						testSubscription(issue2, true)
 | 
				
			||||||
 | 
						testSubscription(issue3, true)
 | 
				
			||||||
 | 
						testSubscription(issue4, false)
 | 
				
			||||||
 | 
						testSubscription(issue5, false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						issue1Repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: issue1.RepoID}).(*models.Repository)
 | 
				
			||||||
 | 
						urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/subscriptions/%s?token=%s", issue1Repo.OwnerName, issue1Repo.Name, issue1.Index, owner.Name, token)
 | 
				
			||||||
 | 
						req := NewRequest(t, "DELETE", urlStr)
 | 
				
			||||||
 | 
						session.MakeRequest(t, req, http.StatusCreated)
 | 
				
			||||||
 | 
						testSubscription(issue1, false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						issue5Repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: issue5.RepoID}).(*models.Repository)
 | 
				
			||||||
 | 
						urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/subscriptions/%s?token=%s", issue5Repo.OwnerName, issue5Repo.Name, issue5.Index, owner.Name, token)
 | 
				
			||||||
 | 
						req = NewRequest(t, "PUT", urlStr)
 | 
				
			||||||
 | 
						session.MakeRequest(t, req, http.StatusCreated)
 | 
				
			||||||
 | 
						testSubscription(issue5, true)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -332,6 +332,13 @@ func (issue *Issue) GetIsRead(userID int64) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// APIURL returns the absolute APIURL to this issue.
 | 
					// APIURL returns the absolute APIURL to this issue.
 | 
				
			||||||
func (issue *Issue) APIURL() string {
 | 
					func (issue *Issue) APIURL() string {
 | 
				
			||||||
 | 
						if issue.Repo == nil {
 | 
				
			||||||
 | 
							err := issue.LoadRepo()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error("Issue[%d].APIURL(): %v", issue.ID, err)
 | 
				
			||||||
 | 
								return ""
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return fmt.Sprintf("%s/issues/%d", issue.Repo.APIURL(), issue.Index)
 | 
						return fmt.Sprintf("%s/issues/%d", issue.Repo.APIURL(), issue.Index)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -64,6 +64,23 @@ func getIssueWatch(e Engine, userID, issueID int64) (iw *IssueWatch, exists bool
 | 
				
			|||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CheckIssueWatch check if an user is watching an issue
 | 
				
			||||||
 | 
					// it takes participants and repo watch into account
 | 
				
			||||||
 | 
					func CheckIssueWatch(user *User, issue *Issue) (bool, error) {
 | 
				
			||||||
 | 
						iw, exist, err := getIssueWatch(x, user.ID, issue.ID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if exist {
 | 
				
			||||||
 | 
							return iw.IsWatching, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						w, err := getWatch(x, user.ID, issue.RepoID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return isWatchMode(w.Mode) || IsUserParticipantsOfIssue(user, issue), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetIssueWatchersIDs returns IDs of subscribers or explicit unsubscribers to a given issue id
 | 
					// GetIssueWatchersIDs returns IDs of subscribers or explicit unsubscribers to a given issue id
 | 
				
			||||||
// but avoids joining with `user` for performance reasons
 | 
					// but avoids joining with `user` for performance reasons
 | 
				
			||||||
// User permissions must be verified elsewhere if required
 | 
					// User permissions must be verified elsewhere if required
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -735,6 +735,7 @@ func RegisterRoutes(m *macaron.Macaron) {
 | 
				
			|||||||
						})
 | 
											})
 | 
				
			||||||
						m.Group("/subscriptions", func() {
 | 
											m.Group("/subscriptions", func() {
 | 
				
			||||||
							m.Get("", repo.GetIssueSubscribers)
 | 
												m.Get("", repo.GetIssueSubscribers)
 | 
				
			||||||
 | 
												m.Get("/check", reqToken(), repo.CheckIssueSubscription)
 | 
				
			||||||
							m.Put("/:user", reqToken(), repo.AddIssueSubscription)
 | 
												m.Put("/:user", reqToken(), repo.AddIssueSubscription)
 | 
				
			||||||
							m.Delete("/:user", reqToken(), repo.DelIssueSubscription)
 | 
												m.Delete("/:user", reqToken(), repo.DelIssueSubscription)
 | 
				
			||||||
						})
 | 
											})
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
 | 
						api "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
	"code.gitea.io/gitea/routers/api/v1/utils"
 | 
						"code.gitea.io/gitea/routers/api/v1/utils"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -133,6 +134,64 @@ func setIssueSubscription(ctx *context.APIContext, watch bool) {
 | 
				
			|||||||
	ctx.Status(http.StatusCreated)
 | 
						ctx.Status(http.StatusCreated)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CheckIssueSubscription check if user is subscribed to an issue
 | 
				
			||||||
 | 
					func CheckIssueSubscription(ctx *context.APIContext) {
 | 
				
			||||||
 | 
						// swagger:operation GET /repos/{owner}/{repo}/issues/{index}/subscriptions/check issue issueCheckSubscription
 | 
				
			||||||
 | 
						// ---
 | 
				
			||||||
 | 
						// summary: Check if user is subscribed to an 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/WatchInfo"
 | 
				
			||||||
 | 
						//   "404":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/notFound"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if models.IsErrIssueNotExist(err) {
 | 
				
			||||||
 | 
								ctx.NotFound()
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						watching, err := models.CheckIssueWatch(ctx.User, issue)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.InternalServerError(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ctx.JSON(http.StatusOK, api.WatchInfo{
 | 
				
			||||||
 | 
							Subscribed:    watching,
 | 
				
			||||||
 | 
							Ignored:       !watching,
 | 
				
			||||||
 | 
							Reason:        nil,
 | 
				
			||||||
 | 
							CreatedAt:     issue.CreatedUnix.AsTime(),
 | 
				
			||||||
 | 
							URL:           issue.APIURL() + "/subscriptions",
 | 
				
			||||||
 | 
							RepositoryURL: ctx.Repo.Repository.APIURL(),
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetIssueSubscribers return subscribers of an issue
 | 
					// GetIssueSubscribers return subscribers of an issue
 | 
				
			||||||
func GetIssueSubscribers(ctx *context.APIContext) {
 | 
					func GetIssueSubscribers(ctx *context.APIContext) {
 | 
				
			||||||
	// swagger:operation GET /repos/{owner}/{repo}/issues/{index}/subscriptions issue issueSubscriptions
 | 
						// swagger:operation GET /repos/{owner}/{repo}/issues/{index}/subscriptions issue issueSubscriptions
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,6 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
					 | 
				
			||||||
	api "code.gitea.io/gitea/modules/structs"
 | 
						api "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
	"code.gitea.io/gitea/routers/api/v1/utils"
 | 
						"code.gitea.io/gitea/routers/api/v1/utils"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -124,7 +123,7 @@ func IsWatching(ctx *context.APIContext) {
 | 
				
			|||||||
			Reason:        nil,
 | 
								Reason:        nil,
 | 
				
			||||||
			CreatedAt:     ctx.Repo.Repository.CreatedUnix.AsTime(),
 | 
								CreatedAt:     ctx.Repo.Repository.CreatedUnix.AsTime(),
 | 
				
			||||||
			URL:           subscriptionURL(ctx.Repo.Repository),
 | 
								URL:           subscriptionURL(ctx.Repo.Repository),
 | 
				
			||||||
			RepositoryURL: repositoryURL(ctx.Repo.Repository),
 | 
								RepositoryURL: ctx.Repo.Repository.APIURL(),
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		ctx.NotFound()
 | 
							ctx.NotFound()
 | 
				
			||||||
@@ -162,7 +161,7 @@ func Watch(ctx *context.APIContext) {
 | 
				
			|||||||
		Reason:        nil,
 | 
							Reason:        nil,
 | 
				
			||||||
		CreatedAt:     ctx.Repo.Repository.CreatedUnix.AsTime(),
 | 
							CreatedAt:     ctx.Repo.Repository.CreatedUnix.AsTime(),
 | 
				
			||||||
		URL:           subscriptionURL(ctx.Repo.Repository),
 | 
							URL:           subscriptionURL(ctx.Repo.Repository),
 | 
				
			||||||
		RepositoryURL: repositoryURL(ctx.Repo.Repository),
 | 
							RepositoryURL: ctx.Repo.Repository.APIURL(),
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -197,10 +196,5 @@ func Unwatch(ctx *context.APIContext) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// subscriptionURL returns the URL of the subscription API endpoint of a repo
 | 
					// subscriptionURL returns the URL of the subscription API endpoint of a repo
 | 
				
			||||||
func subscriptionURL(repo *models.Repository) string {
 | 
					func subscriptionURL(repo *models.Repository) string {
 | 
				
			||||||
	return repositoryURL(repo) + "/subscription"
 | 
						return repo.APIURL() + "/subscription"
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// repositoryURL returns the URL of the API endpoint of a repo
 | 
					 | 
				
			||||||
func repositoryURL(repo *models.Repository) string {
 | 
					 | 
				
			||||||
	return setting.AppURL + "api/v1/" + repo.FullName()
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -749,21 +749,15 @@ func ViewIssue(ctx *context.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title)
 | 
						ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var iw *models.IssueWatch
 | 
						iw := new(models.IssueWatch)
 | 
				
			||||||
	var exists bool
 | 
					 | 
				
			||||||
	if ctx.User != nil {
 | 
						if ctx.User != nil {
 | 
				
			||||||
		iw, exists, err = models.GetIssueWatch(ctx.User.ID, issue.ID)
 | 
							iw.UserID = ctx.User.ID
 | 
				
			||||||
 | 
							iw.IssueID = issue.ID
 | 
				
			||||||
 | 
							iw.IsWatching, err = models.CheckIssueWatch(ctx.User, issue)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			ctx.ServerError("GetIssueWatch", err)
 | 
								ctx.InternalServerError(err)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if !exists {
 | 
					 | 
				
			||||||
			iw = &models.IssueWatch{
 | 
					 | 
				
			||||||
				UserID:     ctx.User.ID,
 | 
					 | 
				
			||||||
				IssueID:    issue.ID,
 | 
					 | 
				
			||||||
				IsWatching: models.IsWatching(ctx.User.ID, ctx.Repo.Repository.ID) || models.IsUserParticipantsOfIssue(ctx.User, issue),
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ctx.Data["IssueWatch"] = iw
 | 
						ctx.Data["IssueWatch"] = iw
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5217,6 +5217,53 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "/repos/{owner}/{repo}/issues/{index}/subscriptions/check": {
 | 
				
			||||||
 | 
					      "get": {
 | 
				
			||||||
 | 
					        "consumes": [
 | 
				
			||||||
 | 
					          "application/json"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "produces": [
 | 
				
			||||||
 | 
					          "application/json"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "tags": [
 | 
				
			||||||
 | 
					          "issue"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "summary": "Check if user is subscribed to an issue",
 | 
				
			||||||
 | 
					        "operationId": "issueCheckSubscription",
 | 
				
			||||||
 | 
					        "parameters": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "owner of the repo",
 | 
				
			||||||
 | 
					            "name": "owner",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "name of the repo",
 | 
				
			||||||
 | 
					            "name": "repo",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "integer",
 | 
				
			||||||
 | 
					            "format": "int64",
 | 
				
			||||||
 | 
					            "description": "index of the issue",
 | 
				
			||||||
 | 
					            "name": "index",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "responses": {
 | 
				
			||||||
 | 
					          "200": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/WatchInfo"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "404": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/notFound"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "/repos/{owner}/{repo}/issues/{index}/subscriptions/{user}": {
 | 
					    "/repos/{owner}/{repo}/issues/{index}/subscriptions/{user}": {
 | 
				
			||||||
      "put": {
 | 
					      "put": {
 | 
				
			||||||
        "consumes": [
 | 
					        "consumes": [
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user