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

Add issue subscription check to API #10967

Merged
merged 32 commits into from
Apr 21, 2020
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
7ca8cbb
move Check-Logic where it belongs
6543 Apr 4, 2020
c7ad54c
imprufe code [noise]
6543 Apr 4, 2020
ff590a6
add CheckIssueSubscription
6543 Apr 5, 2020
7939985
fix nill exeption
6543 Apr 5, 2020
83aedc2
fix
6543 Apr 5, 2020
e418b89
add TEST for Issue-Subscribe; Issue-Unsubscribe; CheckIssueSubscribtion
6543 Apr 5, 2020
7c4f75d
privacy extention
6543 Apr 5, 2020
02f3de5
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 5, 2020
7cf5ebf
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 5, 2020
322344e
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 5, 2020
c3d34db
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 5, 2020
e36866c
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 6, 2020
f366191
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 7, 2020
01a2cf9
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 8, 2020
0144089
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 10, 2020
ac19d91
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 11, 2020
919152b
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 11, 2020
308d8b8
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 13, 2020
92c61ec
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 15, 2020
aec512f
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 15, 2020
dc70f73
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 15, 2020
0867fbd
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 17, 2020
843f52c
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 18, 2020
5c24f78
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 18, 2020
d4f5df8
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 19, 2020
aab1f55
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 19, 2020
c5e53e5
CI.restart()
6543 Apr 19, 2020
6b44a24
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 19, 2020
059ae71
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 20, 2020
e540228
Merge branch 'master' into api-IssueSubscription-check_10962
zeripath Apr 20, 2020
858ce8e
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 20, 2020
d7d62ac
Merge branch 'master' into api-IssueSubscription-check_10962
6543 Apr 21, 2020
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
66 changes: 66 additions & 0 deletions integrations/api_issue_subscription_test.go
Original file line number Diff line number Diff line change
@@ -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) {
6543 marked this conversation as resolved.
Show resolved Hide resolved
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)
}
7 changes: 7 additions & 0 deletions models/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,13 @@ func (issue *Issue) GetIsRead(userID int64) error {

// APIURL returns the absolute APIURL to this issue.
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)
}

Expand Down
17 changes: 17 additions & 0 deletions models/issue_watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,23 @@ func getIssueWatch(e Engine, userID, issueID int64) (iw *IssueWatch, exists bool
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
// but avoids joining with `user` for performance reasons
// User permissions must be verified elsewhere if required
Expand Down
1 change: 1 addition & 0 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,7 @@ func RegisterRoutes(m *macaron.Macaron) {
})
m.Group("/subscriptions", func() {
m.Get("", repo.GetIssueSubscribers)
m.Get("/check", reqToken(), repo.CheckIssueSubscription)
m.Put("/:user", reqToken(), repo.AddIssueSubscription)
m.Delete("/:user", reqToken(), repo.DelIssueSubscription)
})
Expand Down
59 changes: 59 additions & 0 deletions routers/api/v1/repo/issue_subscription.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
)

Expand Down Expand Up @@ -133,6 +134,64 @@ func setIssueSubscription(ctx *context.APIContext, watch bool) {
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,
6543 marked this conversation as resolved.
Show resolved Hide resolved
Reason: nil,
CreatedAt: issue.CreatedUnix.AsTime(),
URL: issue.APIURL() + "/subscriptions",
RepositoryURL: ctx.Repo.Repository.APIURL(),
})
}

// GetIssueSubscribers return subscribers of an issue
func GetIssueSubscribers(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/issues/{index}/subscriptions issue issueSubscriptions
Expand Down
12 changes: 3 additions & 9 deletions routers/api/v1/user/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
)
Expand Down Expand Up @@ -122,7 +121,7 @@ func IsWatching(ctx *context.APIContext) {
Reason: nil,
CreatedAt: ctx.Repo.Repository.CreatedUnix.AsTime(),
URL: subscriptionURL(ctx.Repo.Repository),
RepositoryURL: repositoryURL(ctx.Repo.Repository),
RepositoryURL: ctx.Repo.Repository.APIURL(),
})
} else {
ctx.NotFound()
Expand Down Expand Up @@ -160,7 +159,7 @@ func Watch(ctx *context.APIContext) {
Reason: nil,
CreatedAt: ctx.Repo.Repository.CreatedUnix.AsTime(),
URL: subscriptionURL(ctx.Repo.Repository),
RepositoryURL: repositoryURL(ctx.Repo.Repository),
RepositoryURL: ctx.Repo.Repository.APIURL(),
})

}
Expand Down Expand Up @@ -195,10 +194,5 @@ func Unwatch(ctx *context.APIContext) {

// subscriptionURL returns the URL of the subscription API endpoint of a repo
func subscriptionURL(repo *models.Repository) string {
return repositoryURL(repo) + "/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()
return repo.APIURL() + "/subscription"
}
16 changes: 5 additions & 11 deletions routers/repo/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -749,21 +749,15 @@ func ViewIssue(ctx *context.Context) {

ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title)

var iw *models.IssueWatch
var exists bool
iw := new(models.IssueWatch)
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 {
ctx.ServerError("GetIssueWatch", err)
ctx.InternalServerError(err)
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

Expand Down
47 changes: 47 additions & 0 deletions templates/swagger/v1_json.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -5164,6 +5164,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}": {
"put": {
"consumes": [
Expand Down