summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrey Nering <andrey.nering@gmail.com>2016-12-30 14:44:54 -0200
committerLunny Xiao <xiaolunwen@gmail.com>2016-12-31 00:44:54 +0800
commit42904cb98a4b8e7accdac90bc9f06347cb0521f7 (patch)
treeff7b39817584c93ad9afd2d2136f51f5d4ddb0a0
parent37eec6c9b7056e1e05d0067aef86d2e61d67f757 (diff)
downloadgitea-42904cb98a4b8e7accdac90bc9f06347cb0521f7.tar.gz
gitea-42904cb98a4b8e7accdac90bc9f06347cb0521f7.zip
Notification - Step 1 (#523)
* Notification - Step 1 * Add copyright headers * Cache issue and repository on notification model
-rw-r--r--models/issue.go12
-rw-r--r--models/models.go44
-rw-r--r--models/notification.go249
-rw-r--r--modules/notification/notification.go50
-rw-r--r--routers/repo/issue.go5
5 files changed, 349 insertions, 11 deletions
diff --git a/models/issue.go b/models/issue.go
index 86becdbbae..6303911808 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -443,8 +443,16 @@ func (issue *Issue) GetAssignee() (err error) {
}
// ReadBy sets issue to be read by given user.
-func (issue *Issue) ReadBy(uid int64) error {
- return UpdateIssueUserByRead(uid, issue.ID)
+func (issue *Issue) ReadBy(userID int64) error {
+ if err := UpdateIssueUserByRead(userID, issue.ID); err != nil {
+ return err
+ }
+
+ if err := setNotificationStatusRead(x, userID, issue.ID); err != nil {
+ return err
+ }
+
+ return nil
}
func updateIssueCols(e Engine, issue *Issue, cols ...string) error {
diff --git a/models/models.go b/models/models.go
index 20812d0ebf..1bebaa602c 100644
--- a/models/models.go
+++ b/models/models.go
@@ -71,15 +71,41 @@ var (
func init() {
tables = append(tables,
- new(User), new(PublicKey), new(AccessToken),
- new(Repository), new(DeployKey), new(Collaboration), new(Access), new(Upload),
- new(Watch), new(Star), new(Follow), new(Action),
- new(Issue), new(PullRequest), new(Comment), new(Attachment), new(IssueUser),
- new(Label), new(IssueLabel), new(Milestone),
- new(Mirror), new(Release), new(LoginSource), new(Webhook),
- new(UpdateTask), new(HookTask),
- new(Team), new(OrgUser), new(TeamUser), new(TeamRepo),
- new(Notice), new(EmailAddress), new(LFSMetaObject))
+ new(User),
+ new(PublicKey),
+ new(AccessToken),
+ new(Repository),
+ new(DeployKey),
+ new(Collaboration),
+ new(Access),
+ new(Upload),
+ new(Watch),
+ new(Star),
+ new(Follow),
+ new(Action),
+ new(Issue),
+ new(PullRequest),
+ new(Comment),
+ new(Attachment),
+ new(Label),
+ new(IssueLabel),
+ new(Milestone),
+ new(Mirror),
+ new(Release),
+ new(LoginSource),
+ new(Webhook),
+ new(UpdateTask),
+ new(HookTask),
+ new(Team),
+ new(OrgUser),
+ new(TeamUser),
+ new(TeamRepo),
+ new(Notice),
+ new(EmailAddress),
+ new(Notification),
+ new(IssueUser),
+ new(LFSMetaObject),
+ )
gonicNames := []string{"SSL", "UID"}
for _, name := range gonicNames {
diff --git a/models/notification.go b/models/notification.go
new file mode 100644
index 0000000000..46d63b4823
--- /dev/null
+++ b/models/notification.go
@@ -0,0 +1,249 @@
+// Copyright 2016 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 models
+
+import (
+ "time"
+)
+
+type (
+ // NotificationStatus is the status of the notification (read or unread)
+ NotificationStatus uint8
+ // NotificationSource is the source of the notification (issue, PR, commit, etc)
+ NotificationSource uint8
+)
+
+const (
+ // NotificationStatusUnread represents an unread notification
+ NotificationStatusUnread NotificationStatus = iota + 1
+ // NotificationStatusRead represents a read notification
+ NotificationStatusRead
+)
+
+const (
+ // NotificationSourceIssue is a notification of an issue
+ NotificationSourceIssue NotificationSource = iota + 1
+ // NotificationSourcePullRequest is a notification of a pull request
+ NotificationSourcePullRequest
+ // NotificationSourceCommit is a notification of a commit
+ NotificationSourceCommit
+)
+
+// Notification represents a notification
+type Notification struct {
+ ID int64 `xorm:"pk autoincr"`
+ UserID int64 `xorm:"INDEX NOT NULL"`
+ RepoID int64 `xorm:"INDEX NOT NULL"`
+
+ Status NotificationStatus `xorm:"SMALLINT INDEX NOT NULL"`
+ Source NotificationSource `xorm:"SMALLINT INDEX NOT NULL"`
+
+ IssueID int64 `xorm:"INDEX NOT NULL"`
+ CommitID string `xorm:"INDEX"`
+
+ UpdatedBy int64 `xorm:"INDEX NOT NULL"`
+
+ Issue *Issue `xorm:"-"`
+ Repository *Repository `xorm:"-"`
+
+ Created time.Time `xorm:"-"`
+ CreatedUnix int64 `xorm:"INDEX NOT NULL"`
+ Updated time.Time `xorm:"-"`
+ UpdatedUnix int64 `xorm:"INDEX NOT NULL"`
+}
+
+// BeforeInsert runs while inserting a record
+func (n *Notification) BeforeInsert() {
+ var (
+ now = time.Now()
+ nowUnix = now.Unix()
+ )
+ n.Created = now
+ n.CreatedUnix = nowUnix
+ n.Updated = now
+ n.UpdatedUnix = nowUnix
+}
+
+// BeforeUpdate runs while updateing a record
+func (n *Notification) BeforeUpdate() {
+ var (
+ now = time.Now()
+ nowUnix = now.Unix()
+ )
+ n.Updated = now
+ n.UpdatedUnix = nowUnix
+}
+
+// CreateOrUpdateIssueNotifications creates an issue notification
+// for each watcher, or updates it if already exists
+func CreateOrUpdateIssueNotifications(issue *Issue, notificationAuthorID int64) error {
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+
+ if err := createOrUpdateIssueNotifications(sess, issue, notificationAuthorID); err != nil {
+ return err
+ }
+
+ return sess.Commit()
+}
+
+func createOrUpdateIssueNotifications(e Engine, issue *Issue, notificationAuthorID int64) error {
+ watches, err := getWatchers(e, issue.RepoID)
+ if err != nil {
+ return err
+ }
+
+ notifications, err := getNotificationsByIssueID(e, issue.ID)
+ if err != nil {
+ return err
+ }
+
+ for _, watch := range watches {
+ // do not send notification for the own issuer/commenter
+ if watch.UserID == notificationAuthorID {
+ continue
+ }
+
+ if notificationExists(notifications, issue.ID, watch.UserID) {
+ err = updateIssueNotification(e, watch.UserID, issue.ID, notificationAuthorID)
+ } else {
+ err = createIssueNotification(e, watch.UserID, issue, notificationAuthorID)
+ }
+
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func getNotificationsByIssueID(e Engine, issueID int64) (notifications []*Notification, err error) {
+ err = e.
+ Where("issue_id = ?", issueID).
+ Find(&notifications)
+ return
+}
+
+func notificationExists(notifications []*Notification, issueID, userID int64) bool {
+ for _, notification := range notifications {
+ if notification.IssueID == issueID && notification.UserID == userID {
+ return true
+ }
+ }
+
+ return false
+}
+
+func createIssueNotification(e Engine, userID int64, issue *Issue, updatedByID int64) error {
+ notification := &Notification{
+ UserID: userID,
+ RepoID: issue.RepoID,
+ Status: NotificationStatusUnread,
+ IssueID: issue.ID,
+ UpdatedBy: updatedByID,
+ }
+
+ if issue.IsPull {
+ notification.Source = NotificationSourcePullRequest
+ } else {
+ notification.Source = NotificationSourceIssue
+ }
+
+ _, err := e.Insert(notification)
+ return err
+}
+
+func updateIssueNotification(e Engine, userID, issueID, updatedByID int64) error {
+ notification, err := getIssueNotification(e, userID, issueID)
+ if err != nil {
+ return err
+ }
+
+ notification.Status = NotificationStatusUnread
+ notification.UpdatedBy = updatedByID
+
+ _, err = e.Id(notification.ID).Update(notification)
+ return err
+}
+
+func getIssueNotification(e Engine, userID, issueID int64) (*Notification, error) {
+ notification := new(Notification)
+ _, err := e.
+ Where("user_id = ?", userID).
+ And("issue_id = ?", issueID).
+ Get(notification)
+ return notification, err
+}
+
+// NotificationsForUser returns notifications for a given user and status
+func NotificationsForUser(user *User, status NotificationStatus) ([]*Notification, error) {
+ return notificationsForUser(x, user, status)
+}
+func notificationsForUser(e Engine, user *User, status NotificationStatus) (notifications []*Notification, err error) {
+ err = e.
+ Where("user_id = ?", user.ID).
+ And("status = ?", status).
+ OrderBy("updated_unix DESC").
+ Find(&notifications)
+ return
+}
+
+// GetRepo returns the repo of the notification
+func (n *Notification) GetRepo() (*Repository, error) {
+ n.Repository = new(Repository)
+ _, err := x.
+ Where("id = ?", n.RepoID).
+ Get(n.Repository)
+ return n.Repository, err
+}
+
+// GetIssue returns the issue of the notification
+func (n *Notification) GetIssue() (*Issue, error) {
+ n.Issue = new(Issue)
+ _, err := x.
+ Where("id = ?", n.IssueID).
+ Get(n.Issue)
+ return n.Issue, err
+}
+
+// GetNotificationReadCount returns the notification read count for user
+func GetNotificationReadCount(user *User) (int64, error) {
+ return GetNotificationCount(user, NotificationStatusRead)
+}
+
+// GetNotificationUnreadCount returns the notification unread count for user
+func GetNotificationUnreadCount(user *User) (int64, error) {
+ return GetNotificationCount(user, NotificationStatusUnread)
+}
+
+// GetNotificationCount returns the notification count for user
+func GetNotificationCount(user *User, status NotificationStatus) (int64, error) {
+ return getNotificationCount(x, user, status)
+}
+
+func getNotificationCount(e Engine, user *User, status NotificationStatus) (count int64, err error) {
+ count, err = e.
+ Where("user_id = ?", user.ID).
+ And("status = ?", status).
+ Count(&Notification{})
+ return
+}
+
+func setNotificationStatusRead(e Engine, userID, issueID int64) error {
+ notification, err := getIssueNotification(e, userID, issueID)
+ // ignore if not exists
+ if err != nil {
+ return nil
+ }
+
+ notification.Status = NotificationStatusRead
+
+ _, err = e.Id(notification.ID).Update(notification)
+ return err
+}
diff --git a/modules/notification/notification.go b/modules/notification/notification.go
new file mode 100644
index 0000000000..ffe885240b
--- /dev/null
+++ b/modules/notification/notification.go
@@ -0,0 +1,50 @@
+// Copyright 2016 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 notification
+
+import (
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/log"
+)
+
+type (
+ notificationService struct {
+ issueQueue chan issueNotificationOpts
+ }
+
+ issueNotificationOpts struct {
+ issue *models.Issue
+ notificationAuthorID int64
+ }
+)
+
+var (
+ // Service is the notification service
+ Service = &notificationService{
+ issueQueue: make(chan issueNotificationOpts, 100),
+ }
+)
+
+func init() {
+ go Service.Run()
+}
+
+func (ns *notificationService) Run() {
+ for {
+ select {
+ case opts := <-ns.issueQueue:
+ if err := models.CreateOrUpdateIssueNotifications(opts.issue, opts.notificationAuthorID); err != nil {
+ log.Error(4, "Was unable to create issue notification: %v", err)
+ }
+ }
+ }
+}
+
+func (ns *notificationService) NotifyIssue(issue *models.Issue, notificationAuthorID int64) {
+ ns.issueQueue <- issueNotificationOpts{
+ issue,
+ notificationAuthorID,
+ }
+}
diff --git a/routers/repo/issue.go b/routers/repo/issue.go
index 6eeb5bef50..049eac06c3 100644
--- a/routers/repo/issue.go
+++ b/routers/repo/issue.go
@@ -24,6 +24,7 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markdown"
+ "code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/setting"
)
@@ -467,6 +468,8 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
return
}
+ notification.Service.NotifyIssue(issue, ctx.User.ID)
+
log.Trace("Issue created: %d/%d", repo.ID, issue.ID)
ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index))
}
@@ -931,6 +934,8 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
return
}
+ notification.Service.NotifyIssue(issue, ctx.User.ID)
+
log.Trace("Comment created: %d/%d/%d", ctx.Repo.Repository.ID, issue.ID, comment.ID)
}