From 0f1b484e9a0a687a343248d830daa7ffcbcc64c0 Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Wed, 30 Nov 2016 20:31:10 -0200 Subject: [PATCH 01/14] Create notification model/table --- models/models.go | 43 ++++++++++++++++++++++++++------- models/notification.go | 54 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 models/notification.go diff --git a/models/models.go b/models/models.go index 56306d61f5afe..4b556c5aba07f 100644 --- a/models/models.go +++ b/models/models.go @@ -68,15 +68,40 @@ 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(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(Notification), + ) gonicNames := []string{"SSL", "UID"} for _, name := range gonicNames { diff --git a/models/notification.go b/models/notification.go new file mode 100644 index 0000000000000..296852f23dd1e --- /dev/null +++ b/models/notification.go @@ -0,0 +1,54 @@ +package models + +import ( + "time" +) + +const ( + NotificationStatusUnread = "U" + NotificationStatusRead = "R" + + NotificationSourceIssue = "I" + NotificationSourcePullRequest = "P" + NotificationSourceCommit = "C" +) + +type Notification struct { + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"INDEX NOT NULL"` + RepoID int64 `xorm:"INDEX NOT NULL"` + + Status string `xorm:"VARCHAR(1) INDEX NOT NULL"` + Source string `xorm:"VARCHAR(1) INDEX NOT NULL"` + + IssueID int64 `xorm:"INDEX NOT NULL"` + PullID int64 `xorm:"INDEX"` + CommitID string `xorm:"INDEX"` + + Issue *Issue `xorm:"-"` + PullRequest *PullRequest `xorm:"-"` + + Created time.Time `xorm:"-"` + CreatedUnix int64 `xorm:"INDEX NOT NULL"` + Updated time.Time `xorm:"-"` + UpdatedUnix int64 `xorm:"INDEX NOT NULL"` +} + +func (n *Notification) BeforeInsert() { + var ( + now = time.Now() + nowUnix = now.Unix() + ) + n.Created = now + n.CreatedUnix = nowUnix + n.Updated = now + n.UpdatedUnix = nowUnix +} +func (n *Notification) BeforeUpdate() { + var ( + now = time.Now() + nowUnix = now.Unix() + ) + n.Updated = now + n.UpdatedUnix = nowUnix +} From b19ec553387afbb84166b5c0bbdd744db2af5fae Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Wed, 30 Nov 2016 21:24:27 -0200 Subject: [PATCH 02/14] Creating notifications on new issue --- models/notification.go | 80 ++++++++++++++++++++++++++++++++++++++++++ routers/repo/issue.go | 5 +++ 2 files changed, 85 insertions(+) diff --git a/models/notification.go b/models/notification.go index 296852f23dd1e..776b5aa5f7f9d 100644 --- a/models/notification.go +++ b/models/notification.go @@ -52,3 +52,83 @@ func (n *Notification) BeforeUpdate() { n.Updated = now n.UpdatedUnix = nowUnix } + +func CreateOrUpdateIssueNotifications(issue *Issue) error { + watches, err := getWatchers(x, issue.RepoID) + if err != nil { + return err + } + + sess := x.NewSession() + if err := sess.Begin(); err != nil { + return err + } + + defer sess.Close() + + for _, watch := range watches { + exists, err := issueNotificationExists(sess, watch.UserID, watch.RepoID) + if err != nil { + return err + } + + if exists { + err = updateIssueNotification(sess, watch.UserID, issue.ID) + } else { + err = createIssueNotification(sess, watch.UserID, issue) + } + + if err != nil { + return err + } + } + + return sess.Commit() +} + +func issueNotificationExists(e Engine, userID, issueID int64) (bool, error) { + count, err := e. + Where("user_id = ?", userID). + And("issue_id = ?", issueID). + Count(Notification{}) + return count > 0, err +} + +func createIssueNotification(e Engine, userID int64, issue *Issue) error { + notification := &Notification{ + UserID: userID, + RepoID: issue.RepoID, + Status: NotificationStatusUnread, + IssueID: issue.ID, + } + + if issue.IsPull { + notification.Source = NotificationSourcePullRequest + } else { + notification.Source = NotificationSourceIssue + } + + _, err := e.Insert(notification) + return err +} + +func updateIssueNotification(e Engine, userID, issueID int64) error { + notification, err := getIssueNotification(e, userID, issueID) + if err != nil { + return err + } + + notification.Status = NotificationStatusUnread + + _, 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 = ?"). + And("issue_id = ?", issueID). + Get(notification) + return notification, err +} diff --git a/routers/repo/issue.go b/routers/repo/issue.go index e3c088d94024b..d2c5be9ed673a 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -453,6 +453,11 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) { return } + if err := models.CreateOrUpdateIssueNotifications(issue); err != nil { + ctx.Handle(500, "CreateOrUpdateIssueNotifications", err) + return + } + log.Trace("Issue created: %d/%d", repo.ID, issue.ID) ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index)) } From 75c2752ee6344e4b69e89783649d8aa501754dd0 Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Mon, 5 Dec 2016 19:03:09 -0200 Subject: [PATCH 03/14] Use own type (smallint on DB) and iota instead of VARCHAR(1) --- models/notification.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/models/notification.go b/models/notification.go index 776b5aa5f7f9d..d5ebab78edcd3 100644 --- a/models/notification.go +++ b/models/notification.go @@ -4,13 +4,20 @@ import ( "time" ) +type ( + NotificationStatus uint8 + NotificationSource uint8 +) + const ( - NotificationStatusUnread = "U" - NotificationStatusRead = "R" + NotificationStatusUnread NotificationStatus = iota + 1 + NotificationStatusRead +) - NotificationSourceIssue = "I" - NotificationSourcePullRequest = "P" - NotificationSourceCommit = "C" +const ( + NotificationSourceIssue NotificationSource = iota + 1 + NotificationSourcePullRequest + NotificationSourceCommit ) type Notification struct { @@ -18,8 +25,8 @@ type Notification struct { UserID int64 `xorm:"INDEX NOT NULL"` RepoID int64 `xorm:"INDEX NOT NULL"` - Status string `xorm:"VARCHAR(1) INDEX NOT NULL"` - Source string `xorm:"VARCHAR(1) INDEX NOT NULL"` + Status NotificationStatus `xorm:"SMALLINT INDEX NOT NULL"` + Source NotificationSource `xorm:"SMALLINT INDEX NOT NULL"` IssueID int64 `xorm:"INDEX NOT NULL"` PullID int64 `xorm:"INDEX"` From c3810c6d43ea2d6656eb59caa62982e8f97c1160 Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Mon, 5 Dec 2016 19:14:52 -0200 Subject: [PATCH 04/14] Lint --- models/notification.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/models/notification.go b/models/notification.go index d5ebab78edcd3..44d838fe17687 100644 --- a/models/notification.go +++ b/models/notification.go @@ -5,21 +5,29 @@ import ( ) 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"` @@ -41,6 +49,7 @@ type Notification struct { UpdatedUnix int64 `xorm:"INDEX NOT NULL"` } +// BeforeInsert runs while inserting a record func (n *Notification) BeforeInsert() { var ( now = time.Now() @@ -51,6 +60,8 @@ func (n *Notification) BeforeInsert() { n.Updated = now n.UpdatedUnix = nowUnix } + +// BeforeUpdate runs while updateing a record func (n *Notification) BeforeUpdate() { var ( now = time.Now() @@ -60,6 +71,8 @@ func (n *Notification) BeforeUpdate() { n.UpdatedUnix = nowUnix } +// CreateOrUpdateIssueNotifications creates an issue notification +// for each watcher, or updates it if already exists func CreateOrUpdateIssueNotifications(issue *Issue) error { watches, err := getWatchers(x, issue.RepoID) if err != nil { @@ -70,7 +83,6 @@ func CreateOrUpdateIssueNotifications(issue *Issue) error { if err := sess.Begin(); err != nil { return err } - defer sess.Close() for _, watch := range watches { From 8a3c856b1cb08a81204577838c57e28e5514b66c Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Mon, 5 Dec 2016 19:19:59 -0200 Subject: [PATCH 05/14] Also create notification while commenting on issue --- routers/repo/issue.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/routers/repo/issue.go b/routers/repo/issue.go index d2c5be9ed673a..e09374ce936bb 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -903,6 +903,11 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) { return } + if err := models.CreateOrUpdateIssueNotifications(issue); err != nil { + ctx.Handle(500, "CreateOrUpdateIssueNotifications", err) + return + } + log.Trace("Comment created: %d/%d/%d", ctx.Repo.Repository.ID, issue.ID, comment.ID) } From 679d91afdfdbe845308b426d780aeb75416dc470 Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Tue, 6 Dec 2016 20:17:25 -0200 Subject: [PATCH 06/14] Fix SQLs --- models/notification.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/notification.go b/models/notification.go index 44d838fe17687..f46f27b12ecdf 100644 --- a/models/notification.go +++ b/models/notification.go @@ -86,7 +86,7 @@ func CreateOrUpdateIssueNotifications(issue *Issue) error { defer sess.Close() for _, watch := range watches { - exists, err := issueNotificationExists(sess, watch.UserID, watch.RepoID) + exists, err := issueNotificationExists(sess, watch.UserID, issue.ID) if err != nil { return err } @@ -146,7 +146,7 @@ func updateIssueNotification(e Engine, userID, issueID int64) error { func getIssueNotification(e Engine, userID, issueID int64) (*Notification, error) { notification := new(Notification) _, err := e. - Where("user_id = ?"). + Where("user_id = ?", userID). And("issue_id = ?", issueID). Get(notification) return notification, err From f692adea69f186079c4576a819f2321389621941 Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Tue, 6 Dec 2016 20:54:44 -0200 Subject: [PATCH 07/14] First version of notification service --- modules/notification/notification.go | 36 ++++++++++++++++++++++++++++ routers/repo/issue.go | 11 +++------ 2 files changed, 39 insertions(+), 8 deletions(-) create mode 100644 modules/notification/notification.go diff --git a/modules/notification/notification.go b/modules/notification/notification.go new file mode 100644 index 0000000000000..bb56f543fa76e --- /dev/null +++ b/modules/notification/notification.go @@ -0,0 +1,36 @@ +package notification + +import ( + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/log" +) + +type notificationService struct { + issueQueue chan *models.Issue +} + +var ( + // Service is the notification service + Service = ¬ificationService{ + issueQueue: make(chan *models.Issue), + } +) + +func init() { + go Service.Run() +} + +func (ns *notificationService) Run() { + for { + select { + case issue := <-ns.issueQueue: + if err := models.CreateOrUpdateIssueNotifications(issue); err != nil { + log.Error(4, "Was unable to create issue notification: %v", err) + } + } + } +} + +func (ns *notificationService) NotifyIssue(issue *models.Issue) { + ns.issueQueue <- issue +} diff --git a/routers/repo/issue.go b/routers/repo/issue.go index e09374ce936bb..4717c90a64dc6 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -23,6 +23,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" ) @@ -453,10 +454,7 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) { return } - if err := models.CreateOrUpdateIssueNotifications(issue); err != nil { - ctx.Handle(500, "CreateOrUpdateIssueNotifications", err) - return - } + notification.Service.NotifyIssue(issue) log.Trace("Issue created: %d/%d", repo.ID, issue.ID) ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index)) @@ -903,10 +901,7 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) { return } - if err := models.CreateOrUpdateIssueNotifications(issue); err != nil { - ctx.Handle(500, "CreateOrUpdateIssueNotifications", err) - return - } + notification.Service.NotifyIssue(issue) log.Trace("Comment created: %d/%d/%d", ctx.Repo.Repository.ID, issue.ID, comment.ID) } From fedf445f6a7b94431c55e277528a21c3518b742e Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Tue, 6 Dec 2016 21:13:19 -0200 Subject: [PATCH 08/14] Notification item on menu --- conf/locale/locale_en-US.ini | 1 + conf/locale/locale_pt-BR.ini | 1 + templates/base/head.tmpl | 6 ++++++ 3 files changed, 8 insertions(+) diff --git a/conf/locale/locale_en-US.ini b/conf/locale/locale_en-US.ini index 654498cd788dd..3ecd1b3e8c205 100644 --- a/conf/locale/locale_en-US.ini +++ b/conf/locale/locale_en-US.ini @@ -13,6 +13,7 @@ version = Version page = Page template = Template language = Language +notifications = Notifications create_new = Create... user_profile_and_more = User profile and more signed_in_as = Signed in as diff --git a/conf/locale/locale_pt-BR.ini b/conf/locale/locale_pt-BR.ini index f0349b1119a6e..99aaa4572f5ac 100644 --- a/conf/locale/locale_pt-BR.ini +++ b/conf/locale/locale_pt-BR.ini @@ -13,6 +13,7 @@ version=Versão page=Página template=Template language=Idioma +notifications = Notificações create_new=Criar... user_profile_and_more=Perfil do usuário e configurações signed_in_as=Logado como diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index 65a8fd3370c01..0646627b02946 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -75,6 +75,12 @@ {{if .IsSigned}}