Signed-off-by: oppiliappan me@oppi.li
+63
-53
appview/notify/db/db.go
+63
-53
appview/notify/db/db.go
···
3
import (
4
"context"
5
"log"
6
-
"maps"
7
"slices"
8
9
"github.com/bluesky-social/indigo/atproto/syntax"
···
12
"tangled.org/core/appview/models"
13
"tangled.org/core/appview/notify"
14
"tangled.org/core/idresolver"
15
)
16
17
const (
18
-
maxMentions = 5
19
)
20
21
type databaseNotifier struct {
···
49
}
50
51
actorDid := syntax.DID(star.Did)
52
-
recipients := []syntax.DID{syntax.DID(repo.Did)}
53
eventType := models.NotificationTypeRepoStarred
54
entityType := "repo"
55
entityId := star.RepoAt.String()
···
74
}
75
76
func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) {
77
-
78
-
// build the recipients list
79
-
// - owner of the repo
80
-
// - collaborators in the repo
81
-
var recipients []syntax.DID
82
-
recipients = append(recipients, syntax.DID(issue.Repo.Did))
83
collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", issue.Repo.RepoAt()))
84
if err != nil {
85
log.Printf("failed to fetch collaborators: %v", err)
86
return
87
}
88
for _, c := range collaborators {
89
-
recipients = append(recipients, c.SubjectDid)
90
}
91
92
actorDid := syntax.DID(issue.Did)
···
108
)
109
n.notifyEvent(
110
actorDid,
111
-
mentions,
112
models.NotificationTypeUserMentioned,
113
entityType,
114
entityId,
···
130
}
131
issue := issues[0]
132
133
-
var recipients []syntax.DID
134
-
recipients = append(recipients, syntax.DID(issue.Repo.Did))
135
136
if comment.IsReply() {
137
// if this comment is a reply, then notify everybody in that thread
138
parentAtUri := *comment.ReplyTo
139
-
allThreads := issue.CommentList()
140
141
// find the parent thread, and add all DIDs from here to the recipient list
142
-
for _, t := range allThreads {
143
if t.Self.AtUri().String() == parentAtUri {
144
-
recipients = append(recipients, t.Participants()...)
145
}
146
}
147
} else {
148
// not a reply, notify just the issue author
149
-
recipients = append(recipients, syntax.DID(issue.Did))
150
}
151
152
actorDid := syntax.DID(comment.Did)
···
168
)
169
n.notifyEvent(
170
actorDid,
171
-
mentions,
172
models.NotificationTypeUserMentioned,
173
entityType,
174
entityId,
···
184
185
func (n *databaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) {
186
actorDid := syntax.DID(follow.UserDid)
187
-
recipients := []syntax.DID{syntax.DID(follow.SubjectDid)}
188
eventType := models.NotificationTypeFollowed
189
entityType := "follow"
190
entityId := follow.UserDid
···
216
// build the recipients list
217
// - owner of the repo
218
// - collaborators in the repo
219
-
var recipients []syntax.DID
220
-
recipients = append(recipients, syntax.DID(repo.Did))
221
collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", repo.RepoAt()))
222
if err != nil {
223
log.Printf("failed to fetch collaborators: %v", err)
224
return
225
}
226
for _, c := range collaborators {
227
-
recipients = append(recipients, c.SubjectDid)
228
}
229
230
actorDid := syntax.DID(pull.OwnerDid)
···
267
// build up the recipients list:
268
// - repo owner
269
// - all pull participants
270
-
var recipients []syntax.DID
271
-
recipients = append(recipients, syntax.DID(repo.Did))
272
for _, p := range pull.Participants() {
273
-
recipients = append(recipients, syntax.DID(p))
274
}
275
276
actorDid := syntax.DID(comment.OwnerDid)
···
294
)
295
n.notifyEvent(
296
actorDid,
297
-
mentions,
298
models.NotificationTypeUserMentioned,
299
entityType,
300
entityId,
···
321
}
322
323
func (n *databaseNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) {
324
-
// build up the recipients list:
325
-
// - repo owner
326
-
// - repo collaborators
327
-
// - all issue participants
328
-
var recipients []syntax.DID
329
-
recipients = append(recipients, syntax.DID(issue.Repo.Did))
330
collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", issue.Repo.RepoAt()))
331
if err != nil {
332
log.Printf("failed to fetch collaborators: %v", err)
333
return
334
}
335
for _, c := range collaborators {
336
-
recipients = append(recipients, c.SubjectDid)
337
}
338
for _, p := range issue.Participants() {
339
-
recipients = append(recipients, syntax.DID(p))
340
}
341
342
entityType := "pull"
···
372
return
373
}
374
375
-
// build up the recipients list:
376
-
// - repo owner
377
-
// - all pull participants
378
-
var recipients []syntax.DID
379
-
recipients = append(recipients, syntax.DID(repo.Did))
380
collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", repo.RepoAt()))
381
if err != nil {
382
log.Printf("failed to fetch collaborators: %v", err)
383
return
384
}
385
for _, c := range collaborators {
386
-
recipients = append(recipients, c.SubjectDid)
387
}
388
for _, p := range pull.Participants() {
389
-
recipients = append(recipients, syntax.DID(p))
390
}
391
392
entityType := "pull"
···
422
423
func (n *databaseNotifier) notifyEvent(
424
actorDid syntax.DID,
425
-
recipients []syntax.DID,
426
eventType models.NotificationType,
427
entityType string,
428
entityId string,
···
430
issueId *int64,
431
pullId *int64,
432
) {
433
-
if eventType == models.NotificationTypeUserMentioned && len(recipients) > maxMentions {
434
-
recipients = recipients[:maxMentions]
435
-
}
436
-
recipientSet := make(map[syntax.DID]struct{})
437
-
for _, did := range recipients {
438
-
// everybody except actor themselves
439
-
if did != actorDid {
440
-
recipientSet[did] = struct{}{}
441
-
}
442
}
443
444
prefMap, err := db.GetNotificationPreferences(
445
n.db,
446
-
db.FilterIn("user_did", slices.Collect(maps.Keys(recipientSet))),
447
)
448
if err != nil {
449
// failed to get prefs for users
···
459
defer tx.Rollback()
460
461
// filter based on preferences
462
-
for recipientDid := range recipientSet {
463
prefs, ok := prefMap[recipientDid]
464
if !ok {
465
prefs = models.DefaultNotificationPreferences(recipientDid)
···
3
import (
4
"context"
5
"log"
6
"slices"
7
8
"github.com/bluesky-social/indigo/atproto/syntax"
···
11
"tangled.org/core/appview/models"
12
"tangled.org/core/appview/notify"
13
"tangled.org/core/idresolver"
14
+
"tangled.org/oppi.li/sets"
15
)
16
17
const (
18
+
maxMentions = 8
19
)
20
21
type databaseNotifier struct {
···
49
}
50
51
actorDid := syntax.DID(star.Did)
52
+
recipients := sets.Singleton(syntax.DID(repo.Did))
53
eventType := models.NotificationTypeRepoStarred
54
entityType := "repo"
55
entityId := star.RepoAt.String()
···
74
}
75
76
func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) {
77
collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", issue.Repo.RepoAt()))
78
if err != nil {
79
log.Printf("failed to fetch collaborators: %v", err)
80
return
81
}
82
+
83
+
// build the recipients list
84
+
// - owner of the repo
85
+
// - collaborators in the repo
86
+
// - remove users already mentioned
87
+
recipients := sets.Singleton(syntax.DID(issue.Repo.Did))
88
for _, c := range collaborators {
89
+
recipients.Insert(c.SubjectDid)
90
+
}
91
+
for _, m := range mentions {
92
+
recipients.Remove(m)
93
}
94
95
actorDid := syntax.DID(issue.Did)
···
111
)
112
n.notifyEvent(
113
actorDid,
114
+
sets.Collect(slices.Values(mentions)),
115
models.NotificationTypeUserMentioned,
116
entityType,
117
entityId,
···
133
}
134
issue := issues[0]
135
136
+
// built the recipients list:
137
+
// - the owner of the repo
138
+
// - | if the comment is a reply -> everybody on that thread
139
+
// | if the comment is a top level -> just the issue owner
140
+
// - remove mentioned users from the recipients list
141
+
recipients := sets.Singleton(syntax.DID(issue.Repo.Did))
142
143
if comment.IsReply() {
144
// if this comment is a reply, then notify everybody in that thread
145
parentAtUri := *comment.ReplyTo
146
147
// find the parent thread, and add all DIDs from here to the recipient list
148
+
for _, t := range issue.CommentList() {
149
if t.Self.AtUri().String() == parentAtUri {
150
+
for _, p := range t.Participants() {
151
+
recipients.Insert(p)
152
+
}
153
}
154
}
155
} else {
156
// not a reply, notify just the issue author
157
+
recipients.Insert(syntax.DID(issue.Did))
158
+
}
159
+
160
+
for _, m := range mentions {
161
+
recipients.Remove(m)
162
}
163
164
actorDid := syntax.DID(comment.Did)
···
180
)
181
n.notifyEvent(
182
actorDid,
183
+
sets.Collect(slices.Values(mentions)),
184
models.NotificationTypeUserMentioned,
185
entityType,
186
entityId,
···
196
197
func (n *databaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) {
198
actorDid := syntax.DID(follow.UserDid)
199
+
recipients := sets.Singleton(syntax.DID(follow.SubjectDid))
200
eventType := models.NotificationTypeFollowed
201
entityType := "follow"
202
entityId := follow.UserDid
···
228
// build the recipients list
229
// - owner of the repo
230
// - collaborators in the repo
231
+
recipients := sets.Singleton(syntax.DID(repo.Did))
232
collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", repo.RepoAt()))
233
if err != nil {
234
log.Printf("failed to fetch collaborators: %v", err)
235
return
236
}
237
for _, c := range collaborators {
238
+
recipients.Insert(c.SubjectDid)
239
}
240
241
actorDid := syntax.DID(pull.OwnerDid)
···
278
// build up the recipients list:
279
// - repo owner
280
// - all pull participants
281
+
// - remove those already mentioned
282
+
recipients := sets.Singleton(syntax.DID(repo.Did))
283
for _, p := range pull.Participants() {
284
+
recipients.Insert(syntax.DID(p))
285
+
}
286
+
for _, m := range mentions {
287
+
recipients.Remove(m)
288
}
289
290
actorDid := syntax.DID(comment.OwnerDid)
···
308
)
309
n.notifyEvent(
310
actorDid,
311
+
sets.Collect(slices.Values(mentions)),
312
models.NotificationTypeUserMentioned,
313
entityType,
314
entityId,
···
335
}
336
337
func (n *databaseNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) {
338
collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", issue.Repo.RepoAt()))
339
if err != nil {
340
log.Printf("failed to fetch collaborators: %v", err)
341
return
342
}
343
+
344
+
// build up the recipients list:
345
+
// - repo owner
346
+
// - repo collaborators
347
+
// - all issue participants
348
+
recipients := sets.Singleton(syntax.DID(issue.Repo.Did))
349
for _, c := range collaborators {
350
+
recipients.Insert(c.SubjectDid)
351
}
352
for _, p := range issue.Participants() {
353
+
recipients.Insert(syntax.DID(p))
354
}
355
356
entityType := "pull"
···
386
return
387
}
388
389
collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", repo.RepoAt()))
390
if err != nil {
391
log.Printf("failed to fetch collaborators: %v", err)
392
return
393
}
394
+
395
+
// build up the recipients list:
396
+
// - repo owner
397
+
// - all pull participants
398
+
recipients := sets.Singleton(syntax.DID(repo.Did))
399
for _, c := range collaborators {
400
+
recipients.Insert(c.SubjectDid)
401
}
402
for _, p := range pull.Participants() {
403
+
recipients.Insert(syntax.DID(p))
404
}
405
406
entityType := "pull"
···
436
437
func (n *databaseNotifier) notifyEvent(
438
actorDid syntax.DID,
439
+
recipients sets.Set[syntax.DID],
440
eventType models.NotificationType,
441
entityType string,
442
entityId string,
···
444
issueId *int64,
445
pullId *int64,
446
) {
447
+
// if the user is attempting to mention >maxMentions users, this is probably spam, do not mention anybody
448
+
if eventType == models.NotificationTypeUserMentioned && recipients.Len() > maxMentions {
449
+
return
450
}
451
452
+
recipients.Remove(actorDid)
453
+
454
prefMap, err := db.GetNotificationPreferences(
455
n.db,
456
+
db.FilterIn("user_did", slices.Collect(recipients.All())),
457
)
458
if err != nil {
459
// failed to get prefs for users
···
469
defer tx.Rollback()
470
471
// filter based on preferences
472
+
for recipientDid := range recipients.All() {
473
prefs, ok := prefMap[recipientDid]
474
if !ok {
475
prefs = models.DefaultNotificationPreferences(recipientDid)