Signed-off-by: Anirudh Oppiliappan anirudh@tangled.sh
+469
appview/notify/db/db.go
+469
appview/notify/db/db.go
···
1
+
package db
2
+
3
+
import (
4
+
"context"
5
+
"log"
6
+
7
+
"tangled.sh/tangled.sh/core/appview/db"
8
+
"tangled.sh/tangled.sh/core/appview/notify"
9
+
"tangled.sh/tangled.sh/core/idresolver"
10
+
)
11
+
12
+
type databaseNotifier struct {
13
+
db *db.DB
14
+
res *idresolver.Resolver
15
+
}
16
+
17
+
func NewDatabaseNotifier(database *db.DB, resolver *idresolver.Resolver) notify.Notifier {
18
+
return &databaseNotifier{
19
+
db: database,
20
+
res: resolver,
21
+
}
22
+
}
23
+
24
+
var _ notify.Notifier = &databaseNotifier{}
25
+
26
+
func (n *databaseNotifier) NewRepo(ctx context.Context, repo *db.Repo) {
27
+
// no-op for now
28
+
}
29
+
30
+
func (n *databaseNotifier) NewStar(ctx context.Context, star *db.Star) {
31
+
var err error
32
+
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(star.RepoAt)))
33
+
if err != nil {
34
+
log.Printf("NewStar: failed to get repos: %v", err)
35
+
return
36
+
}
37
+
if len(repos) == 0 {
38
+
log.Printf("NewStar: no repo found for %s", star.RepoAt)
39
+
return
40
+
}
41
+
repo := repos[0]
42
+
43
+
// don't notify yourself
44
+
if repo.Did == star.StarredByDid {
45
+
return
46
+
}
47
+
48
+
// check if user wants these notifications
49
+
prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did)
50
+
if err != nil {
51
+
log.Printf("NewStar: failed to get notification preferences for %s: %v", repo.Did, err)
52
+
return
53
+
}
54
+
if !prefs.RepoStarred {
55
+
return
56
+
}
57
+
58
+
notification := &models.Notification{
59
+
RecipientDid: repo.Did,
60
+
ActorDid: star.StarredByDid,
61
+
Type: models.NotificationTypeRepoStarred,
62
+
EntityType: "repo",
63
+
EntityId: string(star.RepoAt),
64
+
RepoId: &repo.ID,
65
+
}
66
+
67
+
err = n.db.CreateNotification(ctx, notification)
68
+
if err != nil {
69
+
log.Printf("NewStar: failed to create notification: %v", err)
70
+
return
71
+
}
72
+
}
73
+
74
+
func (n *databaseNotifier) DeleteStar(ctx context.Context, star *db.Star) {
75
+
// no-op
76
+
}
77
+
78
+
func (n *databaseNotifier) NewIssue(ctx context.Context, issue *db.Issue) {
79
+
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(issue.RepoAt)))
80
+
if err != nil {
81
+
log.Printf("NewIssue: failed to get repos: %v", err)
82
+
return
83
+
}
84
+
if len(repos) == 0 {
85
+
log.Printf("NewIssue: no repo found for %s", issue.RepoAt)
86
+
return
87
+
}
88
+
repo := repos[0]
89
+
90
+
if repo.Did == issue.Did {
91
+
return
92
+
}
93
+
94
+
prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did)
95
+
if err != nil {
96
+
log.Printf("NewIssue: failed to get notification preferences for %s: %v", repo.Did, err)
97
+
return
98
+
}
99
+
if !prefs.IssueCreated {
100
+
return
101
+
}
102
+
103
+
notification := &models.Notification{
104
+
RecipientDid: repo.Did,
105
+
ActorDid: issue.Did,
106
+
Type: models.NotificationTypeIssueCreated,
107
+
EntityType: "issue",
108
+
EntityId: string(issue.AtUri()),
109
+
RepoId: &repo.ID,
110
+
IssueId: &issue.Id,
111
+
}
112
+
113
+
err = n.db.CreateNotification(ctx, notification)
114
+
if err != nil {
115
+
log.Printf("NewIssue: failed to create notification: %v", err)
116
+
return
117
+
}
118
+
}
119
+
120
+
func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *db.IssueComment) {
121
+
issues, err := db.GetIssues(n.db, db.FilterEq("at_uri", comment.IssueAt))
122
+
if err != nil {
123
+
log.Printf("NewIssueComment: failed to get issues: %v", err)
124
+
return
125
+
}
126
+
if len(issues) == 0 {
127
+
log.Printf("NewIssueComment: no issue found for %s", comment.IssueAt)
128
+
return
129
+
}
130
+
issue := issues[0]
131
+
132
+
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(issue.RepoAt)))
133
+
if err != nil {
134
+
log.Printf("NewIssueComment: failed to get repos: %v", err)
135
+
return
136
+
}
137
+
if len(repos) == 0 {
138
+
log.Printf("NewIssueComment: no repo found for %s", issue.RepoAt)
139
+
return
140
+
}
141
+
repo := repos[0]
142
+
143
+
recipients := make(map[string]bool)
144
+
145
+
// notify issue author (if not the commenter)
146
+
if issue.Did != comment.Did {
147
+
prefs, err := n.db.GetNotificationPreferences(ctx, issue.Did)
148
+
if err == nil && prefs.IssueCommented {
149
+
recipients[issue.Did] = true
150
+
} else if err != nil {
151
+
log.Printf("NewIssueComment: failed to get preferences for issue author %s: %v", issue.Did, err)
152
+
}
153
+
}
154
+
155
+
// notify repo owner (if not the commenter and not already added)
156
+
if repo.Did != comment.Did && repo.Did != issue.Did {
157
+
prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did)
158
+
if err == nil && prefs.IssueCommented {
159
+
recipients[repo.Did] = true
160
+
} else if err != nil {
161
+
log.Printf("NewIssueComment: failed to get preferences for repo owner %s: %v", repo.Did, err)
162
+
}
163
+
}
164
+
165
+
// create notifications for all recipients
166
+
for recipientDid := range recipients {
167
+
notification := &models.Notification{
168
+
RecipientDid: recipientDid,
169
+
ActorDid: comment.Did,
170
+
Type: models.NotificationTypeIssueCommented,
171
+
EntityType: "issue",
172
+
EntityId: string(issue.AtUri()),
173
+
RepoId: &repo.ID,
174
+
IssueId: &issue.Id,
175
+
}
176
+
177
+
err = n.db.CreateNotification(ctx, notification)
178
+
if err != nil {
179
+
log.Printf("NewIssueComment: failed to create notification for %s: %v", recipientDid, err)
180
+
}
181
+
}
182
+
}
183
+
184
+
func (n *databaseNotifier) NewFollow(ctx context.Context, follow *db.Follow) {
185
+
prefs, err := n.db.GetNotificationPreferences(ctx, follow.SubjectDid)
186
+
if err != nil {
187
+
log.Printf("NewFollow: failed to get notification preferences for %s: %v", follow.SubjectDid, err)
188
+
return
189
+
}
190
+
if !prefs.Followed {
191
+
return
192
+
}
193
+
194
+
notification := &models.Notification{
195
+
RecipientDid: follow.SubjectDid,
196
+
ActorDid: follow.UserDid,
197
+
Type: models.NotificationTypeFollowed,
198
+
EntityType: "follow",
199
+
EntityId: follow.UserDid,
200
+
}
201
+
202
+
err = n.db.CreateNotification(ctx, notification)
203
+
if err != nil {
204
+
log.Printf("NewFollow: failed to create notification: %v", err)
205
+
return
206
+
}
207
+
}
208
+
209
+
func (n *databaseNotifier) DeleteFollow(ctx context.Context, follow *db.Follow) {
210
+
// no-op
211
+
}
212
+
213
+
func (n *databaseNotifier) NewPull(ctx context.Context, pull *db.Pull) {
214
+
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(pull.RepoAt)))
215
+
if err != nil {
216
+
log.Printf("NewPull: failed to get repos: %v", err)
217
+
return
218
+
}
219
+
if len(repos) == 0 {
220
+
log.Printf("NewPull: no repo found for %s", pull.RepoAt)
221
+
return
222
+
}
223
+
repo := repos[0]
224
+
225
+
if repo.Did == pull.OwnerDid {
226
+
return
227
+
}
228
+
229
+
prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did)
230
+
if err != nil {
231
+
log.Printf("NewPull: failed to get notification preferences for %s: %v", repo.Did, err)
232
+
return
233
+
}
234
+
if !prefs.PullCreated {
235
+
return
236
+
}
237
+
238
+
notification := &models.Notification{
239
+
RecipientDid: repo.Did,
240
+
ActorDid: pull.OwnerDid,
241
+
Type: models.NotificationTypePullCreated,
242
+
EntityType: "pull",
243
+
EntityId: string(pull.RepoAt),
244
+
RepoId: &repo.ID,
245
+
PullId: func() *int64 { id := int64(pull.ID); return &id }(),
246
+
}
247
+
248
+
err = n.db.CreateNotification(ctx, notification)
249
+
if err != nil {
250
+
log.Printf("NewPull: failed to create notification: %v", err)
251
+
return
252
+
}
253
+
}
254
+
255
+
func (n *databaseNotifier) NewPullComment(ctx context.Context, comment *db.PullComment) {
256
+
pulls, err := db.GetPulls(n.db,
257
+
db.FilterEq("repo_at", comment.RepoAt),
258
+
db.FilterEq("pull_id", comment.PullId))
259
+
if err != nil {
260
+
log.Printf("NewPullComment: failed to get pulls: %v", err)
261
+
return
262
+
}
263
+
if len(pulls) == 0 {
264
+
log.Printf("NewPullComment: no pull found for %s PR %d", comment.RepoAt, comment.PullId)
265
+
return
266
+
}
267
+
pull := pulls[0]
268
+
269
+
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", comment.RepoAt))
270
+
if err != nil {
271
+
log.Printf("NewPullComment: failed to get repos: %v", err)
272
+
return
273
+
}
274
+
if len(repos) == 0 {
275
+
log.Printf("NewPullComment: no repo found for %s", comment.RepoAt)
276
+
return
277
+
}
278
+
repo := repos[0]
279
+
280
+
recipients := make(map[string]bool)
281
+
282
+
// notify pull request author (if not the commenter)
283
+
if pull.OwnerDid != comment.OwnerDid {
284
+
prefs, err := n.db.GetNotificationPreferences(ctx, pull.OwnerDid)
285
+
if err == nil && prefs.PullCommented {
286
+
recipients[pull.OwnerDid] = true
287
+
} else if err != nil {
288
+
log.Printf("NewPullComment: failed to get preferences for pull author %s: %v", pull.OwnerDid, err)
289
+
}
290
+
}
291
+
292
+
// notify repo owner (if not the commenter and not already added)
293
+
if repo.Did != comment.OwnerDid && repo.Did != pull.OwnerDid {
294
+
prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did)
295
+
if err == nil && prefs.PullCommented {
296
+
recipients[repo.Did] = true
297
+
} else if err != nil {
298
+
log.Printf("NewPullComment: failed to get preferences for repo owner %s: %v", repo.Did, err)
299
+
}
300
+
}
301
+
302
+
for recipientDid := range recipients {
303
+
notification := &models.Notification{
304
+
RecipientDid: recipientDid,
305
+
ActorDid: comment.OwnerDid,
306
+
Type: models.NotificationTypePullCommented,
307
+
EntityType: "pull",
308
+
EntityId: comment.RepoAt,
309
+
RepoId: &repo.ID,
310
+
PullId: func() *int64 { id := int64(pull.ID); return &id }(),
311
+
}
312
+
313
+
err = n.db.CreateNotification(ctx, notification)
314
+
if err != nil {
315
+
log.Printf("NewPullComment: failed to create notification for %s: %v", recipientDid, err)
316
+
}
317
+
}
318
+
}
319
+
320
+
func (n *databaseNotifier) UpdateProfile(ctx context.Context, profile *db.Profile) {
321
+
// no-op
322
+
}
323
+
324
+
func (n *databaseNotifier) DeleteString(ctx context.Context, did, rkey string) {
325
+
// no-op
326
+
}
327
+
328
+
func (n *databaseNotifier) EditString(ctx context.Context, string *db.String) {
329
+
// no-op
330
+
}
331
+
332
+
func (n *databaseNotifier) NewString(ctx context.Context, string *db.String) {
333
+
// no-op
334
+
}
335
+
336
+
func (n *databaseNotifier) NewIssueClosed(ctx context.Context, issue *db.Issue) {
337
+
// Get repo details
338
+
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(issue.RepoAt)))
339
+
if err != nil {
340
+
log.Printf("NewIssueClosed: failed to get repos: %v", err)
341
+
return
342
+
}
343
+
if len(repos) == 0 {
344
+
log.Printf("NewIssueClosed: no repo found for %s", issue.RepoAt)
345
+
return
346
+
}
347
+
repo := repos[0]
348
+
349
+
// Don't notify yourself
350
+
if repo.Did == issue.Did {
351
+
return
352
+
}
353
+
354
+
// Check if user wants these notifications
355
+
prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did)
356
+
if err != nil {
357
+
log.Printf("NewIssueClosed: failed to get notification preferences for %s: %v", repo.Did, err)
358
+
return
359
+
}
360
+
if !prefs.IssueClosed {
361
+
return
362
+
}
363
+
364
+
notification := &models.Notification{
365
+
RecipientDid: repo.Did,
366
+
ActorDid: issue.Did,
367
+
Type: models.NotificationTypeIssueClosed,
368
+
EntityType: "issue",
369
+
EntityId: string(issue.AtUri()),
370
+
RepoId: &repo.ID,
371
+
IssueId: &issue.Id,
372
+
}
373
+
374
+
err = n.db.CreateNotification(ctx, notification)
375
+
if err != nil {
376
+
log.Printf("NewIssueClosed: failed to create notification: %v", err)
377
+
return
378
+
}
379
+
}
380
+
381
+
func (n *databaseNotifier) NewPullMerged(ctx context.Context, pull *db.Pull) {
382
+
// Get repo details
383
+
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(pull.RepoAt)))
384
+
if err != nil {
385
+
log.Printf("NewPullMerged: failed to get repos: %v", err)
386
+
return
387
+
}
388
+
if len(repos) == 0 {
389
+
log.Printf("NewPullMerged: no repo found for %s", pull.RepoAt)
390
+
return
391
+
}
392
+
repo := repos[0]
393
+
394
+
// Don't notify yourself
395
+
if repo.Did == pull.OwnerDid {
396
+
return
397
+
}
398
+
399
+
// Check if user wants these notifications
400
+
prefs, err := n.db.GetNotificationPreferences(ctx, pull.OwnerDid)
401
+
if err != nil {
402
+
log.Printf("NewPullMerged: failed to get notification preferences for %s: %v", pull.OwnerDid, err)
403
+
return
404
+
}
405
+
if !prefs.PullMerged {
406
+
return
407
+
}
408
+
409
+
notification := &models.Notification{
410
+
RecipientDid: pull.OwnerDid,
411
+
ActorDid: repo.Did,
412
+
Type: models.NotificationTypePullMerged,
413
+
EntityType: "pull",
414
+
EntityId: string(pull.RepoAt),
415
+
RepoId: &repo.ID,
416
+
PullId: func() *int64 { id := int64(pull.ID); return &id }(),
417
+
}
418
+
419
+
err = n.db.CreateNotification(ctx, notification)
420
+
if err != nil {
421
+
log.Printf("NewPullMerged: failed to create notification: %v", err)
422
+
return
423
+
}
424
+
}
425
+
426
+
func (n *databaseNotifier) NewPullClosed(ctx context.Context, pull *db.Pull) {
427
+
// Get repo details
428
+
repos, err := db.GetRepos(n.db, 1, db.FilterEq("at_uri", string(pull.RepoAt)))
429
+
if err != nil {
430
+
log.Printf("NewPullClosed: failed to get repos: %v", err)
431
+
return
432
+
}
433
+
if len(repos) == 0 {
434
+
log.Printf("NewPullClosed: no repo found for %s", pull.RepoAt)
435
+
return
436
+
}
437
+
repo := repos[0]
438
+
439
+
// Don't notify yourself
440
+
if repo.Did == pull.OwnerDid {
441
+
return
442
+
}
443
+
444
+
// Check if user wants these notifications - reuse pull_merged preference for now
445
+
prefs, err := n.db.GetNotificationPreferences(ctx, pull.OwnerDid)
446
+
if err != nil {
447
+
log.Printf("NewPullClosed: failed to get notification preferences for %s: %v", pull.OwnerDid, err)
448
+
return
449
+
}
450
+
if !prefs.PullMerged {
451
+
return
452
+
}
453
+
454
+
notification := &models.Notification{
455
+
RecipientDid: pull.OwnerDid,
456
+
ActorDid: repo.Did,
457
+
Type: models.NotificationTypePullClosed,
458
+
EntityType: "pull",
459
+
EntityId: string(pull.RepoAt),
460
+
RepoId: &repo.ID,
461
+
PullId: func() *int64 { id := int64(pull.ID); return &id }(),
462
+
}
463
+
464
+
err = n.db.CreateNotification(ctx, notification)
465
+
if err != nil {
466
+
log.Printf("NewPullClosed: failed to create notification: %v", err)
467
+
return
468
+
}
469
+
}
+5
appview/state/state.go
+5
appview/state/state.go
···
148
148
spindlestream.Start(ctx)
149
149
150
150
var notifiers []notify.Notifier
151
+
152
+
// Always add the database notifier
153
+
notifiers = append(notifiers, dbnotify.NewDatabaseNotifier(d, res))
154
+
155
+
// Add other notifiers in production only
151
156
if !config.Core.Dev {
152
157
notifiers = append(notifiers, phnotify.NewPosthogNotifier(posthog))
153
158
}