Skip to content

Commit 5a187f4

Browse files
6543lunny
authored andcommitted
Add API for Issue set Subscription (#8729)
* add issue subscriber API * subscribers return []user.APIFormat * add comments * more meaningfull description * without "reqToken()" api works ... * should be still secure beause ctx.user has to be there or nothing will hapen * FIX: getIssueWatchers() get only aktive suscriber * add return avter error on right position * Revert "FIX: getIssueWatchers() get only aktive suscriber" This reverts commit 5eca929. * Update routers/api/v1/repo/issue.go Co-Authored-By: guillep2k <[email protected]> * test go linter again * update swagger * GetIssueWatchers -> GetIssueSubscribers part one Co-Authored-By: guillep2k <[email protected]> * GetIssueWatchers -> GetIssueSubscribers part two * Revert "test go linter again" This reverts commit bab1235. * change description for unsubscribe too * golangci-lint timeout avter 5min * move issueSubscription to seperate file * dont create black entitys * use IsWatching until refactoring * Update License Info * better swagger description * Update .golangci.yml because functions moved from issue.go to issue_subscription.go * add IssueWatchList type * batch tasks * use e Engien * add error handling * error should be the last type when returning multiple items * short version * reurn empy UserList instead of nil
1 parent dd9cb32 commit 5a187f4

File tree

7 files changed

+425
-3
lines changed

7 files changed

+425
-3
lines changed

.golangci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ issues:
7373
- path: routers/routes/routes.go
7474
linters:
7575
- dupl
76+
- path: routers/api/v1/repo/issue_subscription.go
77+
linters:
78+
- dupl
7679
- path: routers/repo/view.go
7780
linters:
7881
- dupl

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,4 +521,4 @@ golangci-lint:
521521
export BINARY="golangci-lint"; \
522522
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.20.0; \
523523
fi
524-
golangci-lint run
524+
golangci-lint run --timeout 5m

models/issue_watch.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ type IssueWatch struct {
1616
UpdatedUnix timeutil.TimeStamp `xorm:"updated NOT NULL"`
1717
}
1818

19+
// IssueWatchList contains IssueWatch
20+
type IssueWatchList []*IssueWatch
21+
1922
// CreateOrUpdateIssueWatch set watching for a user and issue
2023
func CreateOrUpdateIssueWatch(userID, issueID int64, isWatching bool) error {
2124
iw, exists, err := getIssueWatch(x, userID, issueID)
@@ -58,11 +61,11 @@ func getIssueWatch(e Engine, userID, issueID int64) (iw *IssueWatch, exists bool
5861
}
5962

6063
// GetIssueWatchers returns watchers/unwatchers of a given issue
61-
func GetIssueWatchers(issueID int64) ([]*IssueWatch, error) {
64+
func GetIssueWatchers(issueID int64) (IssueWatchList, error) {
6265
return getIssueWatchers(x, issueID)
6366
}
6467

65-
func getIssueWatchers(e Engine, issueID int64) (watches []*IssueWatch, err error) {
68+
func getIssueWatchers(e Engine, issueID int64) (watches IssueWatchList, err error) {
6669
err = e.
6770
Where("`issue_watch`.issue_id = ?", issueID).
6871
And("`user`.is_active = ?", true).
@@ -83,3 +86,29 @@ func removeIssueWatchersByRepoID(e Engine, userID int64, repoID int64) error {
8386
Update(iw)
8487
return err
8588
}
89+
90+
// LoadWatchUsers return watching users
91+
func (iwl IssueWatchList) LoadWatchUsers() (users UserList, err error) {
92+
return iwl.loadWatchUsers(x)
93+
}
94+
95+
func (iwl IssueWatchList) loadWatchUsers(e Engine) (users UserList, err error) {
96+
if len(iwl) == 0 {
97+
return []*User{}, nil
98+
}
99+
100+
var userIDs = make([]int64, 0, len(iwl))
101+
for _, iw := range iwl {
102+
if iw.IsWatching {
103+
userIDs = append(userIDs, iw.UserID)
104+
}
105+
}
106+
107+
if len(userIDs) == 0 {
108+
return []*User{}, nil
109+
}
110+
111+
err = e.In("id", userIDs).Find(&users)
112+
113+
return
114+
}

models/userlist.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99

1010
"code.gitea.io/gitea/modules/log"
11+
api "code.gitea.io/gitea/modules/structs"
1112
)
1213

1314
//UserList is a list of user.
@@ -93,3 +94,12 @@ func (users UserList) loadTwoFactorStatus(e Engine) (map[int64]*TwoFactor, error
9394
}
9495
return tokenMaps, nil
9596
}
97+
98+
//APIFormat return list of users in api format
99+
func (users UserList) APIFormat() []*api.User {
100+
var result []*api.User
101+
for _, u := range users {
102+
result = append(result, u.APIFormat())
103+
}
104+
return result
105+
}

routers/api/v1/api.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,11 @@ func RegisterRoutes(m *macaron.Macaron) {
690690
m.Post("/start", reqToken(), repo.StartIssueStopwatch)
691691
m.Post("/stop", reqToken(), repo.StopIssueStopwatch)
692692
})
693+
m.Group("/subscriptions", func() {
694+
m.Get("", bind(api.User{}), repo.GetIssueSubscribers)
695+
m.Put("/:user", repo.AddIssueSubscription)
696+
m.Delete("/:user", repo.DelIssueSubscription)
697+
})
693698
})
694699
}, mustEnableIssuesOrPulls)
695700
m.Group("/labels", func() {
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
// Copyright 2019 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package repo
6+
7+
import (
8+
"code.gitea.io/gitea/models"
9+
"code.gitea.io/gitea/modules/context"
10+
api "code.gitea.io/gitea/modules/structs"
11+
)
12+
13+
// AddIssueSubscription Subscribe user to issue
14+
func AddIssueSubscription(ctx *context.APIContext) {
15+
// swagger:operation PUT /repos/{owner}/{repo}/issues/{index}/subscriptions/{user} issue issueAddSubscription
16+
// ---
17+
// summary: Subscribe user to issue
18+
// consumes:
19+
// - application/json
20+
// produces:
21+
// - application/json
22+
// parameters:
23+
// - name: owner
24+
// in: path
25+
// description: owner of the repo
26+
// type: string
27+
// required: true
28+
// - name: repo
29+
// in: path
30+
// description: name of the repo
31+
// type: string
32+
// required: true
33+
// - name: index
34+
// in: path
35+
// description: index of the issue
36+
// type: integer
37+
// format: int64
38+
// required: true
39+
// - name: user
40+
// in: path
41+
// description: user to subscribe
42+
// type: string
43+
// required: true
44+
// responses:
45+
// "201":
46+
// "$ref": "#/responses/empty"
47+
// "304":
48+
// description: User can only subscribe itself if he is no admin
49+
// "404":
50+
// description: Issue not found
51+
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
52+
if err != nil {
53+
if models.IsErrIssueNotExist(err) {
54+
ctx.NotFound()
55+
} else {
56+
ctx.Error(500, "GetIssueByIndex", err)
57+
}
58+
59+
return
60+
}
61+
62+
user, err := models.GetUserByName(ctx.Params(":user"))
63+
if err != nil {
64+
if models.IsErrUserNotExist(err) {
65+
ctx.NotFound()
66+
} else {
67+
ctx.Error(500, "GetUserByName", err)
68+
}
69+
70+
return
71+
}
72+
73+
//only admin and user for itself can change subscription
74+
if user.ID != ctx.User.ID && !ctx.User.IsAdmin {
75+
ctx.Error(403, "User", nil)
76+
return
77+
}
78+
79+
if err := models.CreateOrUpdateIssueWatch(user.ID, issue.ID, true); err != nil {
80+
ctx.Error(500, "CreateOrUpdateIssueWatch", err)
81+
return
82+
}
83+
84+
ctx.Status(201)
85+
}
86+
87+
// DelIssueSubscription Unsubscribe user from issue
88+
func DelIssueSubscription(ctx *context.APIContext) {
89+
// swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/subscriptions/{user} issue issueDeleteSubscription
90+
// ---
91+
// summary: Unsubscribe user from issue
92+
// consumes:
93+
// - application/json
94+
// produces:
95+
// - application/json
96+
// parameters:
97+
// - name: owner
98+
// in: path
99+
// description: owner of the repo
100+
// type: string
101+
// required: true
102+
// - name: repo
103+
// in: path
104+
// description: name of the repo
105+
// type: string
106+
// required: true
107+
// - name: index
108+
// in: path
109+
// description: index of the issue
110+
// type: integer
111+
// format: int64
112+
// required: true
113+
// - name: user
114+
// in: path
115+
// description: user witch unsubscribe
116+
// type: string
117+
// required: true
118+
// responses:
119+
// "201":
120+
// "$ref": "#/responses/empty"
121+
// "304":
122+
// description: User can only subscribe itself if he is no admin
123+
// "404":
124+
// description: Issue not found
125+
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
126+
if err != nil {
127+
if models.IsErrIssueNotExist(err) {
128+
ctx.NotFound()
129+
} else {
130+
ctx.Error(500, "GetIssueByIndex", err)
131+
}
132+
133+
return
134+
}
135+
136+
user, err := models.GetUserByName(ctx.Params(":user"))
137+
if err != nil {
138+
if models.IsErrUserNotExist(err) {
139+
ctx.NotFound()
140+
} else {
141+
ctx.Error(500, "GetUserByName", err)
142+
}
143+
144+
return
145+
}
146+
147+
//only admin and user for itself can change subscription
148+
if user.ID != ctx.User.ID && !ctx.User.IsAdmin {
149+
ctx.Error(403, "User", nil)
150+
return
151+
}
152+
153+
if err := models.CreateOrUpdateIssueWatch(user.ID, issue.ID, false); err != nil {
154+
ctx.Error(500, "CreateOrUpdateIssueWatch", err)
155+
return
156+
}
157+
158+
ctx.Status(201)
159+
}
160+
161+
// GetIssueSubscribers return subscribers of an issue
162+
func GetIssueSubscribers(ctx *context.APIContext, form api.User) {
163+
// swagger:operation GET /repos/{owner}/{repo}/issues/{index}/subscriptions issue issueSubscriptions
164+
// ---
165+
// summary: Get users who subscribed on an issue.
166+
// consumes:
167+
// - application/json
168+
// produces:
169+
// - application/json
170+
// parameters:
171+
// - name: owner
172+
// in: path
173+
// description: owner of the repo
174+
// type: string
175+
// required: true
176+
// - name: repo
177+
// in: path
178+
// description: name of the repo
179+
// type: string
180+
// required: true
181+
// - name: index
182+
// in: path
183+
// description: index of the issue
184+
// type: integer
185+
// format: int64
186+
// required: true
187+
// responses:
188+
// "201":
189+
// "$ref": "#/responses/empty"
190+
// "404":
191+
// description: Issue not found
192+
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
193+
if err != nil {
194+
if models.IsErrIssueNotExist(err) {
195+
ctx.NotFound()
196+
} else {
197+
ctx.Error(500, "GetIssueByIndex", err)
198+
}
199+
200+
return
201+
}
202+
203+
iwl, err := models.GetIssueWatchers(issue.ID)
204+
if err != nil {
205+
ctx.Error(500, "GetIssueWatchers", err)
206+
return
207+
}
208+
209+
users, err := iwl.LoadWatchUsers()
210+
if err != nil {
211+
ctx.Error(500, "LoadWatchUsers", err)
212+
return
213+
}
214+
215+
ctx.JSON(200, users.APIFormat())
216+
}

0 commit comments

Comments
 (0)