···
9
+
"github.com/bluesky-social/indigo/atproto/syntax"
"tangled.org/core/appview/db"
"tangled.org/core/appview/models"
"tangled.org/core/appview/notify"
···
39
-
// don't notify yourself
40
-
if repo.Did == star.StarredByDid {
44
-
// check if user wants these notifications
45
-
prefs, err := db.GetNotificationPreference(n.db, repo.Did)
47
-
log.Printf("NewStar: failed to get notification preferences for %s: %v", repo.Did, err)
50
-
if !prefs.RepoStarred {
54
-
notification := &models.Notification{
55
-
RecipientDid: repo.Did,
56
-
ActorDid: star.StarredByDid,
57
-
Type: models.NotificationTypeRepoStarred,
59
-
EntityId: string(star.RepoAt),
62
-
err = db.CreateNotification(n.db, notification)
64
-
log.Printf("NewStar: failed to create notification: %v", err)
42
+
actorDid := syntax.DID(star.StarredByDid)
43
+
recipients := []syntax.DID{syntax.DID(repo.Did)}
44
+
eventType := models.NotificationTypeRepoStarred
45
+
entityType := "repo"
46
+
entityId := star.RepoAt.String()
func (n *databaseNotifier) DeleteStar(ctx context.Context, star *models.Star) {
···
func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue) {
74
-
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(issue.RepoAt)))
76
-
log.Printf("NewIssue: failed to get repos: %v", err)
80
-
if repo.Did == issue.Did {
84
-
prefs, err := db.GetNotificationPreference(n.db, repo.Did)
69
+
// build the recipients list
70
+
// - owner of the repo
71
+
// - collaborators in the repo
72
+
var recipients []syntax.DID
73
+
recipients = append(recipients, syntax.DID(issue.Repo.Did))
74
+
collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", issue.Repo.RepoAt()))
86
-
log.Printf("NewIssue: failed to get notification preferences for %s: %v", repo.Did, err)
89
-
if !prefs.IssueCreated {
93
-
notification := &models.Notification{
94
-
RecipientDid: repo.Did,
95
-
ActorDid: issue.Did,
96
-
Type: models.NotificationTypeIssueCreated,
97
-
EntityType: "issue",
98
-
EntityId: string(issue.AtUri()),
100
-
IssueId: &issue.Id,
103
-
err = db.CreateNotification(n.db, notification)
105
-
log.Printf("NewIssue: failed to create notification: %v", err)
76
+
log.Printf("failed to fetch collaborators: %v", err)
79
+
for _, c := range collaborators {
80
+
recipients = append(recipients, c.SubjectDid)
83
+
actorDid := syntax.DID(issue.Did)
84
+
eventType := models.NotificationTypeIssueCreated
85
+
entityType := "issue"
86
+
entityId := issue.AtUri().String()
87
+
repoId := &issue.Repo.Id
88
+
issueId := &issue.Id
func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment) {
···
122
-
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(issue.RepoAt)))
124
-
log.Printf("NewIssueComment: failed to get repos: %v", err)
128
-
recipients := make(map[string]bool)
130
-
// notify issue author (if not the commenter)
131
-
if issue.Did != comment.Did {
132
-
prefs, err := db.GetNotificationPreference(n.db, issue.Did)
133
-
if err == nil && prefs.IssueCommented {
134
-
recipients[issue.Did] = true
135
-
} else if err != nil {
136
-
log.Printf("NewIssueComment: failed to get preferences for issue author %s: %v", issue.Did, err)
140
-
// notify repo owner (if not the commenter and not already added)
141
-
if repo.Did != comment.Did && repo.Did != issue.Did {
142
-
prefs, err := db.GetNotificationPreference(n.db, repo.Did)
143
-
if err == nil && prefs.IssueCommented {
144
-
recipients[repo.Did] = true
145
-
} else if err != nil {
146
-
log.Printf("NewIssueComment: failed to get preferences for repo owner %s: %v", repo.Did, err)
115
+
var recipients []syntax.DID
116
+
recipients = append(recipients, syntax.DID(issue.Repo.Did))
150
-
// create notifications for all recipients
151
-
for recipientDid := range recipients {
152
-
notification := &models.Notification{
153
-
RecipientDid: recipientDid,
154
-
ActorDid: comment.Did,
155
-
Type: models.NotificationTypeIssueCommented,
156
-
EntityType: "issue",
157
-
EntityId: string(issue.AtUri()),
159
-
IssueId: &issue.Id,
118
+
if comment.IsReply() {
119
+
// if this comment is a reply, then notify everybody in that thread
120
+
parentAtUri := *comment.ReplyTo
121
+
allThreads := issue.CommentList()
162
-
err = db.CreateNotification(n.db, notification)
164
-
log.Printf("NewIssueComment: failed to create notification for %s: %v", recipientDid, err)
123
+
// find the parent thread, and add all DIDs from here to the recipient list
124
+
for _, t := range allThreads {
125
+
if t.Self.AtUri().String() == parentAtUri {
126
+
recipients = append(recipients, t.Participants()...)
130
+
// not a reply, notify just the issue author
131
+
recipients = append(recipients, syntax.DID(issue.Did))
134
+
actorDid := syntax.DID(comment.Did)
135
+
eventType := models.NotificationTypeIssueCommented
136
+
entityType := "issue"
137
+
entityId := issue.AtUri().String()
138
+
repoId := &issue.Repo.Id
139
+
issueId := &issue.Id
func (n *databaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) {
170
-
prefs, err := db.GetNotificationPreference(n.db, follow.SubjectDid)
172
-
log.Printf("NewFollow: failed to get notification preferences for %s: %v", follow.SubjectDid, err)
175
-
if !prefs.Followed {
179
-
notification := &models.Notification{
180
-
RecipientDid: follow.SubjectDid,
181
-
ActorDid: follow.UserDid,
182
-
Type: models.NotificationTypeFollowed,
183
-
EntityType: "follow",
184
-
EntityId: follow.UserDid,
187
-
err = db.CreateNotification(n.db, notification)
189
-
log.Printf("NewFollow: failed to create notification: %v", err)
155
+
actorDid := syntax.DID(follow.UserDid)
156
+
recipients := []syntax.DID{syntax.DID(follow.SubjectDid)}
157
+
eventType := models.NotificationTypeFollowed
158
+
entityType := "follow"
159
+
entityId := follow.UserDid
160
+
var repoId, issueId, pullId *int64
func (n *databaseNotifier) DeleteFollow(ctx context.Context, follow *models.Follow) {
···
205
-
if repo.Did == pull.OwnerDid {
209
-
prefs, err := db.GetNotificationPreference(n.db, repo.Did)
185
+
// build the recipients list
186
+
// - owner of the repo
187
+
// - collaborators in the repo
188
+
var recipients []syntax.DID
189
+
recipients = append(recipients, syntax.DID(repo.Did))
190
+
collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", repo.RepoAt()))
211
-
log.Printf("NewPull: failed to get notification preferences for %s: %v", repo.Did, err)
214
-
if !prefs.PullCreated {
218
-
notification := &models.Notification{
219
-
RecipientDid: repo.Did,
220
-
ActorDid: pull.OwnerDid,
221
-
Type: models.NotificationTypePullCreated,
222
-
EntityType: "pull",
223
-
EntityId: string(pull.RepoAt),
225
-
PullId: func() *int64 { id := int64(pull.ID); return &id }(),
228
-
err = db.CreateNotification(n.db, notification)
230
-
log.Printf("NewPull: failed to create notification: %v", err)
192
+
log.Printf("failed to fetch collaborators: %v", err)
195
+
for _, c := range collaborators {
196
+
recipients = append(recipients, c.SubjectDid)
199
+
actorDid := syntax.DID(pull.OwnerDid)
200
+
eventType := models.NotificationTypePullCreated
201
+
entityType := "pull"
202
+
entityId := pull.PullAt().String()
205
+
p := int64(pull.ID)
func (n *databaseNotifier) NewPullComment(ctx context.Context, comment *models.PullComment) {
236
-
pulls, err := db.GetPulls(n.db,
237
-
db.FilterEq("repo_at", comment.RepoAt),
238
-
db.FilterEq("pull_id", comment.PullId))
221
+
pull, err := db.GetPull(n.db,
222
+
syntax.ATURI(comment.RepoAt),
log.Printf("NewPullComment: failed to get pulls: %v", err)
243
-
if len(pulls) == 0 {
244
-
log.Printf("NewPullComment: no pull found for %s PR %d", comment.RepoAt, comment.PullId)
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", comment.RepoAt))
···
255
-
recipients := make(map[string]bool)
257
-
// notify pull request author (if not the commenter)
258
-
if pull.OwnerDid != comment.OwnerDid {
259
-
prefs, err := db.GetNotificationPreference(n.db, pull.OwnerDid)
260
-
if err == nil && prefs.PullCommented {
261
-
recipients[pull.OwnerDid] = true
262
-
} else if err != nil {
263
-
log.Printf("NewPullComment: failed to get preferences for pull author %s: %v", pull.OwnerDid, err)
267
-
// notify repo owner (if not the commenter and not already added)
268
-
if repo.Did != comment.OwnerDid && repo.Did != pull.OwnerDid {
269
-
prefs, err := db.GetNotificationPreference(n.db, repo.Did)
270
-
if err == nil && prefs.PullCommented {
271
-
recipients[repo.Did] = true
272
-
} else if err != nil {
273
-
log.Printf("NewPullComment: failed to get preferences for repo owner %s: %v", repo.Did, err)
277
-
for recipientDid := range recipients {
278
-
notification := &models.Notification{
279
-
RecipientDid: recipientDid,
280
-
ActorDid: comment.OwnerDid,
281
-
Type: models.NotificationTypePullCommented,
282
-
EntityType: "pull",
283
-
EntityId: comment.RepoAt,
285
-
PullId: func() *int64 { id := int64(pull.ID); return &id }(),
288
-
err = db.CreateNotification(n.db, notification)
290
-
log.Printf("NewPullComment: failed to create notification for %s: %v", recipientDid, err)
236
+
// build up the recipients list:
238
+
// - all pull participants
239
+
var recipients []syntax.DID
240
+
recipients = append(recipients, syntax.DID(repo.Did))
241
+
for _, p := range pull.Participants() {
242
+
recipients = append(recipients, syntax.DID(p))
245
+
actorDid := syntax.DID(comment.OwnerDid)
246
+
eventType := models.NotificationTypePullCommented
247
+
entityType := "pull"
248
+
entityId := pull.PullAt().String()
251
+
p := int64(pull.ID)
func (n *databaseNotifier) UpdateProfile(ctx context.Context, profile *models.Profile) {
···
func (n *databaseNotifier) NewIssueClosed(ctx context.Context, issue *models.Issue) {
312
-
// Get repo details
313
-
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(issue.RepoAt)))
283
+
// build up the recipients list:
285
+
// - repo collaborators
286
+
// - all issue participants
287
+
var recipients []syntax.DID
288
+
recipients = append(recipients, syntax.DID(issue.Repo.Did))
289
+
collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", issue.Repo.RepoAt()))
315
-
log.Printf("NewIssueClosed: failed to get repos: %v", err)
319
-
// Don't notify yourself
320
-
if repo.Did == issue.Did {
291
+
log.Printf("failed to fetch collaborators: %v", err)
294
+
for _, c := range collaborators {
295
+
recipients = append(recipients, c.SubjectDid)
297
+
for _, p := range issue.Participants() {
298
+
recipients = append(recipients, syntax.DID(p))
301
+
actorDid := syntax.DID(issue.Repo.Did)
302
+
eventType := models.NotificationTypeIssueClosed
303
+
entityType := "pull"
304
+
entityId := issue.AtUri().String()
305
+
repoId := &issue.Repo.Id
306
+
issueId := &issue.Id
324
-
// Check if user wants these notifications
325
-
prefs, err := db.GetNotificationPreference(n.db, repo.Did)
321
+
func (n *databaseNotifier) NewPullMerged(ctx context.Context, pull *models.Pull) {
322
+
// Get repo details
323
+
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt)))
327
-
log.Printf("NewIssueClosed: failed to get notification preferences for %s: %v", repo.Did, err)
330
-
if !prefs.IssueClosed {
325
+
log.Printf("NewPullMerged: failed to get repos: %v", err)
334
-
notification := &models.Notification{
335
-
RecipientDid: repo.Did,
336
-
ActorDid: issue.Did,
337
-
Type: models.NotificationTypeIssueClosed,
338
-
EntityType: "issue",
339
-
EntityId: string(issue.AtUri()),
341
-
IssueId: &issue.Id,
344
-
err = db.CreateNotification(n.db, notification)
329
+
// build up the recipients list:
331
+
// - all pull participants
332
+
var recipients []syntax.DID
333
+
recipients = append(recipients, syntax.DID(repo.Did))
334
+
collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", repo.RepoAt()))
346
-
log.Printf("NewIssueClosed: failed to create notification: %v", err)
336
+
log.Printf("failed to fetch collaborators: %v", err)
339
+
for _, c := range collaborators {
340
+
recipients = append(recipients, c.SubjectDid)
342
+
for _, p := range pull.Participants() {
343
+
recipients = append(recipients, syntax.DID(p))
346
+
actorDid := syntax.DID(repo.Did)
347
+
eventType := models.NotificationTypePullMerged
348
+
entityType := "pull"
349
+
entityId := pull.PullAt().String()
352
+
p := int64(pull.ID)
351
-
func (n *databaseNotifier) NewPullMerged(ctx context.Context, pull *models.Pull) {
367
+
func (n *databaseNotifier) NewPullClosed(ctx context.Context, pull *models.Pull) {
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt)))
···
359
-
// Don't notify yourself
360
-
if repo.Did == pull.OwnerDid {
364
-
// Check if user wants these notifications
365
-
prefs, err := db.GetNotificationPreference(n.db, pull.OwnerDid)
375
+
// build up the recipients list:
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()))
367
-
log.Printf("NewPullMerged: failed to get notification preferences for %s: %v", pull.OwnerDid, err)
370
-
if !prefs.PullMerged {
382
+
log.Printf("failed to fetch collaborators: %v", err)
385
+
for _, c := range collaborators {
386
+
recipients = append(recipients, c.SubjectDid)
388
+
for _, p := range pull.Participants() {
389
+
recipients = append(recipients, syntax.DID(p))
392
+
actorDid := syntax.DID(repo.Did)
393
+
eventType := models.NotificationTypePullClosed
394
+
entityType := "pull"
395
+
entityId := pull.PullAt().String()
398
+
p := int64(pull.ID)
374
-
notification := &models.Notification{
375
-
RecipientDid: pull.OwnerDid,
376
-
ActorDid: repo.Did,
377
-
Type: models.NotificationTypePullMerged,
378
-
EntityType: "pull",
379
-
EntityId: string(pull.RepoAt),
381
-
PullId: func() *int64 { id := int64(pull.ID); return &id }(),
413
+
func (n *databaseNotifier) notifyEvent(
414
+
actorDid syntax.DID,
415
+
recipients []syntax.DID,
416
+
eventType models.NotificationType,
423
+
recipientSet := make(map[syntax.DID]struct{})
424
+
for _, did := range recipients {
425
+
// everybody except actor themselves
426
+
if did != actorDid {
427
+
recipientSet[did] = struct{}{}
384
-
err = db.CreateNotification(n.db, notification)
431
+
prefMap, err := db.GetNotificationPreferences(
433
+
db.FilterIn("user_did", slices.Collect(maps.Keys(recipientSet))),
386
-
log.Printf("NewPullMerged: failed to create notification: %v", err)
436
+
// failed to get prefs for users
391
-
func (n *databaseNotifier) NewPullClosed(ctx context.Context, pull *models.Pull) {
392
-
// Get repo details
393
-
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt)))
440
+
// create a transaction for bulk notification storage
441
+
tx, err := n.db.Begin()
395
-
log.Printf("NewPullClosed: failed to get repos: %v", err)
443
+
// failed to start tx
446
+
defer tx.Rollback()
399
-
// Don't notify yourself
400
-
if repo.Did == pull.OwnerDid {
448
+
// filter based on preferences
449
+
for recipientDid := range recipientSet {
450
+
prefs, ok := prefMap[recipientDid]
452
+
prefs = models.DefaultNotificationPreferences(recipientDid)
404
-
// Check if user wants these notifications - reuse pull_merged preference for now
405
-
prefs, err := db.GetNotificationPreference(n.db, pull.OwnerDid)
407
-
log.Printf("NewPullClosed: failed to get notification preferences for %s: %v", pull.OwnerDid, err)
410
-
if !prefs.PullMerged {
455
+
// skip users who don’t want this type
456
+
if !prefs.ShouldNotify(eventType) {
414
-
notification := &models.Notification{
415
-
RecipientDid: pull.OwnerDid,
416
-
ActorDid: repo.Did,
417
-
Type: models.NotificationTypePullClosed,
418
-
EntityType: "pull",
419
-
EntityId: string(pull.RepoAt),
421
-
PullId: func() *int64 { id := int64(pull.ID); return &id }(),
460
+
// create notification
461
+
notif := &models.Notification{
462
+
RecipientDid: recipientDid.String(),
463
+
ActorDid: actorDid.String(),
465
+
EntityType: entityType,
466
+
EntityId: entityId,
472
+
if err := db.CreateNotification(tx, notif); err != nil {
473
+
log.Printf("notifyEvent: failed to create notification for %s: %v", recipientDid, err)
424
-
err = db.CreateNotification(n.db, notification)
426
-
log.Printf("NewPullClosed: failed to create notification: %v", err)
477
+
if err := tx.Commit(); err != nil {
478
+
// failed to commit