[API] extend StopWatch (#9196)
* squash api-stopwatch * fix prepair logic! + add Tests * fix lint * more robust time compare * delete responce 202 -> 204 * change http responce in test too
This commit is contained in:
		
							parent
							
								
									382936a668
								
							
						
					
					
						commit
						aceb1085c7
					
				
					 9 changed files with 482 additions and 141 deletions
				
			
		
							
								
								
									
										83
									
								
								integrations/api_issue_stopwatch_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								integrations/api_issue_stopwatch_test.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,83 @@ | ||||||
|  | // 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 integrations | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"code.gitea.io/gitea/models" | ||||||
|  | 	api "code.gitea.io/gitea/modules/structs" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestAPIListStopWatches(t *testing.T) { | ||||||
|  | 	defer prepareTestEnv(t)() | ||||||
|  | 
 | ||||||
|  | 	repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) | ||||||
|  | 	owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User) | ||||||
|  | 
 | ||||||
|  | 	session := loginUser(t, owner.Name) | ||||||
|  | 	token := getTokenForLoggedInUser(t, session) | ||||||
|  | 	req := NewRequestf(t, "GET", "/api/v1/user/stopwatches?token=%s", token) | ||||||
|  | 	resp := session.MakeRequest(t, req, http.StatusOK) | ||||||
|  | 	var apiWatches []*api.StopWatch | ||||||
|  | 	DecodeJSON(t, resp, &apiWatches) | ||||||
|  | 	expect := models.AssertExistsAndLoadBean(t, &models.Stopwatch{UserID: owner.ID}).(*models.Stopwatch) | ||||||
|  | 	expectAPI, _ := expect.APIFormat() | ||||||
|  | 	assert.Len(t, apiWatches, 1) | ||||||
|  | 
 | ||||||
|  | 	assert.EqualValues(t, expectAPI.IssueIndex, apiWatches[0].IssueIndex) | ||||||
|  | 	assert.EqualValues(t, expectAPI.Created.Unix(), apiWatches[0].Created.Unix()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestAPIStopStopWatches(t *testing.T) { | ||||||
|  | 	defer prepareTestEnv(t)() | ||||||
|  | 
 | ||||||
|  | 	issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue) | ||||||
|  | 	_ = issue.LoadRepo() | ||||||
|  | 	owner := models.AssertExistsAndLoadBean(t, &models.User{ID: issue.Repo.OwnerID}).(*models.User) | ||||||
|  | 	user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) | ||||||
|  | 
 | ||||||
|  | 	session := loginUser(t, user.Name) | ||||||
|  | 	token := getTokenForLoggedInUser(t, session) | ||||||
|  | 
 | ||||||
|  | 	req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/issues/%d/stopwatch/stop?token=%s", owner.Name, issue.Repo.Name, issue.Index, token) | ||||||
|  | 	session.MakeRequest(t, req, http.StatusCreated) | ||||||
|  | 	session.MakeRequest(t, req, http.StatusConflict) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestAPICancelStopWatches(t *testing.T) { | ||||||
|  | 	defer prepareTestEnv(t)() | ||||||
|  | 
 | ||||||
|  | 	issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 1}).(*models.Issue) | ||||||
|  | 	_ = issue.LoadRepo() | ||||||
|  | 	owner := models.AssertExistsAndLoadBean(t, &models.User{ID: issue.Repo.OwnerID}).(*models.User) | ||||||
|  | 	user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) | ||||||
|  | 
 | ||||||
|  | 	session := loginUser(t, user.Name) | ||||||
|  | 	token := getTokenForLoggedInUser(t, session) | ||||||
|  | 
 | ||||||
|  | 	req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/%d/stopwatch/delete?token=%s", owner.Name, issue.Repo.Name, issue.Index, token) | ||||||
|  | 	session.MakeRequest(t, req, http.StatusNoContent) | ||||||
|  | 	session.MakeRequest(t, req, http.StatusConflict) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestAPIStartStopWatches(t *testing.T) { | ||||||
|  | 	defer prepareTestEnv(t)() | ||||||
|  | 
 | ||||||
|  | 	issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 3}).(*models.Issue) | ||||||
|  | 	_ = issue.LoadRepo() | ||||||
|  | 	owner := models.AssertExistsAndLoadBean(t, &models.User{ID: issue.Repo.OwnerID}).(*models.User) | ||||||
|  | 	user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) | ||||||
|  | 
 | ||||||
|  | 	session := loginUser(t, user.Name) | ||||||
|  | 	token := getTokenForLoggedInUser(t, session) | ||||||
|  | 
 | ||||||
|  | 	req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/issues/%d/stopwatch/start?token=%s", owner.Name, issue.Repo.Name, issue.Index, token) | ||||||
|  | 	session.MakeRequest(t, req, http.StatusCreated) | ||||||
|  | 	session.MakeRequest(t, req, http.StatusConflict) | ||||||
|  | } | ||||||
|  | @ -2,10 +2,10 @@ | ||||||
|   id: 1 |   id: 1 | ||||||
|   user_id: 1 |   user_id: 1 | ||||||
|   issue_id: 1 |   issue_id: 1 | ||||||
|   created_unix: 1500988502 |   created_unix: 1500988001 | ||||||
| 
 | 
 | ||||||
| - | - | ||||||
|   id: 2 |   id: 2 | ||||||
|   user_id: 2 |   user_id: 2 | ||||||
|   issue_id: 2 |   issue_id: 2 | ||||||
|   created_unix: 1500988502 |   created_unix: 1500988002 | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | 	api "code.gitea.io/gitea/modules/structs" | ||||||
| 	"code.gitea.io/gitea/modules/timeutil" | 	"code.gitea.io/gitea/modules/timeutil" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -19,6 +20,9 @@ type Stopwatch struct { | ||||||
| 	CreatedUnix timeutil.TimeStamp `xorm:"created"` | 	CreatedUnix timeutil.TimeStamp `xorm:"created"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Stopwatches is a List ful of Stopwatch
 | ||||||
|  | type Stopwatches []Stopwatch | ||||||
|  | 
 | ||||||
| func getStopwatch(e Engine, userID, issueID int64) (sw *Stopwatch, exists bool, err error) { | func getStopwatch(e Engine, userID, issueID int64) (sw *Stopwatch, exists bool, err error) { | ||||||
| 	sw = new(Stopwatch) | 	sw = new(Stopwatch) | ||||||
| 	exists, err = e. | 	exists, err = e. | ||||||
|  | @ -28,6 +32,16 @@ func getStopwatch(e Engine, userID, issueID int64) (sw *Stopwatch, exists bool, | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // GetUserStopwatches return list of all stopwatches of a user
 | ||||||
|  | func GetUserStopwatches(userID int64) (sws *Stopwatches, err error) { | ||||||
|  | 	sws = new(Stopwatches) | ||||||
|  | 	err = x.Where("stopwatch.user_id = ?", userID).Find(sws) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return sws, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // StopwatchExists returns true if the stopwatch exists
 | // StopwatchExists returns true if the stopwatch exists
 | ||||||
| func StopwatchExists(userID int64, issueID int64) bool { | func StopwatchExists(userID int64, issueID int64) bool { | ||||||
| 	_, exists, _ := getStopwatch(x, userID, issueID) | 	_, exists, _ := getStopwatch(x, userID, issueID) | ||||||
|  | @ -160,3 +174,28 @@ func SecToTime(duration int64) string { | ||||||
| 
 | 
 | ||||||
| 	return hrs | 	return hrs | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // APIFormat convert Stopwatch type to api.StopWatch type
 | ||||||
|  | func (sw *Stopwatch) APIFormat() (api.StopWatch, error) { | ||||||
|  | 	issue, err := getIssueByID(x, sw.IssueID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return api.StopWatch{}, err | ||||||
|  | 	} | ||||||
|  | 	return api.StopWatch{ | ||||||
|  | 		Created:    sw.CreatedUnix.AsTime(), | ||||||
|  | 		IssueIndex: issue.Index, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // APIFormat convert Stopwatches type to api.StopWatches type
 | ||||||
|  | func (sws Stopwatches) APIFormat() (api.StopWatches, error) { | ||||||
|  | 	result := api.StopWatches(make([]api.StopWatch, 0, len(sws))) | ||||||
|  | 	for _, sw := range sws { | ||||||
|  | 		apiSW, err := sw.APIFormat() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		result = append(result, apiSW) | ||||||
|  | 	} | ||||||
|  | 	return result, nil | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										19
									
								
								modules/structs/issue_stopwatch.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								modules/structs/issue_stopwatch.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | ||||||
|  | // 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 structs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // StopWatch represent a running stopwatch
 | ||||||
|  | type StopWatch struct { | ||||||
|  | 	// swagger:strfmt date-time
 | ||||||
|  | 	Created    time.Time `json:"created"` | ||||||
|  | 	IssueIndex int64     `json:"issue_index"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // StopWatches represent a list of stopwatches
 | ||||||
|  | type StopWatches []StopWatch | ||||||
|  | @ -584,6 +584,8 @@ func RegisterRoutes(m *macaron.Macaron) { | ||||||
| 			}) | 			}) | ||||||
| 			m.Get("/times", repo.ListMyTrackedTimes) | 			m.Get("/times", repo.ListMyTrackedTimes) | ||||||
| 
 | 
 | ||||||
|  | 			m.Get("/stopwatches", repo.GetStopwatches) | ||||||
|  | 
 | ||||||
| 			m.Get("/subscriptions", user.GetMyWatchedRepos) | 			m.Get("/subscriptions", user.GetMyWatchedRepos) | ||||||
| 
 | 
 | ||||||
| 			m.Get("/teams", org.ListUserTeams) | 			m.Get("/teams", org.ListUserTeams) | ||||||
|  | @ -691,6 +693,7 @@ func RegisterRoutes(m *macaron.Macaron) { | ||||||
| 						m.Group("/stopwatch", func() { | 						m.Group("/stopwatch", func() { | ||||||
| 							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.Delete("/delete", reqToken(), repo.DeleteIssueStopwatch) | ||||||
| 						}) | 						}) | ||||||
| 						m.Group("/subscriptions", func() { | 						m.Group("/subscriptions", func() { | ||||||
| 							m.Get("", repo.GetIssueSubscribers) | 							m.Get("", repo.GetIssueSubscribers) | ||||||
|  |  | ||||||
|  | @ -598,141 +598,3 @@ func UpdateIssueDeadline(ctx *context.APIContext, form api.EditDeadlineOption) { | ||||||
| 
 | 
 | ||||||
| 	ctx.JSON(201, api.IssueDeadline{Deadline: &deadline}) | 	ctx.JSON(201, api.IssueDeadline{Deadline: &deadline}) | ||||||
| } | } | ||||||
| 
 |  | ||||||
| // StartIssueStopwatch creates a stopwatch for the given issue.
 |  | ||||||
| func StartIssueStopwatch(ctx *context.APIContext) { |  | ||||||
| 	// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/stopwatch/start issue issueStartStopWatch
 |  | ||||||
| 	// ---
 |  | ||||||
| 	// summary: Start stopwatch 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 to create the stopwatch on
 |  | ||||||
| 	//   type: integer
 |  | ||||||
| 	//   format: int64
 |  | ||||||
| 	//   required: true
 |  | ||||||
| 	// responses:
 |  | ||||||
| 	//   "201":
 |  | ||||||
| 	//     "$ref": "#/responses/empty"
 |  | ||||||
| 	//   "403":
 |  | ||||||
| 	//     description: Not repo writer, user does not have rights to toggle stopwatch
 |  | ||||||
| 	//   "404":
 |  | ||||||
| 	//     description: Issue not found
 |  | ||||||
| 	//   "409":
 |  | ||||||
| 	//     description: Cannot start a stopwatch again if it already exists
 |  | ||||||
| 	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 |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if !ctx.Repo.CanWrite(models.UnitTypeIssues) { |  | ||||||
| 		ctx.Status(403) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if !ctx.Repo.CanUseTimetracker(issue, ctx.User) { |  | ||||||
| 		ctx.Status(403) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if models.StopwatchExists(ctx.User.ID, issue.ID) { |  | ||||||
| 		ctx.Error(409, "StopwatchExists", "a stopwatch has already been started for this issue") |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err := models.CreateOrStopIssueStopwatch(ctx.User, issue); err != nil { |  | ||||||
| 		ctx.Error(500, "CreateOrStopIssueStopwatch", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	ctx.Status(201) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // StopIssueStopwatch stops a stopwatch for the given issue.
 |  | ||||||
| func StopIssueStopwatch(ctx *context.APIContext) { |  | ||||||
| 	// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/stopwatch/stop issue issueStopWatch
 |  | ||||||
| 	// ---
 |  | ||||||
| 	// summary: Stop an issue's existing stopwatch.
 |  | ||||||
| 	// 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 to stop the stopwatch on
 |  | ||||||
| 	//   type: integer
 |  | ||||||
| 	//   format: int64
 |  | ||||||
| 	//   required: true
 |  | ||||||
| 	// responses:
 |  | ||||||
| 	//   "201":
 |  | ||||||
| 	//     "$ref": "#/responses/empty"
 |  | ||||||
| 	//   "403":
 |  | ||||||
| 	//     description: Not repo writer, user does not have rights to toggle stopwatch
 |  | ||||||
| 	//   "404":
 |  | ||||||
| 	//     description: Issue not found
 |  | ||||||
| 	//   "409":
 |  | ||||||
| 	//     description:  Cannot stop a non existent stopwatch
 |  | ||||||
| 	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 |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if !ctx.Repo.CanWrite(models.UnitTypeIssues) { |  | ||||||
| 		ctx.Status(403) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if !ctx.Repo.CanUseTimetracker(issue, ctx.User) { |  | ||||||
| 		ctx.Status(403) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if !models.StopwatchExists(ctx.User.ID, issue.ID) { |  | ||||||
| 		ctx.Error(409, "StopwatchExists", "cannot stop a non existent stopwatch") |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err := models.CreateOrStopIssueStopwatch(ctx.User, issue); err != nil { |  | ||||||
| 		ctx.Error(500, "CreateOrStopIssueStopwatch", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	ctx.Status(201) |  | ||||||
| } |  | ||||||
|  |  | ||||||
							
								
								
									
										216
									
								
								routers/api/v1/repo/issue_stopwatch.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								routers/api/v1/repo/issue_stopwatch.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,216 @@ | ||||||
|  | // 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" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // StartIssueStopwatch creates a stopwatch for the given issue.
 | ||||||
|  | func StartIssueStopwatch(ctx *context.APIContext) { | ||||||
|  | 	// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/stopwatch/start issue issueStartStopWatch
 | ||||||
|  | 	// ---
 | ||||||
|  | 	// summary: Start stopwatch 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 to create the stopwatch on
 | ||||||
|  | 	//   type: integer
 | ||||||
|  | 	//   format: int64
 | ||||||
|  | 	//   required: true
 | ||||||
|  | 	// responses:
 | ||||||
|  | 	//   "201":
 | ||||||
|  | 	//     "$ref": "#/responses/empty"
 | ||||||
|  | 	//   "403":
 | ||||||
|  | 	//     description: Not repo writer, user does not have rights to toggle stopwatch
 | ||||||
|  | 	//   "404":
 | ||||||
|  | 	//     description: Issue not found
 | ||||||
|  | 	//   "409":
 | ||||||
|  | 	//     description: Cannot start a stopwatch again if it already exists
 | ||||||
|  | 	issue, err := prepareIssueStopwatch(ctx, false) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := models.CreateOrStopIssueStopwatch(ctx.User, issue); err != nil { | ||||||
|  | 		ctx.Error(500, "CreateOrStopIssueStopwatch", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ctx.Status(201) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // StopIssueStopwatch stops a stopwatch for the given issue.
 | ||||||
|  | func StopIssueStopwatch(ctx *context.APIContext) { | ||||||
|  | 	// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/stopwatch/stop issue issueStopStopWatch
 | ||||||
|  | 	// ---
 | ||||||
|  | 	// summary: Stop an issue's existing stopwatch.
 | ||||||
|  | 	// 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 to stop the stopwatch on
 | ||||||
|  | 	//   type: integer
 | ||||||
|  | 	//   format: int64
 | ||||||
|  | 	//   required: true
 | ||||||
|  | 	// responses:
 | ||||||
|  | 	//   "201":
 | ||||||
|  | 	//     "$ref": "#/responses/empty"
 | ||||||
|  | 	//   "403":
 | ||||||
|  | 	//     description: Not repo writer, user does not have rights to toggle stopwatch
 | ||||||
|  | 	//   "404":
 | ||||||
|  | 	//     description: Issue not found
 | ||||||
|  | 	//   "409":
 | ||||||
|  | 	//     description:  Cannot stop a non existent stopwatch
 | ||||||
|  | 	issue, err := prepareIssueStopwatch(ctx, true) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := models.CreateOrStopIssueStopwatch(ctx.User, issue); err != nil { | ||||||
|  | 		ctx.Error(500, "CreateOrStopIssueStopwatch", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ctx.Status(201) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DeleteIssueStopwatch delete a specific stopwatch
 | ||||||
|  | func DeleteIssueStopwatch(ctx *context.APIContext) { | ||||||
|  | 	// swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/stopwatch/delete issue issueDeleteStopWatch
 | ||||||
|  | 	// ---
 | ||||||
|  | 	// summary: Delete an issue's existing stopwatch.
 | ||||||
|  | 	// 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 to stop the stopwatch on
 | ||||||
|  | 	//   type: integer
 | ||||||
|  | 	//   format: int64
 | ||||||
|  | 	//   required: true
 | ||||||
|  | 	// responses:
 | ||||||
|  | 	//   "204":
 | ||||||
|  | 	//     "$ref": "#/responses/empty"
 | ||||||
|  | 	//   "403":
 | ||||||
|  | 	//     description: Not repo writer, user does not have rights to toggle stopwatch
 | ||||||
|  | 	//   "404":
 | ||||||
|  | 	//     description: Issue not found
 | ||||||
|  | 	//   "409":
 | ||||||
|  | 	//     description:  Cannot cancel a non existent stopwatch
 | ||||||
|  | 	issue, err := prepareIssueStopwatch(ctx, true) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := models.CancelStopwatch(ctx.User, issue); err != nil { | ||||||
|  | 		ctx.Error(500, "CancelStopwatch", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ctx.Status(204) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func prepareIssueStopwatch(ctx *context.APIContext, shouldExist bool) (*models.Issue, error) { | ||||||
|  | 	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 nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !ctx.Repo.CanWrite(models.UnitTypeIssues) { | ||||||
|  | 		ctx.Status(403) | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !ctx.Repo.CanUseTimetracker(issue, ctx.User) { | ||||||
|  | 		ctx.Status(403) | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if models.StopwatchExists(ctx.User.ID, issue.ID) != shouldExist { | ||||||
|  | 		if shouldExist { | ||||||
|  | 			ctx.Error(409, "StopwatchExists", "cannot stop/cancel a non existent stopwatch") | ||||||
|  | 		} else { | ||||||
|  | 			ctx.Error(409, "StopwatchExists", "cannot start a stopwatch again if it already exists") | ||||||
|  | 		} | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return issue, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetStopwatches get all stopwatches
 | ||||||
|  | func GetStopwatches(ctx *context.APIContext) { | ||||||
|  | 	// swagger:operation GET /user/stopwatches user userGetStopWatches
 | ||||||
|  | 	// ---
 | ||||||
|  | 	// summary: Get list of all existing stopwatches
 | ||||||
|  | 	// consumes:
 | ||||||
|  | 	// - application/json
 | ||||||
|  | 	// produces:
 | ||||||
|  | 	// - application/json
 | ||||||
|  | 	// responses:
 | ||||||
|  | 	//   "200":
 | ||||||
|  | 	//     "$ref": "#/responses/StopWatchList"
 | ||||||
|  | 
 | ||||||
|  | 	sws, err := models.GetUserStopwatches(ctx.User.ID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.Error(500, "GetUserStopwatches", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	apiSWs, err := sws.APIFormat() | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.Error(500, "APIFormat", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ctx.JSON(200, apiSWs) | ||||||
|  | } | ||||||
|  | @ -85,6 +85,20 @@ type swaggerIssueDeadline struct { | ||||||
| 	Body api.IssueDeadline `json:"body"` | 	Body api.IssueDeadline `json:"body"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // StopWatch
 | ||||||
|  | // swagger:response StopWatch
 | ||||||
|  | type swaggerResponseStopWatch struct { | ||||||
|  | 	// in:body
 | ||||||
|  | 	Body api.StopWatch `json:"body"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // StopWatchList
 | ||||||
|  | // swagger:response StopWatchList
 | ||||||
|  | type swaggerResponseStopWatchList struct { | ||||||
|  | 	// in:body
 | ||||||
|  | 	Body []api.StopWatch `json:"body"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // EditReactionOption
 | // EditReactionOption
 | ||||||
| // swagger:response EditReactionOption
 | // swagger:response EditReactionOption
 | ||||||
| type swaggerEditReactionOption struct { | type swaggerEditReactionOption struct { | ||||||
|  |  | ||||||
|  | @ -3972,6 +3972,59 @@ | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "/repos/{owner}/{repo}/issues/{index}/stopwatch/delete": { | ||||||
|  |       "delete": { | ||||||
|  |         "consumes": [ | ||||||
|  |           "application/json" | ||||||
|  |         ], | ||||||
|  |         "produces": [ | ||||||
|  |           "application/json" | ||||||
|  |         ], | ||||||
|  |         "tags": [ | ||||||
|  |           "issue" | ||||||
|  |         ], | ||||||
|  |         "summary": "Delete an issue's existing stopwatch.", | ||||||
|  |         "operationId": "issueDeleteStopWatch", | ||||||
|  |         "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 to stop the stopwatch on", | ||||||
|  |             "name": "index", | ||||||
|  |             "in": "path", | ||||||
|  |             "required": true | ||||||
|  |           } | ||||||
|  |         ], | ||||||
|  |         "responses": { | ||||||
|  |           "204": { | ||||||
|  |             "$ref": "#/responses/empty" | ||||||
|  |           }, | ||||||
|  |           "403": { | ||||||
|  |             "description": "Not repo writer, user does not have rights to toggle stopwatch" | ||||||
|  |           }, | ||||||
|  |           "404": { | ||||||
|  |             "description": "Issue not found" | ||||||
|  |           }, | ||||||
|  |           "409": { | ||||||
|  |             "description": "Cannot cancel a non existent stopwatch" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "/repos/{owner}/{repo}/issues/{index}/stopwatch/start": { |     "/repos/{owner}/{repo}/issues/{index}/stopwatch/start": { | ||||||
|       "post": { |       "post": { | ||||||
|         "consumes": [ |         "consumes": [ | ||||||
|  | @ -4037,7 +4090,7 @@ | ||||||
|           "issue" |           "issue" | ||||||
|         ], |         ], | ||||||
|         "summary": "Stop an issue's existing stopwatch.", |         "summary": "Stop an issue's existing stopwatch.", | ||||||
|         "operationId": "issueStopWatch", |         "operationId": "issueStopStopWatch", | ||||||
|         "parameters": [ |         "parameters": [ | ||||||
|           { |           { | ||||||
|             "type": "string", |             "type": "string", | ||||||
|  | @ -7174,6 +7227,26 @@ | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "/user/stopwatches": { | ||||||
|  |       "get": { | ||||||
|  |         "consumes": [ | ||||||
|  |           "application/json" | ||||||
|  |         ], | ||||||
|  |         "produces": [ | ||||||
|  |           "application/json" | ||||||
|  |         ], | ||||||
|  |         "tags": [ | ||||||
|  |           "user" | ||||||
|  |         ], | ||||||
|  |         "summary": "Get list of all existing stopwatches", | ||||||
|  |         "operationId": "userGetStopWatches", | ||||||
|  |         "responses": { | ||||||
|  |           "200": { | ||||||
|  |             "$ref": "#/responses/StopWatchList" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "/user/subscriptions": { |     "/user/subscriptions": { | ||||||
|       "get": { |       "get": { | ||||||
|         "produces": [ |         "produces": [ | ||||||
|  | @ -10808,6 +10881,23 @@ | ||||||
|       "type": "string", |       "type": "string", | ||||||
|       "x-go-package": "code.gitea.io/gitea/modules/structs" |       "x-go-package": "code.gitea.io/gitea/modules/structs" | ||||||
|     }, |     }, | ||||||
|  |     "StopWatch": { | ||||||
|  |       "description": "StopWatch represent a running stopwatch", | ||||||
|  |       "type": "object", | ||||||
|  |       "properties": { | ||||||
|  |         "created": { | ||||||
|  |           "type": "string", | ||||||
|  |           "format": "date-time", | ||||||
|  |           "x-go-name": "Created" | ||||||
|  |         }, | ||||||
|  |         "issue_index": { | ||||||
|  |           "type": "integer", | ||||||
|  |           "format": "int64", | ||||||
|  |           "x-go-name": "IssueIndex" | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       "x-go-package": "code.gitea.io/gitea/modules/structs" | ||||||
|  |     }, | ||||||
|     "Tag": { |     "Tag": { | ||||||
|       "description": "Tag represents a repository tag", |       "description": "Tag represents a repository tag", | ||||||
|       "type": "object", |       "type": "object", | ||||||
|  | @ -11553,6 +11643,21 @@ | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "StopWatch": { | ||||||
|  |       "description": "StopWatch", | ||||||
|  |       "schema": { | ||||||
|  |         "$ref": "#/definitions/StopWatch" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "StopWatchList": { | ||||||
|  |       "description": "StopWatchList", | ||||||
|  |       "schema": { | ||||||
|  |         "type": "array", | ||||||
|  |         "items": { | ||||||
|  |           "$ref": "#/definitions/StopWatch" | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "Tag": { |     "Tag": { | ||||||
|       "description": "Tag", |       "description": "Tag", | ||||||
|       "schema": { |       "schema": { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue