THIS IS A TEST INSTANCE ONLY! REPOSITORIES CAN BE DELETED AT ANY TIME!

Browse Source

Notification - Step 1 (#523)

* Notification - Step 1

* Add copyright headers

* Cache issue and repository on notification model
tags/v1.1.0
Andrey Nering 2 years ago
parent
commit
42904cb98a
5 changed files with 349 additions and 11 deletions
  1. 10
    2
      models/issue.go
  2. 35
    9
      models/models.go
  3. 249
    0
      models/notification.go
  4. 50
    0
      modules/notification/notification.go
  5. 5
    0
      routers/repo/issue.go

+ 10
- 2
models/issue.go View File

@@ -443,8 +443,16 @@ func (issue *Issue) GetAssignee() (err error) {
443 443
 }
444 444
 
445 445
 // 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
448 456
 }
449 457
 
450 458
 func updateIssueCols(e Engine, issue *Issue, cols ...string) error {

+ 35
- 9
models/models.go View File

@@ -71,15 +71,41 @@ var (
71 71
 
72 72
 func init() {
73 73
 	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
+	)
83 109
 
84 110
 	gonicNames := []string{"SSL", "UID"}
85 111
 	for _, name := range gonicNames {

+ 249
- 0
models/notification.go View File

@@ -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
+}

+ 50
- 0
modules/notification/notification.go View File

@@ -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
+}

+ 5
- 0
routers/repo/issue.go View File

@@ -24,6 +24,7 @@ import (
24 24
 	"code.gitea.io/gitea/modules/context"
25 25
 	"code.gitea.io/gitea/modules/log"
26 26
 	"code.gitea.io/gitea/modules/markdown"
27
+	"code.gitea.io/gitea/modules/notification"
27 28
 	"code.gitea.io/gitea/modules/setting"
28 29
 )
29 30
 
@@ -467,6 +468,8 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
467 468
 		return
468 469
 	}
469 470
 
471
+	notification.Service.NotifyIssue(issue, ctx.User.ID)
472
+
470 473
 	log.Trace("Issue created: %d/%d", repo.ID, issue.ID)
471 474
 	ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index))
472 475
 }
@@ -931,6 +934,8 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
931 934
 		return
932 935
 	}
933 936
 
937
+	notification.Service.NotifyIssue(issue, ctx.User.ID)
938
+
934 939
 	log.Trace("Comment created: %d/%d/%d", ctx.Repo.Repository.ID, issue.ID, comment.ID)
935 940
 }
936 941
 

Loading…
Cancel
Save