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