* 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 committags/v1.11.0-rc15eca929185
. * Update routers/api/v1/repo/issue.go Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * test go linter again * update swagger * GetIssueWatchers -> GetIssueSubscribers part one Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * GetIssueWatchers -> GetIssueSubscribers part two * Revert "test go linter again" This reverts commitbab1235622
. * 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
- path: routers/routes/routes.go | - path: routers/routes/routes.go | ||||
linters: | linters: | ||||
- dupl | - dupl | ||||
- path: routers/api/v1/repo/issue_subscription.go | |||||
linters: | |||||
- dupl | |||||
- path: routers/repo/view.go | - path: routers/repo/view.go | ||||
linters: | linters: | ||||
- dupl | - dupl |
export BINARY="golangci-lint"; \ | export BINARY="golangci-lint"; \ | ||||
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.20.0; \ | curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.20.0; \ | ||||
fi | fi | ||||
golangci-lint run | |||||
golangci-lint run --timeout 5m |
UpdatedUnix timeutil.TimeStamp `xorm:"updated NOT NULL"` | UpdatedUnix timeutil.TimeStamp `xorm:"updated NOT NULL"` | ||||
} | } | ||||
// IssueWatchList contains IssueWatch | |||||
type IssueWatchList []*IssueWatch | |||||
// CreateOrUpdateIssueWatch set watching for a user and issue | // CreateOrUpdateIssueWatch set watching for a user and issue | ||||
func CreateOrUpdateIssueWatch(userID, issueID int64, isWatching bool) error { | func CreateOrUpdateIssueWatch(userID, issueID int64, isWatching bool) error { | ||||
iw, exists, err := getIssueWatch(x, userID, issueID) | iw, exists, err := getIssueWatch(x, userID, issueID) | ||||
} | } | ||||
// GetIssueWatchers returns watchers/unwatchers of a given issue | // GetIssueWatchers returns watchers/unwatchers of a given issue | ||||
func GetIssueWatchers(issueID int64) ([]*IssueWatch, error) { | |||||
func GetIssueWatchers(issueID int64) (IssueWatchList, error) { | |||||
return getIssueWatchers(x, issueID) | return getIssueWatchers(x, issueID) | ||||
} | } | ||||
func getIssueWatchers(e Engine, issueID int64) (watches []*IssueWatch, err error) { | |||||
func getIssueWatchers(e Engine, issueID int64) (watches IssueWatchList, err error) { | |||||
err = e. | err = e. | ||||
Where("`issue_watch`.issue_id = ?", issueID). | Where("`issue_watch`.issue_id = ?", issueID). | ||||
And("`user`.is_active = ?", true). | And("`user`.is_active = ?", true). | ||||
Update(iw) | Update(iw) | ||||
return err | return err | ||||
} | } | ||||
// LoadWatchUsers return watching users | |||||
func (iwl IssueWatchList) LoadWatchUsers() (users UserList, err error) { | |||||
return iwl.loadWatchUsers(x) | |||||
} | |||||
func (iwl IssueWatchList) loadWatchUsers(e Engine) (users UserList, err error) { | |||||
if len(iwl) == 0 { | |||||
return []*User{}, nil | |||||
} | |||||
var userIDs = make([]int64, 0, len(iwl)) | |||||
for _, iw := range iwl { | |||||
if iw.IsWatching { | |||||
userIDs = append(userIDs, iw.UserID) | |||||
} | |||||
} | |||||
if len(userIDs) == 0 { | |||||
return []*User{}, nil | |||||
} | |||||
err = e.In("id", userIDs).Find(&users) | |||||
return | |||||
} |
"fmt" | "fmt" | ||||
"code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
api "code.gitea.io/gitea/modules/structs" | |||||
) | ) | ||||
//UserList is a list of user. | //UserList is a list of user. | ||||
} | } | ||||
return tokenMaps, nil | return tokenMaps, nil | ||||
} | } | ||||
//APIFormat return list of users in api format | |||||
func (users UserList) APIFormat() []*api.User { | |||||
var result []*api.User | |||||
for _, u := range users { | |||||
result = append(result, u.APIFormat()) | |||||
} | |||||
return result | |||||
} |
m.Post("/start", reqToken(), repo.StartIssueStopwatch) | m.Post("/start", reqToken(), repo.StartIssueStopwatch) | ||||
m.Post("/stop", reqToken(), repo.StopIssueStopwatch) | m.Post("/stop", reqToken(), repo.StopIssueStopwatch) | ||||
}) | }) | ||||
m.Group("/subscriptions", func() { | |||||
m.Get("", bind(api.User{}), repo.GetIssueSubscribers) | |||||
m.Put("/:user", repo.AddIssueSubscription) | |||||
m.Delete("/:user", repo.DelIssueSubscription) | |||||
}) | |||||
}) | }) | ||||
}, mustEnableIssuesOrPulls) | }, mustEnableIssuesOrPulls) | ||||
m.Group("/labels", func() { | m.Group("/labels", func() { |
// Copyright 2019 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 repo | |||||
import ( | |||||
"code.gitea.io/gitea/models" | |||||
"code.gitea.io/gitea/modules/context" | |||||
api "code.gitea.io/gitea/modules/structs" | |||||
) | |||||
// AddIssueSubscription Subscribe user to issue | |||||
func AddIssueSubscription(ctx *context.APIContext) { | |||||
// swagger:operation PUT /repos/{owner}/{repo}/issues/{index}/subscriptions/{user} issue issueAddSubscription | |||||
// --- | |||||
// summary: Subscribe user to 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 | |||||
// - name: user | |||||
// in: path | |||||
// description: user to subscribe | |||||
// type: string | |||||
// required: true | |||||
// responses: | |||||
// "201": | |||||
// "$ref": "#/responses/empty" | |||||
// "304": | |||||
// description: User can only subscribe itself if he is no admin | |||||
// "404": | |||||
// description: Issue not found | |||||
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) | |||||
if err != nil { | |||||
if models.IsErrIssueNotExist(err) { | |||||
ctx.NotFound() | |||||
} else { | |||||
ctx.Error(500, "GetIssueByIndex", err) | |||||
} | |||||
return | |||||
} | |||||
user, err := models.GetUserByName(ctx.Params(":user")) | |||||
if err != nil { | |||||
if models.IsErrUserNotExist(err) { | |||||
ctx.NotFound() | |||||
} else { | |||||
ctx.Error(500, "GetUserByName", err) | |||||
} | |||||
return | |||||
} | |||||
//only admin and user for itself can change subscription | |||||
if user.ID != ctx.User.ID && !ctx.User.IsAdmin { | |||||
ctx.Error(403, "User", nil) | |||||
return | |||||
} | |||||
if err := models.CreateOrUpdateIssueWatch(user.ID, issue.ID, true); err != nil { | |||||
ctx.Error(500, "CreateOrUpdateIssueWatch", err) | |||||
return | |||||
} | |||||
ctx.Status(201) | |||||
} | |||||
// DelIssueSubscription Unsubscribe user from issue | |||||
func DelIssueSubscription(ctx *context.APIContext) { | |||||
// swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/subscriptions/{user} issue issueDeleteSubscription | |||||
// --- | |||||
// summary: Unsubscribe user from 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 | |||||
// - name: user | |||||
// in: path | |||||
// description: user witch unsubscribe | |||||
// type: string | |||||
// required: true | |||||
// responses: | |||||
// "201": | |||||
// "$ref": "#/responses/empty" | |||||
// "304": | |||||
// description: User can only subscribe itself if he is no admin | |||||
// "404": | |||||
// description: Issue not found | |||||
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) | |||||
if err != nil { | |||||
if models.IsErrIssueNotExist(err) { | |||||
ctx.NotFound() | |||||
} else { | |||||
ctx.Error(500, "GetIssueByIndex", err) | |||||
} | |||||
return | |||||
} | |||||
user, err := models.GetUserByName(ctx.Params(":user")) | |||||
if err != nil { | |||||
if models.IsErrUserNotExist(err) { | |||||
ctx.NotFound() | |||||
} else { | |||||
ctx.Error(500, "GetUserByName", err) | |||||
} | |||||
return | |||||
} | |||||
//only admin and user for itself can change subscription | |||||
if user.ID != ctx.User.ID && !ctx.User.IsAdmin { | |||||
ctx.Error(403, "User", nil) | |||||
return | |||||
} | |||||
if err := models.CreateOrUpdateIssueWatch(user.ID, issue.ID, false); err != nil { | |||||
ctx.Error(500, "CreateOrUpdateIssueWatch", err) | |||||
return | |||||
} | |||||
ctx.Status(201) | |||||
} | |||||
// GetIssueSubscribers return subscribers of an issue | |||||
func GetIssueSubscribers(ctx *context.APIContext, form api.User) { | |||||
// swagger:operation GET /repos/{owner}/{repo}/issues/{index}/subscriptions issue issueSubscriptions | |||||
// --- | |||||
// summary: Get users who subscribed on 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: | |||||
// "201": | |||||
// "$ref": "#/responses/empty" | |||||
// "404": | |||||
// description: Issue not found | |||||
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) | |||||
if err != nil { | |||||
if models.IsErrIssueNotExist(err) { | |||||
ctx.NotFound() | |||||
} else { | |||||
ctx.Error(500, "GetIssueByIndex", err) | |||||
} | |||||
return | |||||
} | |||||
iwl, err := models.GetIssueWatchers(issue.ID) | |||||
if err != nil { | |||||
ctx.Error(500, "GetIssueWatchers", err) | |||||
return | |||||
} | |||||
users, err := iwl.LoadWatchUsers() | |||||
if err != nil { | |||||
ctx.Error(500, "LoadWatchUsers", err) | |||||
return | |||||
} | |||||
ctx.JSON(200, users.APIFormat()) | |||||
} |
} | } | ||||
} | } | ||||
}, | }, | ||||
"/repos/{owner}/{repo}/issues/{index}/subscriptions": { | |||||
"get": { | |||||
"consumes": [ | |||||
"application/json" | |||||
], | |||||
"produces": [ | |||||
"application/json" | |||||
], | |||||
"tags": [ | |||||
"issue" | |||||
], | |||||
"summary": "Get users who subscribed on an issue.", | |||||
"operationId": "issueSubscriptions", | |||||
"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": { | |||||
"201": { | |||||
"$ref": "#/responses/empty" | |||||
}, | |||||
"404": { | |||||
"description": "Issue not found" | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"/repos/{owner}/{repo}/issues/{index}/subscriptions/{user}": { | |||||
"put": { | |||||
"consumes": [ | |||||
"application/json" | |||||
], | |||||
"produces": [ | |||||
"application/json" | |||||
], | |||||
"tags": [ | |||||
"issue" | |||||
], | |||||
"summary": "Subscribe user to issue", | |||||
"operationId": "issueAddSubscription", | |||||
"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 | |||||
}, | |||||
{ | |||||
"type": "string", | |||||
"description": "user to subscribe", | |||||
"name": "user", | |||||
"in": "path", | |||||
"required": true | |||||
} | |||||
], | |||||
"responses": { | |||||
"201": { | |||||
"$ref": "#/responses/empty" | |||||
}, | |||||
"304": { | |||||
"description": "User can only subscribe itself if he is no admin" | |||||
}, | |||||
"404": { | |||||
"description": "Issue not found" | |||||
} | |||||
} | |||||
}, | |||||
"delete": { | |||||
"consumes": [ | |||||
"application/json" | |||||
], | |||||
"produces": [ | |||||
"application/json" | |||||
], | |||||
"tags": [ | |||||
"issue" | |||||
], | |||||
"summary": "Unsubscribe user from issue", | |||||
"operationId": "issueDeleteSubscription", | |||||
"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 | |||||
}, | |||||
{ | |||||
"type": "string", | |||||
"description": "user witch unsubscribe", | |||||
"name": "user", | |||||
"in": "path", | |||||
"required": true | |||||
} | |||||
], | |||||
"responses": { | |||||
"201": { | |||||
"$ref": "#/responses/empty" | |||||
}, | |||||
"304": { | |||||
"description": "User can only subscribe itself if he is no admin" | |||||
}, | |||||
"404": { | |||||
"description": "Issue not found" | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"/repos/{owner}/{repo}/keys": { | "/repos/{owner}/{repo}/keys": { | ||||
"get": { | "get": { | ||||
"produces": [ | "produces": [ |