From 9dfdf80af08cee3b2f0297843084fe14e3af31e7 Mon Sep 17 00:00:00 2001
From: Lanre Adelowo <adelowomailbox@gmail.com>
Date: Thu, 7 Feb 2019 03:57:25 +0100
Subject: [PATCH] Expose issue stopwatch toggling via API (#5970)

---
 routers/api/v1/api.go          |   4 +
 routers/api/v1/repo/issue.go   | 138 +++++++++++++++++++++++++++++++++
 templates/swagger/v1_json.tmpl | 106 +++++++++++++++++++++++++
 3 files changed, 248 insertions(+)

diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 55f5c66290..c5a5488a81 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -557,6 +557,10 @@ func RegisterRoutes(m *macaron.Macaron) {
 						})
 
 						m.Combo("/deadline").Post(reqToken(), bind(api.EditDeadlineOption{}), repo.UpdateIssueDeadline)
+						m.Group("/stopwatch", func() {
+							m.Post("/start", reqToken(), repo.StartIssueStopwatch)
+							m.Post("/stop", reqToken(), repo.StopIssueStopwatch)
+						})
 					})
 				}, mustEnableIssuesOrPulls)
 				m.Group("/labels", func() {
diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go
index 1cb9c2f819..d339d8f0b7 100644
--- a/routers/api/v1/repo/issue.go
+++ b/routers/api/v1/repo/issue.go
@@ -439,3 +439,141 @@ func UpdateIssueDeadline(ctx *context.APIContext, form api.EditDeadlineOption) {
 
 	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.Status(404)
+		} 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.Status(404)
+		} 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)
+}
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index c2ed1d75b8..d45a3d6d1b 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -2942,6 +2942,112 @@
         }
       }
     },
+    "/repos/{owner}/{repo}/issues/{index}/stopwatch/start": {
+      "post": {
+        "consumes": [
+          "application/json"
+        ],
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "issue"
+        ],
+        "summary": "Start stopwatch on an issue.",
+        "operationId": "issueStartStopWatch",
+        "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 create the stopwatch on",
+            "name": "index",
+            "in": "path",
+            "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"
+          }
+        }
+      }
+    },
+    "/repos/{owner}/{repo}/issues/{index}/stopwatch/stop": {
+      "post": {
+        "consumes": [
+          "application/json"
+        ],
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "issue"
+        ],
+        "summary": "Stop an issue's existing stopwatch.",
+        "operationId": "issueStopWatch",
+        "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": {
+          "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"
+          }
+        }
+      }
+    },
     "/repos/{owner}/{repo}/keys": {
       "get": {
         "produces": [