Signed-off-by: oppiliappan me@oppi.li
-1
appview/db/artifact.go
-1
appview/db/artifact.go
+81
-45
appview/db/notifications.go
+81
-45
appview/db/notifications.go
···
8
8
"strings"
9
9
"time"
10
10
11
+
"github.com/bluesky-social/indigo/atproto/syntax"
11
12
"tangled.org/core/appview/models"
12
13
"tangled.org/core/appview/pagination"
13
14
)
14
15
15
-
func (d *DB) CreateNotification(ctx context.Context, notification *models.Notification) error {
16
+
func CreateNotification(e Execer, notification *models.Notification) error {
16
17
query := `
17
18
INSERT INTO notifications (recipient_did, actor_did, type, entity_type, entity_id, read, repo_id, issue_id, pull_id)
18
19
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
19
20
`
20
21
21
-
result, err := d.DB.ExecContext(ctx, query,
22
+
result, err := e.Exec(query,
22
23
notification.RecipientDid,
23
24
notification.ActorDid,
24
25
string(notification.Type),
···
274
275
return count, nil
275
276
}
276
277
277
-
func (d *DB) MarkNotificationRead(ctx context.Context, notificationID int64, userDID string) error {
278
+
func MarkNotificationRead(e Execer, notificationID int64, userDID string) error {
278
279
idFilter := FilterEq("id", notificationID)
279
280
recipientFilter := FilterEq("recipient_did", userDID)
280
281
···
286
287
287
288
args := append(idFilter.Arg(), recipientFilter.Arg()...)
288
289
289
-
result, err := d.DB.ExecContext(ctx, query, args...)
290
+
result, err := e.Exec(query, args...)
290
291
if err != nil {
291
292
return fmt.Errorf("failed to mark notification as read: %w", err)
292
293
}
···
303
304
return nil
304
305
}
305
306
306
-
func (d *DB) MarkAllNotificationsRead(ctx context.Context, userDID string) error {
307
+
func MarkAllNotificationsRead(e Execer, userDID string) error {
307
308
recipientFilter := FilterEq("recipient_did", userDID)
308
309
readFilter := FilterEq("read", 0)
309
310
···
315
316
316
317
args := append(recipientFilter.Arg(), readFilter.Arg()...)
317
318
318
-
_, err := d.DB.ExecContext(ctx, query, args...)
319
+
_, err := e.Exec(query, args...)
319
320
if err != nil {
320
321
return fmt.Errorf("failed to mark all notifications as read: %w", err)
321
322
}
···
323
324
return nil
324
325
}
325
326
326
-
func (d *DB) DeleteNotification(ctx context.Context, notificationID int64, userDID string) error {
327
+
func DeleteNotification(e Execer, notificationID int64, userDID string) error {
327
328
idFilter := FilterEq("id", notificationID)
328
329
recipientFilter := FilterEq("recipient_did", userDID)
329
330
···
334
335
335
336
args := append(idFilter.Arg(), recipientFilter.Arg()...)
336
337
337
-
result, err := d.DB.ExecContext(ctx, query, args...)
338
+
result, err := e.Exec(query, args...)
338
339
if err != nil {
339
340
return fmt.Errorf("failed to delete notification: %w", err)
340
341
}
···
351
352
return nil
352
353
}
353
354
354
-
func (d *DB) GetNotificationPreferences(ctx context.Context, userDID string) (*models.NotificationPreferences, error) {
355
-
userFilter := FilterEq("user_did", userDID)
355
+
func GetNotificationPreference(e Execer, userDid string) (*models.NotificationPreferences, error) {
356
+
prefs, err := GetNotificationPreferences(e, FilterEq("user_did", userDid))
357
+
if err != nil {
358
+
return nil, err
359
+
}
360
+
361
+
p, ok := prefs[syntax.DID(userDid)]
362
+
if !ok {
363
+
return models.DefaultNotificationPreferences(syntax.DID(userDid)), nil
364
+
}
365
+
366
+
return p, nil
367
+
}
368
+
369
+
func GetNotificationPreferences(e Execer, filters ...filter) (map[syntax.DID]*models.NotificationPreferences, error) {
370
+
prefsMap := make(map[syntax.DID]*models.NotificationPreferences)
371
+
372
+
var conditions []string
373
+
var args []any
374
+
for _, filter := range filters {
375
+
conditions = append(conditions, filter.Condition())
376
+
args = append(args, filter.Arg()...)
377
+
}
378
+
379
+
whereClause := ""
380
+
if conditions != nil {
381
+
whereClause = " where " + strings.Join(conditions, " and ")
382
+
}
356
383
357
384
query := fmt.Sprintf(`
358
-
SELECT id, user_did, repo_starred, issue_created, issue_commented, pull_created,
359
-
pull_commented, followed, pull_merged, issue_closed, email_notifications
360
-
FROM notification_preferences
361
-
WHERE %s
362
-
`, userFilter.Condition())
363
-
364
-
var prefs models.NotificationPreferences
365
-
err := d.DB.QueryRowContext(ctx, query, userFilter.Arg()...).Scan(
366
-
&prefs.ID,
367
-
&prefs.UserDid,
368
-
&prefs.RepoStarred,
369
-
&prefs.IssueCreated,
370
-
&prefs.IssueCommented,
371
-
&prefs.PullCreated,
372
-
&prefs.PullCommented,
373
-
&prefs.Followed,
374
-
&prefs.PullMerged,
375
-
&prefs.IssueClosed,
376
-
&prefs.EmailNotifications,
377
-
)
385
+
select
386
+
id,
387
+
user_did,
388
+
repo_starred,
389
+
issue_created,
390
+
issue_commented,
391
+
pull_created,
392
+
pull_commented,
393
+
followed,
394
+
pull_merged,
395
+
issue_closed,
396
+
email_notifications
397
+
from
398
+
notification_preferences
399
+
%s
400
+
`, whereClause)
378
401
402
+
rows, err := e.Query(query, args...)
379
403
if err != nil {
380
-
if err == sql.ErrNoRows {
381
-
return &models.NotificationPreferences{
382
-
UserDid: userDID,
383
-
RepoStarred: true,
384
-
IssueCreated: true,
385
-
IssueCommented: true,
386
-
PullCreated: true,
387
-
PullCommented: true,
388
-
Followed: true,
389
-
PullMerged: true,
390
-
IssueClosed: true,
391
-
EmailNotifications: false,
392
-
}, nil
404
+
return nil, err
405
+
}
406
+
defer rows.Close()
407
+
408
+
for rows.Next() {
409
+
var prefs models.NotificationPreferences
410
+
if err := rows.Scan(
411
+
&prefs.ID,
412
+
&prefs.UserDid,
413
+
&prefs.RepoStarred,
414
+
&prefs.IssueCreated,
415
+
&prefs.IssueCommented,
416
+
&prefs.PullCreated,
417
+
&prefs.PullCommented,
418
+
&prefs.Followed,
419
+
&prefs.PullMerged,
420
+
&prefs.IssueClosed,
421
+
&prefs.EmailNotifications,
422
+
); err != nil {
423
+
return nil, err
393
424
}
394
-
return nil, fmt.Errorf("failed to get notification preferences: %w", err)
425
+
426
+
prefsMap[prefs.UserDid] = &prefs
427
+
}
428
+
429
+
if err := rows.Err(); err != nil {
430
+
return nil, err
395
431
}
396
432
397
-
return &prefs, nil
433
+
return prefsMap, nil
398
434
}
399
435
400
436
func (d *DB) UpdateNotificationPreferences(ctx context.Context, prefs *models.NotificationPreferences) error {
+17
-1
appview/models/notifications.go
+17
-1
appview/models/notifications.go
···
2
2
3
3
import (
4
4
"time"
5
+
6
+
"github.com/bluesky-social/indigo/atproto/syntax"
5
7
)
6
8
7
9
type NotificationType string
···
69
71
70
72
type NotificationPreferences struct {
71
73
ID int64
72
-
UserDid string
74
+
UserDid syntax.DID
73
75
RepoStarred bool
74
76
IssueCreated bool
75
77
IssueCommented bool
···
80
82
IssueClosed bool
81
83
EmailNotifications bool
82
84
}
85
+
func DefaultNotificationPreferences(user syntax.DID) *NotificationPreferences {
86
+
return &NotificationPreferences{
87
+
UserDid: user,
88
+
RepoStarred: true,
89
+
IssueCreated: true,
90
+
IssueCommented: true,
91
+
PullCreated: true,
92
+
PullCommented: true,
93
+
Followed: true,
94
+
PullMerged: true,
95
+
IssueClosed: true,
96
+
EmailNotifications: false,
97
+
}
98
+
}
+4
-4
appview/notifications/notifications.go
+4
-4
appview/notifications/notifications.go
···
76
76
return
77
77
}
78
78
79
-
err = n.db.MarkAllNotificationsRead(r.Context(), user.Did)
79
+
err = db.MarkAllNotificationsRead(n.db, user.Did)
80
80
if err != nil {
81
81
l.Error("failed to mark notifications as read", "err", err)
82
82
}
···
128
128
return
129
129
}
130
130
131
-
err = n.db.MarkNotificationRead(r.Context(), notificationID, userDid)
131
+
err = db.MarkNotificationRead(n.db, notificationID, userDid)
132
132
if err != nil {
133
133
http.Error(w, "Failed to mark notification as read", http.StatusInternalServerError)
134
134
return
···
140
140
func (n *Notifications) markAllRead(w http.ResponseWriter, r *http.Request) {
141
141
userDid := n.oauth.GetDid(r)
142
142
143
-
err := n.db.MarkAllNotificationsRead(r.Context(), userDid)
143
+
err := db.MarkAllNotificationsRead(n.db, userDid)
144
144
if err != nil {
145
145
http.Error(w, "Failed to mark all notifications as read", http.StatusInternalServerError)
146
146
return
···
159
159
return
160
160
}
161
161
162
-
err = n.db.DeleteNotification(r.Context(), notificationID, userDid)
162
+
err = db.DeleteNotification(n.db, notificationID, userDid)
163
163
if err != nil {
164
164
http.Error(w, "Failed to delete notification", http.StatusInternalServerError)
165
165
return
+20
-20
appview/notify/db/db.go
+20
-20
appview/notify/db/db.go
···
42
42
}
43
43
44
44
// check if user wants these notifications
45
-
prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did)
45
+
prefs, err := db.GetNotificationPreference(n.db, repo.Did)
46
46
if err != nil {
47
47
log.Printf("NewStar: failed to get notification preferences for %s: %v", repo.Did, err)
48
48
return
···
59
59
EntityId: string(star.RepoAt),
60
60
RepoId: &repo.Id,
61
61
}
62
-
err = n.db.CreateNotification(ctx, notification)
62
+
err = db.CreateNotification(n.db, notification)
63
63
if err != nil {
64
64
log.Printf("NewStar: failed to create notification: %v", err)
65
65
return
···
81
81
return
82
82
}
83
83
84
-
prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did)
84
+
prefs, err := db.GetNotificationPreference(n.db, repo.Did)
85
85
if err != nil {
86
86
log.Printf("NewIssue: failed to get notification preferences for %s: %v", repo.Did, err)
87
87
return
···
100
100
IssueId: &issue.Id,
101
101
}
102
102
103
-
err = n.db.CreateNotification(ctx, notification)
103
+
err = db.CreateNotification(n.db, notification)
104
104
if err != nil {
105
105
log.Printf("NewIssue: failed to create notification: %v", err)
106
106
return
···
129
129
130
130
// notify issue author (if not the commenter)
131
131
if issue.Did != comment.Did {
132
-
prefs, err := n.db.GetNotificationPreferences(ctx, issue.Did)
132
+
prefs, err := db.GetNotificationPreference(n.db, issue.Did)
133
133
if err == nil && prefs.IssueCommented {
134
134
recipients[issue.Did] = true
135
135
} else if err != nil {
···
139
139
140
140
// notify repo owner (if not the commenter and not already added)
141
141
if repo.Did != comment.Did && repo.Did != issue.Did {
142
-
prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did)
142
+
prefs, err := db.GetNotificationPreference(n.db, repo.Did)
143
143
if err == nil && prefs.IssueCommented {
144
144
recipients[repo.Did] = true
145
145
} else if err != nil {
···
159
159
IssueId: &issue.Id,
160
160
}
161
161
162
-
err = n.db.CreateNotification(ctx, notification)
162
+
err = db.CreateNotification(n.db, notification)
163
163
if err != nil {
164
164
log.Printf("NewIssueComment: failed to create notification for %s: %v", recipientDid, err)
165
165
}
···
167
167
}
168
168
169
169
func (n *databaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) {
170
-
prefs, err := n.db.GetNotificationPreferences(ctx, follow.SubjectDid)
170
+
prefs, err := db.GetNotificationPreference(n.db, follow.SubjectDid)
171
171
if err != nil {
172
172
log.Printf("NewFollow: failed to get notification preferences for %s: %v", follow.SubjectDid, err)
173
173
return
···
184
184
EntityId: follow.UserDid,
185
185
}
186
186
187
-
err = n.db.CreateNotification(ctx, notification)
187
+
err = db.CreateNotification(n.db, notification)
188
188
if err != nil {
189
189
log.Printf("NewFollow: failed to create notification: %v", err)
190
190
return
···
206
206
return
207
207
}
208
208
209
-
prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did)
209
+
prefs, err := db.GetNotificationPreference(n.db, repo.Did)
210
210
if err != nil {
211
211
log.Printf("NewPull: failed to get notification preferences for %s: %v", repo.Did, err)
212
212
return
···
225
225
PullId: func() *int64 { id := int64(pull.ID); return &id }(),
226
226
}
227
227
228
-
err = n.db.CreateNotification(ctx, notification)
228
+
err = db.CreateNotification(n.db, notification)
229
229
if err != nil {
230
230
log.Printf("NewPull: failed to create notification: %v", err)
231
231
return
···
256
256
257
257
// notify pull request author (if not the commenter)
258
258
if pull.OwnerDid != comment.OwnerDid {
259
-
prefs, err := n.db.GetNotificationPreferences(ctx, pull.OwnerDid)
259
+
prefs, err := db.GetNotificationPreference(n.db, pull.OwnerDid)
260
260
if err == nil && prefs.PullCommented {
261
261
recipients[pull.OwnerDid] = true
262
262
} else if err != nil {
···
266
266
267
267
// notify repo owner (if not the commenter and not already added)
268
268
if repo.Did != comment.OwnerDid && repo.Did != pull.OwnerDid {
269
-
prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did)
269
+
prefs, err := db.GetNotificationPreference(n.db, repo.Did)
270
270
if err == nil && prefs.PullCommented {
271
271
recipients[repo.Did] = true
272
272
} else if err != nil {
···
285
285
PullId: func() *int64 { id := int64(pull.ID); return &id }(),
286
286
}
287
287
288
-
err = n.db.CreateNotification(ctx, notification)
288
+
err = db.CreateNotification(n.db, notification)
289
289
if err != nil {
290
290
log.Printf("NewPullComment: failed to create notification for %s: %v", recipientDid, err)
291
291
}
···
322
322
}
323
323
324
324
// Check if user wants these notifications
325
-
prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did)
325
+
prefs, err := db.GetNotificationPreference(n.db, repo.Did)
326
326
if err != nil {
327
327
log.Printf("NewIssueClosed: failed to get notification preferences for %s: %v", repo.Did, err)
328
328
return
···
341
341
IssueId: &issue.Id,
342
342
}
343
343
344
-
err = n.db.CreateNotification(ctx, notification)
344
+
err = db.CreateNotification(n.db, notification)
345
345
if err != nil {
346
346
log.Printf("NewIssueClosed: failed to create notification: %v", err)
347
347
return
···
362
362
}
363
363
364
364
// Check if user wants these notifications
365
-
prefs, err := n.db.GetNotificationPreferences(ctx, pull.OwnerDid)
365
+
prefs, err := db.GetNotificationPreference(n.db, pull.OwnerDid)
366
366
if err != nil {
367
367
log.Printf("NewPullMerged: failed to get notification preferences for %s: %v", pull.OwnerDid, err)
368
368
return
···
381
381
PullId: func() *int64 { id := int64(pull.ID); return &id }(),
382
382
}
383
383
384
-
err = n.db.CreateNotification(ctx, notification)
384
+
err = db.CreateNotification(n.db, notification)
385
385
if err != nil {
386
386
log.Printf("NewPullMerged: failed to create notification: %v", err)
387
387
return
···
402
402
}
403
403
404
404
// Check if user wants these notifications - reuse pull_merged preference for now
405
-
prefs, err := n.db.GetNotificationPreferences(ctx, pull.OwnerDid)
405
+
prefs, err := db.GetNotificationPreference(n.db, pull.OwnerDid)
406
406
if err != nil {
407
407
log.Printf("NewPullClosed: failed to get notification preferences for %s: %v", pull.OwnerDid, err)
408
408
return
···
421
421
PullId: func() *int64 { id := int64(pull.ID); return &id }(),
422
422
}
423
423
424
-
err = n.db.CreateNotification(ctx, notification)
424
+
err = db.CreateNotification(n.db, notification)
425
425
if err != nil {
426
426
log.Printf("NewPullClosed: failed to create notification: %v", err)
427
427
return
+3
-2
appview/settings/settings.go
+3
-2
appview/settings/settings.go
···
22
22
"tangled.org/core/tid"
23
23
24
24
comatproto "github.com/bluesky-social/indigo/api/atproto"
25
+
"github.com/bluesky-social/indigo/atproto/syntax"
25
26
lexutil "github.com/bluesky-social/indigo/lex/util"
26
27
"github.com/gliderlabs/ssh"
27
28
"github.com/google/uuid"
···
91
92
user := s.OAuth.GetUser(r)
92
93
did := s.OAuth.GetDid(r)
93
94
94
-
prefs, err := s.Db.GetNotificationPreferences(r.Context(), did)
95
+
prefs, err := db.GetNotificationPreference(s.Db, did)
95
96
if err != nil {
96
97
log.Printf("failed to get notification preferences: %s", err)
97
98
s.Pages.Notice(w, "settings-notifications-error", "Unable to load notification preferences.")
···
110
111
did := s.OAuth.GetDid(r)
111
112
112
113
prefs := &models.NotificationPreferences{
113
-
UserDid: did,
114
+
UserDid: syntax.DID(did),
114
115
RepoStarred: r.FormValue("repo_starred") == "on",
115
116
IssueCreated: r.FormValue("issue_created") == "on",
116
117
IssueCommented: r.FormValue("issue_commented") == "on",