@@ -6,8 +6,14 @@ package models
6
6
7
7
import (
8
8
"fmt"
9
+ "path"
9
10
11
+ "code.gitea.io/gitea/modules/setting"
12
+ api "code.gitea.io/gitea/modules/structs"
10
13
"code.gitea.io/gitea/modules/timeutil"
14
+
15
+ "xorm.io/builder"
16
+ "xorm.io/xorm"
11
17
)
12
18
13
19
type (
@@ -47,17 +53,67 @@ type Notification struct {
47
53
IssueID int64 `xorm:"INDEX NOT NULL"`
48
54
CommitID string `xorm:"INDEX"`
49
55
CommentID int64
50
- Comment * Comment `xorm:"-"`
51
56
52
57
UpdatedBy int64 `xorm:"INDEX NOT NULL"`
53
58
54
59
Issue * Issue `xorm:"-"`
55
60
Repository * Repository `xorm:"-"`
61
+ Comment * Comment `xorm:"-"`
62
+ User * User `xorm:"-"`
56
63
57
64
CreatedUnix timeutil.TimeStamp `xorm:"created INDEX NOT NULL"`
58
65
UpdatedUnix timeutil.TimeStamp `xorm:"updated INDEX NOT NULL"`
59
66
}
60
67
68
+ // FindNotificationOptions represent the filters for notifications. If an ID is 0 it will be ignored.
69
+ type FindNotificationOptions struct {
70
+ UserID int64
71
+ RepoID int64
72
+ IssueID int64
73
+ Status NotificationStatus
74
+ UpdatedAfterUnix int64
75
+ UpdatedBeforeUnix int64
76
+ }
77
+
78
+ // ToCond will convert each condition into a xorm-Cond
79
+ func (opts * FindNotificationOptions ) ToCond () builder.Cond {
80
+ cond := builder .NewCond ()
81
+ if opts .UserID != 0 {
82
+ cond = cond .And (builder.Eq {"notification.user_id" : opts .UserID })
83
+ }
84
+ if opts .RepoID != 0 {
85
+ cond = cond .And (builder.Eq {"notification.repo_id" : opts .RepoID })
86
+ }
87
+ if opts .IssueID != 0 {
88
+ cond = cond .And (builder.Eq {"notification.issue_id" : opts .IssueID })
89
+ }
90
+ if opts .Status != 0 {
91
+ cond = cond .And (builder.Eq {"notification.status" : opts .Status })
92
+ }
93
+ if opts .UpdatedAfterUnix != 0 {
94
+ cond = cond .And (builder.Gte {"notification.updated_unix" : opts .UpdatedAfterUnix })
95
+ }
96
+ if opts .UpdatedBeforeUnix != 0 {
97
+ cond = cond .And (builder.Lte {"notification.updated_unix" : opts .UpdatedBeforeUnix })
98
+ }
99
+ return cond
100
+ }
101
+
102
+ // ToSession will convert the given options to a xorm Session by using the conditions from ToCond and joining with issue table if required
103
+ func (opts * FindNotificationOptions ) ToSession (e Engine ) * xorm.Session {
104
+ return e .Where (opts .ToCond ())
105
+ }
106
+
107
+ func getNotifications (e Engine , options FindNotificationOptions ) (nl NotificationList , err error ) {
108
+ err = options .ToSession (e ).OrderBy ("notification.updated_unix DESC" ).Find (& nl )
109
+ return
110
+ }
111
+
112
+ // GetNotifications returns all notifications that fit to the given options.
113
+ func GetNotifications (opts FindNotificationOptions ) (NotificationList , error ) {
114
+ return getNotifications (x , opts )
115
+ }
116
+
61
117
// CreateOrUpdateIssueNotifications creates an issue notification
62
118
// for each watcher, or updates it if already exists
63
119
func CreateOrUpdateIssueNotifications (issueID , commentID int64 , notificationAuthorID int64 ) error {
@@ -238,21 +294,125 @@ func notificationsForUser(e Engine, user *User, statuses []NotificationStatus, p
238
294
return
239
295
}
240
296
297
+ // APIFormat converts a Notification to api.NotificationThread
298
+ func (n * Notification ) APIFormat () * api.NotificationThread {
299
+ result := & api.NotificationThread {
300
+ ID : n .ID ,
301
+ Unread : ! (n .Status == NotificationStatusRead || n .Status == NotificationStatusPinned ),
302
+ Pinned : n .Status == NotificationStatusPinned ,
303
+ UpdatedAt : n .UpdatedUnix .AsTime (),
304
+ URL : n .APIURL (),
305
+ }
306
+
307
+ //since user only get notifications when he has access to use minimal access mode
308
+ if n .Repository != nil {
309
+ result .Repository = n .Repository .APIFormat (AccessModeRead )
310
+ }
311
+
312
+ //handle Subject
313
+ switch n .Source {
314
+ case NotificationSourceIssue :
315
+ result .Subject = & api.NotificationSubject {Type : "Issue" }
316
+ if n .Issue != nil {
317
+ result .Subject .Title = n .Issue .Title
318
+ result .Subject .URL = n .Issue .APIURL ()
319
+ comment , err := n .Issue .GetLastComment ()
320
+ if err == nil && comment != nil {
321
+ result .Subject .LatestCommentURL = comment .APIURL ()
322
+ }
323
+ }
324
+ case NotificationSourcePullRequest :
325
+ result .Subject = & api.NotificationSubject {Type : "Pull" }
326
+ if n .Issue != nil {
327
+ result .Subject .Title = n .Issue .Title
328
+ result .Subject .URL = n .Issue .APIURL ()
329
+ comment , err := n .Issue .GetLastComment ()
330
+ if err == nil && comment != nil {
331
+ result .Subject .LatestCommentURL = comment .APIURL ()
332
+ }
333
+ }
334
+ case NotificationSourceCommit :
335
+ result .Subject = & api.NotificationSubject {
336
+ Type : "Commit" ,
337
+ Title : n .CommitID ,
338
+ }
339
+ //unused until now
340
+ }
341
+
342
+ return result
343
+ }
344
+
345
+ // LoadAttributes load Repo Issue User and Comment if not loaded
346
+ func (n * Notification ) LoadAttributes () (err error ) {
347
+ return n .loadAttributes (x )
348
+ }
349
+
350
+ func (n * Notification ) loadAttributes (e Engine ) (err error ) {
351
+ if err = n .loadRepo (e ); err != nil {
352
+ return
353
+ }
354
+ if err = n .loadIssue (e ); err != nil {
355
+ return
356
+ }
357
+ if err = n .loadUser (e ); err != nil {
358
+ return
359
+ }
360
+ if err = n .loadComment (e ); err != nil {
361
+ return
362
+ }
363
+ return
364
+ }
365
+
366
+ func (n * Notification ) loadRepo (e Engine ) (err error ) {
367
+ if n .Repository == nil {
368
+ n .Repository , err = getRepositoryByID (e , n .RepoID )
369
+ if err != nil {
370
+ return fmt .Errorf ("getRepositoryByID [%d]: %v" , n .RepoID , err )
371
+ }
372
+ }
373
+ return nil
374
+ }
375
+
376
+ func (n * Notification ) loadIssue (e Engine ) (err error ) {
377
+ if n .Issue == nil {
378
+ n .Issue , err = getIssueByID (e , n .IssueID )
379
+ if err != nil {
380
+ return fmt .Errorf ("getIssueByID [%d]: %v" , n .IssueID , err )
381
+ }
382
+ return n .Issue .loadAttributes (e )
383
+ }
384
+ return nil
385
+ }
386
+
387
+ func (n * Notification ) loadComment (e Engine ) (err error ) {
388
+ if n .Comment == nil && n .CommentID > 0 {
389
+ n .Comment , err = GetCommentByID (n .CommentID )
390
+ if err != nil {
391
+ return fmt .Errorf ("GetCommentByID [%d]: %v" , n .CommentID , err )
392
+ }
393
+ }
394
+ return nil
395
+ }
396
+
397
+ func (n * Notification ) loadUser (e Engine ) (err error ) {
398
+ if n .User == nil {
399
+ n .User , err = getUserByID (e , n .UserID )
400
+ if err != nil {
401
+ return fmt .Errorf ("getUserByID [%d]: %v" , n .UserID , err )
402
+ }
403
+ }
404
+ return nil
405
+ }
406
+
241
407
// GetRepo returns the repo of the notification
242
408
func (n * Notification ) GetRepo () (* Repository , error ) {
243
- n .Repository = new (Repository )
244
- _ , err := x .
245
- Where ("id = ?" , n .RepoID ).
246
- Get (n .Repository )
409
+ err := n .loadRepo (x )
247
410
return n .Repository , err
248
411
}
249
412
250
413
// GetIssue returns the issue of the notification
251
414
func (n * Notification ) GetIssue () (* Issue , error ) {
252
- n .Issue = new (Issue )
253
- _ , err := x .
254
- Where ("id = ?" , n .IssueID ).
255
- Get (n .Issue )
415
+ err := n .loadIssue (x )
256
416
return n .Issue , err
257
417
}
258
418
@@ -264,9 +424,34 @@ func (n *Notification) HTMLURL() string {
264
424
return n .Issue .HTMLURL ()
265
425
}
266
426
427
+ // APIURL formats a URL-string to the notification
428
+ func (n * Notification ) APIURL () string {
429
+ return setting .AppURL + path .Join ("api/v1/notifications/threads" , fmt .Sprintf ("%d" , n .ID ))
430
+ }
431
+
267
432
// NotificationList contains a list of notifications
268
433
type NotificationList []* Notification
269
434
435
+ // APIFormat converts a NotificationList to api.NotificationThread list
436
+ func (nl NotificationList ) APIFormat () []* api.NotificationThread {
437
+ var result = make ([]* api.NotificationThread , 0 , len (nl ))
438
+ for _ , n := range nl {
439
+ result = append (result , n .APIFormat ())
440
+ }
441
+ return result
442
+ }
443
+
444
+ // LoadAttributes load Repo Issue User and Comment if not loaded
445
+ func (nl NotificationList ) LoadAttributes () (err error ) {
446
+ for i := 0 ; i < len (nl ); i ++ {
447
+ err = nl [i ].LoadAttributes ()
448
+ if err != nil {
449
+ return
450
+ }
451
+ }
452
+ return
453
+ }
454
+
270
455
func (nl NotificationList ) getPendingRepoIDs () []int64 {
271
456
var ids = make (map [int64 ]struct {}, len (nl ))
272
457
for _ , notification := range nl {
@@ -486,7 +671,7 @@ func setNotificationStatusReadIfUnread(e Engine, userID, issueID int64) error {
486
671
487
672
// SetNotificationStatus change the notification status
488
673
func SetNotificationStatus (notificationID int64 , user * User , status NotificationStatus ) error {
489
- notification , err := getNotificationByID (notificationID )
674
+ notification , err := getNotificationByID (x , notificationID )
490
675
if err != nil {
491
676
return err
492
677
}
@@ -501,9 +686,14 @@ func SetNotificationStatus(notificationID int64, user *User, status Notification
501
686
return err
502
687
}
503
688
504
- func getNotificationByID (notificationID int64 ) (* Notification , error ) {
689
+ // GetNotificationByID return notification by ID
690
+ func GetNotificationByID (notificationID int64 ) (* Notification , error ) {
691
+ return getNotificationByID (x , notificationID )
692
+ }
693
+
694
+ func getNotificationByID (e Engine , notificationID int64 ) (* Notification , error ) {
505
695
notification := new (Notification )
506
- ok , err := x .
696
+ ok , err := e .
507
697
Where ("id = ?" , notificationID ).
508
698
Get (notification )
509
699
@@ -512,7 +702,7 @@ func getNotificationByID(notificationID int64) (*Notification, error) {
512
702
}
513
703
514
704
if ! ok {
515
- return nil , fmt . Errorf ( "Notification %d does not exists" , notificationID )
705
+ return nil , ErrNotExist { ID : notificationID }
516
706
}
517
707
518
708
return notification , nil
0 commit comments