From b6744607484008826d18f129326664105b9d7bfc Mon Sep 17 00:00:00 2001
From: Andrey Nering <andrey.nering@gmail.com>
Date: Wed, 29 Mar 2017 20:31:47 -0300
Subject: [PATCH] Add watch button on issue

---
 cmd/web.go                                    |  1 +
 models/issue_watch.go                         | 40 +++++++++++++++++++
 options/locale/locale_en-US.ini               |  3 ++
 routers/repo/issue.go                         | 14 +++++++
 routers/repo/issue_watch.go                   | 34 ++++++++++++++++
 .../repo/issue/view_content/sidebar.tmpl      | 19 +++++++++
 6 files changed, 111 insertions(+)
 create mode 100644 routers/repo/issue_watch.go

diff --git a/cmd/web.go b/cmd/web.go
index 1f2561ca68..b2cc3959a2 100644
--- a/cmd/web.go
+++ b/cmd/web.go
@@ -491,6 +491,7 @@ func runWeb(ctx *cli.Context) error {
 			m.Group("/:index", func() {
 				m.Post("/title", repo.UpdateIssueTitle)
 				m.Post("/content", repo.UpdateIssueContent)
+				m.Post("/watch", repo.IssueWatch)
 				m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
 			})
 
diff --git a/models/issue_watch.go b/models/issue_watch.go
index 96e080136f..d082211c77 100644
--- a/models/issue_watch.go
+++ b/models/issue_watch.go
@@ -16,5 +16,45 @@ type IssueWatch struct {
 
 // BeforeInsert is invoked from XORM before inserting an object of this type.
 func (iw *IssueWatch) BeforeInsert() {
+	iw.Created = time.Now()
 	iw.CreatedUnix = time.Now().Unix()
 }
+
+// CreateOrUpdateIssueWatch set watching for a user and issue
+func CreateOrUpdateIssueWatch(userID, issueID int64, isWatching bool) error {
+	iw, exists, err := getIssueWatch(x, userID, issueID)
+	if err != nil {
+		return err
+	}
+
+	if !exists {
+		iw = &IssueWatch{
+			UserID:     userID,
+			IssueID:    issueID,
+			IsWatching: isWatching,
+		}
+
+		if _, err := x.Insert(iw); err != nil {
+			return err
+		}
+	} else {
+		if _, err := x.Table(&IssueWatch{}).Id(iw.ID).Update(map[string]interface{}{"is_watching": isWatching}); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// GetIssueWatch returns an issue watch by user and issue
+func GetIssueWatch(userID, issueID int64) (iw *IssueWatch, exists bool, err error) {
+	iw, exists, err = getIssueWatch(x, userID, issueID)
+	return
+}
+func getIssueWatch(e Engine, userID, issueID int64) (iw *IssueWatch, exists bool, err error) {
+	iw = new(IssueWatch)
+	exists, err = e.
+		Where("user_id = ?", userID).
+		And("issue_id = ?", issueID).
+		Get(iw)
+	return
+}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 80260d4b7d..822d9cdc99 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -652,6 +652,9 @@ issues.label.filter_sort.reverse_alphabetically = Reverse alphabetically
 issues.num_participants = %d Participants
 issues.attachment.open_tab = `Click to see "%s" in a new tab`
 issues.attachment.download = `Click to download "%s"`
+issues.watch = Watch
+issues.watch_issue = Watch issue
+issues.unwatch_issue = Unwatch issue
 
 pulls.new = New Pull Request
 pulls.compare_changes = Compare Changes
diff --git a/routers/repo/issue.go b/routers/repo/issue.go
index 0a723d755b..61f79a239c 100644
--- a/routers/repo/issue.go
+++ b/routers/repo/issue.go
@@ -465,6 +465,20 @@ func ViewIssue(ctx *context.Context) {
 	}
 	ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title)
 
+	iw, exists, err := models.GetIssueWatch(ctx.User.ID, issue.ID)
+	if err != nil {
+		ctx.Handle(500, "GetIssueWatch", err)
+		return
+	}
+	if !exists {
+		iw = &models.IssueWatch{
+			UserID:     ctx.User.ID,
+			IssueID:    issue.ID,
+			IsWatching: models.IsWatching(ctx.User.ID, ctx.Repo.Repository.ID),
+		}
+	}
+	ctx.Data["IssueWatch"] = iw
+
 	// Make sure type and URL matches.
 	if ctx.Params(":type") == "issues" && issue.IsPull {
 		ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
diff --git a/routers/repo/issue_watch.go b/routers/repo/issue_watch.go
new file mode 100644
index 0000000000..64c99c5f79
--- /dev/null
+++ b/routers/repo/issue_watch.go
@@ -0,0 +1,34 @@
+package repo
+
+import (
+	"fmt"
+	"net/http"
+	"strconv"
+
+	"code.gitea.io/gitea/models"
+	"code.gitea.io/gitea/modules/context"
+)
+
+// IssueWatch sets issue watching
+func IssueWatch(c *context.Context) {
+	watch, err := strconv.ParseBool(c.Req.PostForm.Get("watch"))
+	if err != nil {
+		c.Handle(http.StatusInternalServerError, "watch is not bool", err)
+		return
+	}
+
+	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.CreateOrUpdateIssueWatch(c.User.ID, issue.ID, watch); err != nil {
+		c.Handle(http.StatusInternalServerError, "CreateOrUpdateIssueWatch", err)
+		return
+	}
+
+	url := fmt.Sprintf("%s/issues/%d", c.Repo.RepoLink, issueIndex)
+	c.Redirect(url, http.StatusSeeOther)
+}
diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl
index ea46e5f94d..9a4a6cb1ae 100644
--- a/templates/repo/issue/view_content/sidebar.tmpl
+++ b/templates/repo/issue/view_content/sidebar.tmpl
@@ -98,5 +98,24 @@
 				{{end}}
 			</div>
 		</div>
+
+		<div class="ui divider"></div>
+
+		<div class="ui watching">
+			<span class="text"><strong>{{.i18n.Tr "repo.issues.watch"}}</strong></span>
+			<div>
+				<form method="POST" action="{{$.RepoLink}}/issues/{{.Issue.Index}}/watch">
+					<input type="hidden" name="watch" value="{{if $.IssueWatch.IsWatching}}0{{else}}1{{end}}" />
+					{{$.CsrfTokenHtml}}
+					<button class="fluid ui button">
+						{{if $.IssueWatch.IsWatching}}
+							{{.i18n.Tr "repo.issues.unwatch_issue"}}
+						{{else}}
+							{{.i18n.Tr "repo.issues.watch_issue"}}
+						{{end}}
+					</button>
+				</form>
+			</div>
+		</div>
 	</div>
 </div>