[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 tootokarchuk/v1.17
							parent
							
								
									382936a668
								
							
						
					
					
						commit
						aceb1085c7
					
				@ -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) | 
				
			||||||
 | 
					} | 
				
			||||||
@ -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 | 
				
			||||||
@ -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) | 
				
			||||||
 | 
					} | 
				
			||||||
					Loading…
					
					
				
		Reference in new issue