Skip to content

Commit 42904cb

Browse files
andreyneringlunny
authored andcommitted
Notification - Step 1 (#523)
* Notification - Step 1 * Add copyright headers * Cache issue and repository on notification model
1 parent 37eec6c commit 42904cb

File tree

5 files changed

+349
-11
lines changed

5 files changed

+349
-11
lines changed

models/issue.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -443,8 +443,16 @@ func (issue *Issue) GetAssignee() (err error) {
443443
}
444444

445445
// ReadBy sets issue to be read by given user.
446-
func (issue *Issue) ReadBy(uid int64) error {
447-
return UpdateIssueUserByRead(uid, issue.ID)
446+
func (issue *Issue) ReadBy(userID int64) error {
447+
if err := UpdateIssueUserByRead(userID, issue.ID); err != nil {
448+
return err
449+
}
450+
451+
if err := setNotificationStatusRead(x, userID, issue.ID); err != nil {
452+
return err
453+
}
454+
455+
return nil
448456
}
449457

450458
func updateIssueCols(e Engine, issue *Issue, cols ...string) error {

models/models.go

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,41 @@ var (
7171

7272
func init() {
7373
tables = append(tables,
74-
new(User), new(PublicKey), new(AccessToken),
75-
new(Repository), new(DeployKey), new(Collaboration), new(Access), new(Upload),
76-
new(Watch), new(Star), new(Follow), new(Action),
77-
new(Issue), new(PullRequest), new(Comment), new(Attachment), new(IssueUser),
78-
new(Label), new(IssueLabel), new(Milestone),
79-
new(Mirror), new(Release), new(LoginSource), new(Webhook),
80-
new(UpdateTask), new(HookTask),
81-
new(Team), new(OrgUser), new(TeamUser), new(TeamRepo),
82-
new(Notice), new(EmailAddress), new(LFSMetaObject))
74+
new(User),
75+
new(PublicKey),
76+
new(AccessToken),
77+
new(Repository),
78+
new(DeployKey),
79+
new(Collaboration),
80+
new(Access),
81+
new(Upload),
82+
new(Watch),
83+
new(Star),
84+
new(Follow),
85+
new(Action),
86+
new(Issue),
87+
new(PullRequest),
88+
new(Comment),
89+
new(Attachment),
90+
new(Label),
91+
new(IssueLabel),
92+
new(Milestone),
93+
new(Mirror),
94+
new(Release),
95+
new(LoginSource),
96+
new(Webhook),
97+
new(UpdateTask),
98+
new(HookTask),
99+
new(Team),
100+
new(OrgUser),
101+
new(TeamUser),
102+
new(TeamRepo),
103+
new(Notice),
104+
new(EmailAddress),
105+
new(Notification),
106+
new(IssueUser),
107+
new(LFSMetaObject),
108+
)
83109

84110
gonicNames := []string{"SSL", "UID"}
85111
for _, name := range gonicNames {

models/notification.go

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
// Copyright 2016 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package models
6+
7+
import (
8+
"time"
9+
)
10+
11+
type (
12+
// NotificationStatus is the status of the notification (read or unread)
13+
NotificationStatus uint8
14+
// NotificationSource is the source of the notification (issue, PR, commit, etc)
15+
NotificationSource uint8
16+
)
17+
18+
const (
19+
// NotificationStatusUnread represents an unread notification
20+
NotificationStatusUnread NotificationStatus = iota + 1
21+
// NotificationStatusRead represents a read notification
22+
NotificationStatusRead
23+
)
24+
25+
const (
26+
// NotificationSourceIssue is a notification of an issue
27+
NotificationSourceIssue NotificationSource = iota + 1
28+
// NotificationSourcePullRequest is a notification of a pull request
29+
NotificationSourcePullRequest
30+
// NotificationSourceCommit is a notification of a commit
31+
NotificationSourceCommit
32+
)
33+
34+
// Notification represents a notification
35+
type Notification struct {
36+
ID int64 `xorm:"pk autoincr"`
37+
UserID int64 `xorm:"INDEX NOT NULL"`
38+
RepoID int64 `xorm:"INDEX NOT NULL"`
39+
40+
Status NotificationStatus `xorm:"SMALLINT INDEX NOT NULL"`
41+
Source NotificationSource `xorm:"SMALLINT INDEX NOT NULL"`
42+
43+
IssueID int64 `xorm:"INDEX NOT NULL"`
44+
CommitID string `xorm:"INDEX"`
45+
46+
UpdatedBy int64 `xorm:"INDEX NOT NULL"`
47+
48+
Issue *Issue `xorm:"-"`
49+
Repository *Repository `xorm:"-"`
50+
51+
Created time.Time `xorm:"-"`
52+
CreatedUnix int64 `xorm:"INDEX NOT NULL"`
53+
Updated time.Time `xorm:"-"`
54+
UpdatedUnix int64 `xorm:"INDEX NOT NULL"`
55+
}
56+
57+
// BeforeInsert runs while inserting a record
58+
func (n *Notification) BeforeInsert() {
59+
var (
60+
now = time.Now()
61+
nowUnix = now.Unix()
62+
)
63+
n.Created = now
64+
n.CreatedUnix = nowUnix
65+
n.Updated = now
66+
n.UpdatedUnix = nowUnix
67+
}
68+
69+
// BeforeUpdate runs while updateing a record
70+
func (n *Notification) BeforeUpdate() {
71+
var (
72+
now = time.Now()
73+
nowUnix = now.Unix()
74+
)
75+
n.Updated = now
76+
n.UpdatedUnix = nowUnix
77+
}
78+
79+
// CreateOrUpdateIssueNotifications creates an issue notification
80+
// for each watcher, or updates it if already exists
81+
func CreateOrUpdateIssueNotifications(issue *Issue, notificationAuthorID int64) error {
82+
sess := x.NewSession()
83+
defer sess.Close()
84+
if err := sess.Begin(); err != nil {
85+
return err
86+
}
87+
88+
if err := createOrUpdateIssueNotifications(sess, issue, notificationAuthorID); err != nil {
89+
return err
90+
}
91+
92+
return sess.Commit()
93+
}
94+
95+
func createOrUpdateIssueNotifications(e Engine, issue *Issue, notificationAuthorID int64) error {
96+
watches, err := getWatchers(e, issue.RepoID)
97+
if err != nil {
98+
return err
99+
}
100+
101+
notifications, err := getNotificationsByIssueID(e, issue.ID)
102+
if err != nil {
103+
return err
104+
}
105+
106+
for _, watch := range watches {
107+
// do not send notification for the own issuer/commenter
108+
if watch.UserID == notificationAuthorID {
109+
continue
110+
}
111+
112+
if notificationExists(notifications, issue.ID, watch.UserID) {
113+
err = updateIssueNotification(e, watch.UserID, issue.ID, notificationAuthorID)
114+
} else {
115+
err = createIssueNotification(e, watch.UserID, issue, notificationAuthorID)
116+
}
117+
118+
if err != nil {
119+
return err
120+
}
121+
}
122+
123+
return nil
124+
}
125+
126+
func getNotificationsByIssueID(e Engine, issueID int64) (notifications []*Notification, err error) {
127+
err = e.
128+
Where("issue_id = ?", issueID).
129+
Find(&notifications)
130+
return
131+
}
132+
133+
func notificationExists(notifications []*Notification, issueID, userID int64) bool {
134+
for _, notification := range notifications {
135+
if notification.IssueID == issueID && notification.UserID == userID {
136+
return true
137+
}
138+
}
139+
140+
return false
141+
}
142+
143+
func createIssueNotification(e Engine, userID int64, issue *Issue, updatedByID int64) error {
144+
notification := &Notification{
145+
UserID: userID,
146+
RepoID: issue.RepoID,
147+
Status: NotificationStatusUnread,
148+
IssueID: issue.ID,
149+
UpdatedBy: updatedByID,
150+
}
151+
152+
if issue.IsPull {
153+
notification.Source = NotificationSourcePullRequest
154+
} else {
155+
notification.Source = NotificationSourceIssue
156+
}
157+
158+
_, err := e.Insert(notification)
159+
return err
160+
}
161+
162+
func updateIssueNotification(e Engine, userID, issueID, updatedByID int64) error {
163+
notification, err := getIssueNotification(e, userID, issueID)
164+
if err != nil {
165+
return err
166+
}
167+
168+
notification.Status = NotificationStatusUnread
169+
notification.UpdatedBy = updatedByID
170+
171+
_, err = e.Id(notification.ID).Update(notification)
172+
return err
173+
}
174+
175+
func getIssueNotification(e Engine, userID, issueID int64) (*Notification, error) {
176+
notification := new(Notification)
177+
_, err := e.
178+
Where("user_id = ?", userID).
179+
And("issue_id = ?", issueID).
180+
Get(notification)
181+
return notification, err
182+
}
183+
184+
// NotificationsForUser returns notifications for a given user and status
185+
func NotificationsForUser(user *User, status NotificationStatus) ([]*Notification, error) {
186+
return notificationsForUser(x, user, status)
187+
}
188+
func notificationsForUser(e Engine, user *User, status NotificationStatus) (notifications []*Notification, err error) {
189+
err = e.
190+
Where("user_id = ?", user.ID).
191+
And("status = ?", status).
192+
OrderBy("updated_unix DESC").
193+
Find(&notifications)
194+
return
195+
}
196+
197+
// GetRepo returns the repo of the notification
198+
func (n *Notification) GetRepo() (*Repository, error) {
199+
n.Repository = new(Repository)
200+
_, err := x.
201+
Where("id = ?", n.RepoID).
202+
Get(n.Repository)
203+
return n.Repository, err
204+
}
205+
206+
// GetIssue returns the issue of the notification
207+
func (n *Notification) GetIssue() (*Issue, error) {
208+
n.Issue = new(Issue)
209+
_, err := x.
210+
Where("id = ?", n.IssueID).
211+
Get(n.Issue)
212+
return n.Issue, err
213+
}
214+
215+
// GetNotificationReadCount returns the notification read count for user
216+
func GetNotificationReadCount(user *User) (int64, error) {
217+
return GetNotificationCount(user, NotificationStatusRead)
218+
}
219+
220+
// GetNotificationUnreadCount returns the notification unread count for user
221+
func GetNotificationUnreadCount(user *User) (int64, error) {
222+
return GetNotificationCount(user, NotificationStatusUnread)
223+
}
224+
225+
// GetNotificationCount returns the notification count for user
226+
func GetNotificationCount(user *User, status NotificationStatus) (int64, error) {
227+
return getNotificationCount(x, user, status)
228+
}
229+
230+
func getNotificationCount(e Engine, user *User, status NotificationStatus) (count int64, err error) {
231+
count, err = e.
232+
Where("user_id = ?", user.ID).
233+
And("status = ?", status).
234+
Count(&Notification{})
235+
return
236+
}
237+
238+
func setNotificationStatusRead(e Engine, userID, issueID int64) error {
239+
notification, err := getIssueNotification(e, userID, issueID)
240+
// ignore if not exists
241+
if err != nil {
242+
return nil
243+
}
244+
245+
notification.Status = NotificationStatusRead
246+
247+
_, err = e.Id(notification.ID).Update(notification)
248+
return err
249+
}

modules/notification/notification.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2016 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package notification
6+
7+
import (
8+
"code.gitea.io/gitea/models"
9+
"code.gitea.io/gitea/modules/log"
10+
)
11+
12+
type (
13+
notificationService struct {
14+
issueQueue chan issueNotificationOpts
15+
}
16+
17+
issueNotificationOpts struct {
18+
issue *models.Issue
19+
notificationAuthorID int64
20+
}
21+
)
22+
23+
var (
24+
// Service is the notification service
25+
Service = &notificationService{
26+
issueQueue: make(chan issueNotificationOpts, 100),
27+
}
28+
)
29+
30+
func init() {
31+
go Service.Run()
32+
}
33+
34+
func (ns *notificationService) Run() {
35+
for {
36+
select {
37+
case opts := <-ns.issueQueue:
38+
if err := models.CreateOrUpdateIssueNotifications(opts.issue, opts.notificationAuthorID); err != nil {
39+
log.Error(4, "Was unable to create issue notification: %v", err)
40+
}
41+
}
42+
}
43+
}
44+
45+
func (ns *notificationService) NotifyIssue(issue *models.Issue, notificationAuthorID int64) {
46+
ns.issueQueue <- issueNotificationOpts{
47+
issue,
48+
notificationAuthorID,
49+
}
50+
}

routers/repo/issue.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"code.gitea.io/gitea/modules/context"
2525
"code.gitea.io/gitea/modules/log"
2626
"code.gitea.io/gitea/modules/markdown"
27+
"code.gitea.io/gitea/modules/notification"
2728
"code.gitea.io/gitea/modules/setting"
2829
)
2930

@@ -467,6 +468,8 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
467468
return
468469
}
469470

471+
notification.Service.NotifyIssue(issue, ctx.User.ID)
472+
470473
log.Trace("Issue created: %d/%d", repo.ID, issue.ID)
471474
ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index))
472475
}
@@ -931,6 +934,8 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
931934
return
932935
}
933936

937+
notification.Service.NotifyIssue(issue, ctx.User.ID)
938+
934939
log.Trace("Comment created: %d/%d/%d", ctx.Repo.Repository.ID, issue.ID, comment.ID)
935940
}
936941

0 commit comments

Comments
 (0)