aboutsummaryrefslogtreecommitdiffstats
path: root/routers
diff options
context:
space:
mode:
Diffstat (limited to 'routers')
-rw-r--r--routers/api/v1/api.go11
-rw-r--r--routers/api/v1/repo/issue_tracked_time.go158
-rw-r--r--routers/install.go2
-rw-r--r--routers/repo/issue.go39
-rw-r--r--routers/repo/issue_stopwatch.go50
-rw-r--r--routers/repo/issue_timetrack.go50
-rw-r--r--routers/repo/setting.go5
-rw-r--r--routers/routes/routes.go13
8 files changed, 324 insertions, 4 deletions
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index b658c972f1..526940493f 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -350,6 +350,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Delete("", user.Unstar)
}, repoAssignment())
})
+ m.Get("/times", repo.ListMyTrackedTimes)
m.Get("/subscriptions", user.GetMyWatchedRepos)
}, reqToken())
@@ -395,6 +396,11 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Combo("/:id").Get(repo.GetDeployKey).
Delete(repo.DeleteDeploykey)
}, reqToken(), reqRepoWriter())
+ m.Group("/times", func() {
+ m.Combo("").Get(repo.ListTrackedTimesByRepository)
+ m.Combo("/:timetrackingusername").Get(repo.ListTrackedTimesByUser)
+
+ }, mustEnableIssues)
m.Group("/issues", func() {
m.Combo("").Get(repo.ListIssues).
Post(reqToken(), bind(api.CreateIssueOption{}), repo.CreateIssue)
@@ -422,6 +428,11 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Delete("/:id", reqToken(), repo.DeleteIssueLabel)
})
+ m.Group("/times", func() {
+ m.Combo("").Get(repo.ListTrackedTimes).
+ Post(reqToken(), bind(api.AddTimeOption{}), repo.AddTime)
+ })
+
})
}, mustEnableIssues)
m.Group("/labels", func() {
diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go
new file mode 100644
index 0000000000..964fc11ddb
--- /dev/null
+++ b/routers/api/v1/repo/issue_tracked_time.go
@@ -0,0 +1,158 @@
+// Copyright 2017 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/sdk/gitea"
+)
+
+// ListTrackedTimes list all the tracked times of an issue
+func ListTrackedTimes(ctx *context.APIContext) {
+ // swagger:route GET /repos/{username}/{reponame}/issues/{issue}/times repository issueTrackedTimes
+ //
+ // Produces:
+ // - application/json
+ //
+ // Responses:
+ // 200: TrackedTimes
+ // 404: error
+ // 500: error
+ if !ctx.Repo.Repository.IsTimetrackerEnabled() {
+ ctx.Error(404, "IsTimetrackerEnabled", "Timetracker is diabled")
+ return
+ }
+ issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
+ if err != nil {
+ if models.IsErrIssueNotExist(err) {
+ ctx.Error(404, "GetIssueByIndex", err)
+ } else {
+ ctx.Error(500, "GetIssueByIndex", err)
+ }
+ return
+ }
+
+ if trackedTimes, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{IssueID: issue.ID}); err != nil {
+ ctx.Error(500, "GetTrackedTimesByIssue", err)
+ } else {
+ ctx.JSON(200, &trackedTimes)
+ }
+}
+
+// AddTime adds time manual to the given issue
+func AddTime(ctx *context.APIContext, form api.AddTimeOption) {
+ // swagger:route Post /repos/{username}/{reponame}/issues/{issue}/times repository addTime
+ //
+ // Produces:
+ // - application/json
+ //
+ // Responses:
+ // 200: TrackedTime
+ // 400: error
+ // 403: error
+ // 404: error
+ // 500: error
+ issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
+ if err != nil {
+ if models.IsErrIssueNotExist(err) {
+ ctx.Error(404, "GetIssueByIndex", err)
+ } else {
+ ctx.Error(500, "GetIssueByIndex", err)
+ }
+ return
+ }
+
+ if !ctx.Repo.CanUseTimetracker(issue, ctx.User) {
+ if !ctx.Repo.Repository.IsTimetrackerEnabled() {
+ ctx.JSON(400, struct{ Message string }{Message: "time tracking disabled"})
+ return
+ }
+ ctx.Status(403)
+ return
+ }
+ var tt *models.TrackedTime
+ if tt, err = models.AddTime(ctx.User, issue, form.Time); err != nil {
+ ctx.Error(500, "AddTime", err)
+ return
+ }
+ ctx.JSON(200, tt)
+
+}
+
+// ListTrackedTimesByUser lists all tracked times of the user
+func ListTrackedTimesByUser(ctx *context.APIContext) {
+ // swagger:route GET /repos/{username}/{reponame}/times/{timetrackingusername} user userTrackedTimes
+ //
+ // Produces:
+ // - application/json
+ //
+ // Responses:
+ // 200: TrackedTimes
+ // 400: error
+ // 404: error
+ // 500: error
+ if !ctx.Repo.Repository.IsTimetrackerEnabled() {
+ ctx.JSON(400, struct{ Message string }{Message: "time tracking disabled"})
+ return
+ }
+ user, err := models.GetUserByName(ctx.Params(":timetrackingusername"))
+ if err != nil {
+ if models.IsErrUserNotExist(err) {
+ ctx.Error(404, "GetUserByName", err)
+ } else {
+ ctx.Error(500, "GetUserByName", err)
+ }
+ return
+ }
+ if user == nil {
+ ctx.Status(404)
+ return
+ }
+ if trackedTimes, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{UserID: user.ID, RepositoryID: ctx.Repo.Repository.ID}); err != nil {
+ ctx.Error(500, "GetTrackedTimesByUser", err)
+ } else {
+ ctx.JSON(200, &trackedTimes)
+ }
+}
+
+// ListTrackedTimesByRepository lists all tracked times of the user
+func ListTrackedTimesByRepository(ctx *context.APIContext) {
+ // swagger:route GET /repos/{username}/{reponame}/times repository repoTrackedTimes
+ //
+ // Produces:
+ // - application/json
+ //
+ // Responses:
+ // 200: TrackedTimes
+ // 400: error
+ // 500: error
+ if !ctx.Repo.Repository.IsTimetrackerEnabled() {
+ ctx.JSON(400, struct{ Message string }{Message: "time tracking disabled"})
+ return
+ }
+ if trackedTimes, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{RepositoryID: ctx.Repo.Repository.ID}); err != nil {
+ ctx.Error(500, "GetTrackedTimesByUser", err)
+ } else {
+ ctx.JSON(200, &trackedTimes)
+ }
+}
+
+// ListMyTrackedTimes lists all tracked times of the current user
+func ListMyTrackedTimes(ctx *context.APIContext) {
+ // swagger:route GET /user/times user userTrackedTimes
+ //
+ // Produces:
+ // - application/json
+ //
+ // Responses:
+ // 200: TrackedTimes
+ // 500: error
+ if trackedTimes, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{UserID: ctx.User.ID}); err != nil {
+ ctx.Error(500, "GetTrackedTimesByUser", err)
+ } else {
+ ctx.JSON(200, &trackedTimes)
+ }
+}
diff --git a/routers/install.go b/routers/install.go
index 08f5d80f3b..427bb960f2 100644
--- a/routers/install.go
+++ b/routers/install.go
@@ -115,6 +115,7 @@ func Install(ctx *context.Context) {
form.RequireSignInView = setting.Service.RequireSignInView
form.DefaultKeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate
form.DefaultAllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization
+ form.DefaultEnableTimetracking = setting.Service.DefaultEnableTimetracking
form.NoReplyAddress = setting.Service.NoReplyAddress
auth.AssignForm(form, ctx.Data)
@@ -301,6 +302,7 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) {
cfg.Section("service").Key("REQUIRE_SIGNIN_VIEW").SetValue(com.ToStr(form.RequireSignInView))
cfg.Section("service").Key("DEFAULT_KEEP_EMAIL_PRIVATE").SetValue(com.ToStr(form.DefaultKeepEmailPrivate))
cfg.Section("service").Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").SetValue(com.ToStr(form.DefaultAllowCreateOrganization))
+ cfg.Section("service").Key("DEFAULT_ENABLE_TIMETRACKING").SetValue(com.ToStr(form.DefaultEnableTimetracking))
cfg.Section("service").Key("NO_REPLY_ADDRESS").SetValue(com.ToStr(form.NoReplyAddress))
cfg.Section("").Key("RUN_MODE").SetValue("prod")
diff --git a/routers/repo/issue.go b/routers/repo/issue.go
index d1c5e1fe71..0cd4edabb6 100644
--- a/routers/repo/issue.go
+++ b/routers/repo/issue.go
@@ -589,6 +589,38 @@ func ViewIssue(ctx *context.Context) {
comment *models.Comment
participants = make([]*models.User, 1, 10)
)
+ if ctx.Repo.Repository.IsTimetrackerEnabled() {
+ if ctx.IsSigned {
+ // Deal with the stopwatch
+ ctx.Data["IsStopwatchRunning"] = models.StopwatchExists(ctx.User.ID, issue.ID)
+ if !ctx.Data["IsStopwatchRunning"].(bool) {
+ var exists bool
+ var sw *models.Stopwatch
+ if exists, sw, err = models.HasUserStopwatch(ctx.User.ID); err != nil {
+ ctx.Handle(500, "HasUserStopwatch", err)
+ return
+ }
+ ctx.Data["HasUserStopwatch"] = exists
+ if exists {
+ // Add warning if the user has already a stopwatch
+ var otherIssue *models.Issue
+ if otherIssue, err = models.GetIssueByID(sw.IssueID); err != nil {
+ ctx.Handle(500, "GetIssueByID", err)
+ return
+ }
+ // Add link to the issue of the already running stopwatch
+ ctx.Data["OtherStopwatchURL"] = otherIssue.HTMLURL()
+ }
+ }
+ ctx.Data["CanUseTimetracker"] = ctx.Repo.CanUseTimetracker(issue, ctx.User)
+ } else {
+ ctx.Data["CanUseTimetracker"] = false
+ }
+ if ctx.Data["WorkingUsers"], err = models.TotalTimes(models.FindTrackedTimesOptions{IssueID: issue.ID}); err != nil {
+ ctx.Handle(500, "TotalTimes", err)
+ return
+ }
+ }
// Render comments and and fetch participants.
participants[0] = issue.Poster
@@ -683,7 +715,8 @@ func ViewIssue(ctx *context.Context) {
ctx.HTML(200, tplIssueView)
}
-func getActionIssue(ctx *context.Context) *models.Issue {
+// GetActionIssue will return the issue which is used in the context.
+func GetActionIssue(ctx *context.Context) *models.Issue {
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if models.IsErrIssueNotExist(err) {
@@ -720,7 +753,7 @@ func getActionIssues(ctx *context.Context) []*models.Issue {
// UpdateIssueTitle change issue's title
func UpdateIssueTitle(ctx *context.Context) {
- issue := getActionIssue(ctx)
+ issue := GetActionIssue(ctx)
if ctx.Written() {
return
}
@@ -748,7 +781,7 @@ func UpdateIssueTitle(ctx *context.Context) {
// UpdateIssueContent change issue's content
func UpdateIssueContent(ctx *context.Context) {
- issue := getActionIssue(ctx)
+ issue := GetActionIssue(ctx)
if ctx.Written() {
return
}
diff --git a/routers/repo/issue_stopwatch.go b/routers/repo/issue_stopwatch.go
new file mode 100644
index 0000000000..7e3121da9f
--- /dev/null
+++ b/routers/repo/issue_stopwatch.go
@@ -0,0 +1,50 @@
+// Copyright 2017 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 (
+ "net/http"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/context"
+)
+
+// IssueStopwatch creates or stops a stopwatch for the given issue.
+func IssueStopwatch(c *context.Context) {
+ issueIndex := c.ParamsInt64("index")
+ issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, issueIndex)
+
+ if err != nil {
+ c.Handle(http.StatusInternalServerError, "GetIssueByIndex", err)
+ return
+ }
+
+ if err := models.CreateOrStopIssueStopwatch(c.User, issue); err != nil {
+ c.Handle(http.StatusInternalServerError, "CreateOrStopIssueStopwatch", err)
+ return
+ }
+
+ url := issue.HTMLURL()
+ c.Redirect(url, http.StatusSeeOther)
+}
+
+// CancelStopwatch cancel the stopwatch
+func CancelStopwatch(c *context.Context) {
+ issueIndex := c.ParamsInt64("index")
+ issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, issueIndex)
+
+ if err != nil {
+ c.Handle(http.StatusInternalServerError, "GetIssueByIndex", err)
+ return
+ }
+
+ if err := models.CancelStopwatch(c.User, issue); err != nil {
+ c.Handle(http.StatusInternalServerError, "CancelStopwatch", err)
+ return
+ }
+
+ url := issue.HTMLURL()
+ c.Redirect(url, http.StatusSeeOther)
+}
diff --git a/routers/repo/issue_timetrack.go b/routers/repo/issue_timetrack.go
new file mode 100644
index 0000000000..e01cd48a66
--- /dev/null
+++ b/routers/repo/issue_timetrack.go
@@ -0,0 +1,50 @@
+// Copyright 2017 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 (
+ "net/http"
+ "time"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/auth"
+ "code.gitea.io/gitea/modules/context"
+)
+
+// AddTimeManually tracks time manually
+func AddTimeManually(c *context.Context, form auth.AddTimeManuallyForm) {
+ issueIndex := c.ParamsInt64("index")
+ issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, issueIndex)
+ if err != nil {
+ if models.IsErrIssueNotExist(err) {
+ c.Handle(http.StatusNotFound, "GetIssueByIndex", err)
+ return
+ }
+ c.Handle(http.StatusInternalServerError, "GetIssueByIndex", err)
+ return
+ }
+ url := issue.HTMLURL()
+
+ if c.HasError() {
+ c.Flash.Error(c.GetErrMsg())
+ c.Redirect(url)
+ return
+ }
+
+ total := time.Duration(form.Hours)*time.Hour + time.Duration(form.Minutes)*time.Minute
+
+ if total <= 0 {
+ c.Flash.Error(c.Tr("repo.issues.add_time_sum_to_small"))
+ c.Redirect(url, http.StatusSeeOther)
+ return
+ }
+
+ if _, err := models.AddTime(c.User, issue, int64(total)); err != nil {
+ c.Handle(http.StatusInternalServerError, "AddTime", err)
+ return
+ }
+
+ c.Redirect(url, http.StatusSeeOther)
+}
diff --git a/routers/repo/setting.go b/routers/repo/setting.go
index 8b38e80916..6e12c7ad6d 100644
--- a/routers/repo/setting.go
+++ b/routers/repo/setting.go
@@ -201,7 +201,10 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
RepoID: repo.ID,
Type: models.UnitTypeIssues,
Index: int(models.UnitTypeIssues),
- Config: new(models.UnitConfig),
+ Config: &models.IssuesConfig{
+ EnableTimetracker: form.EnableTimetracker,
+ AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime,
+ },
})
}
}
diff --git a/routers/routes/routes.go b/routers/routes/routes.go
index 5fa93fdb09..938dec1dcf 100644
--- a/routers/routes/routes.go
+++ b/routers/routes/routes.go
@@ -484,6 +484,19 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/content", repo.UpdateIssueContent)
m.Post("/watch", repo.IssueWatch)
m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
+ m.Group("/times", func() {
+ m.Post("/add", bindIgnErr(auth.AddTimeManuallyForm{}), repo.AddTimeManually)
+ m.Group("/stopwatch", func() {
+ m.Post("/toggle", repo.IssueStopwatch)
+ m.Post("/cancel", repo.CancelStopwatch)
+ })
+
+ }, func(ctx *context.Context) {
+ if !ctx.Repo.CanUseTimetracker(repo.GetActionIssue(ctx), ctx.User) {
+ ctx.Handle(404, ctx.Req.RequestURI, nil)
+ return
+ }
+ })
})
m.Post("/labels", repo.UpdateIssueLabel, reqRepoWriter)