1package db
2
3import (
4 "context"
5 "log"
6
7 "tangled.org/core/appview/db"
8 "tangled.org/core/appview/models"
9 "tangled.org/core/appview/notify"
10 "tangled.org/core/idresolver"
11)
12
13type databaseNotifier struct {
14 db *db.DB
15 res *idresolver.Resolver
16}
17
18func NewDatabaseNotifier(database *db.DB, resolver *idresolver.Resolver) notify.Notifier {
19 return &databaseNotifier{
20 db: database,
21 res: resolver,
22 }
23}
24
25var _ notify.Notifier = &databaseNotifier{}
26
27func (n *databaseNotifier) NewRepo(ctx context.Context, repo *models.Repo) {
28 // no-op for now
29}
30
31func (n *databaseNotifier) NewStar(ctx context.Context, star *models.Star) {
32 var err error
33 repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(star.RepoAt)))
34 if err != nil {
35 log.Printf("NewStar: failed to get repos: %v", err)
36 return
37 }
38
39 // don't notify yourself
40 if repo.Did == star.StarredByDid {
41 return
42 }
43
44 // check if user wants these notifications
45 prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did)
46 if err != nil {
47 log.Printf("NewStar: failed to get notification preferences for %s: %v", repo.Did, err)
48 return
49 }
50 if !prefs.RepoStarred {
51 return
52 }
53
54 notification := &models.Notification{
55 RecipientDid: repo.Did,
56 ActorDid: star.StarredByDid,
57 Type: models.NotificationTypeRepoStarred,
58 EntityType: "repo",
59 EntityId: string(star.RepoAt),
60 RepoId: &repo.Id,
61 }
62 err = n.db.CreateNotification(ctx, notification)
63 if err != nil {
64 log.Printf("NewStar: failed to create notification: %v", err)
65 return
66 }
67}
68
69func (n *databaseNotifier) DeleteStar(ctx context.Context, star *models.Star) {
70 // no-op
71}
72
73func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue) {
74 repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(issue.RepoAt)))
75 if err != nil {
76 log.Printf("NewIssue: failed to get repos: %v", err)
77 return
78 }
79
80 if repo.Did == issue.Did {
81 return
82 }
83
84 prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did)
85 if err != nil {
86 log.Printf("NewIssue: failed to get notification preferences for %s: %v", repo.Did, err)
87 return
88 }
89 if !prefs.IssueCreated {
90 return
91 }
92
93 notification := &models.Notification{
94 RecipientDid: repo.Did,
95 ActorDid: issue.Did,
96 Type: models.NotificationTypeIssueCreated,
97 EntityType: "issue",
98 EntityId: string(issue.AtUri()),
99 RepoId: &repo.Id,
100 IssueId: &issue.Id,
101 }
102
103 err = n.db.CreateNotification(ctx, notification)
104 if err != nil {
105 log.Printf("NewIssue: failed to create notification: %v", err)
106 return
107 }
108}
109
110func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment) {
111 issues, err := db.GetIssues(n.db, db.FilterEq("at_uri", comment.IssueAt))
112 if err != nil {
113 log.Printf("NewIssueComment: failed to get issues: %v", err)
114 return
115 }
116 if len(issues) == 0 {
117 log.Printf("NewIssueComment: no issue found for %s", comment.IssueAt)
118 return
119 }
120 issue := issues[0]
121
122 repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(issue.RepoAt)))
123 if err != nil {
124 log.Printf("NewIssueComment: failed to get repos: %v", err)
125 return
126 }
127
128 recipients := make(map[string]bool)
129
130 // notify issue author (if not the commenter)
131 if issue.Did != comment.Did {
132 prefs, err := n.db.GetNotificationPreferences(ctx, 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)
137 }
138 }
139
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 := n.db.GetNotificationPreferences(ctx, 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)
147 }
148 }
149
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()),
158 RepoId: &repo.Id,
159 IssueId: &issue.Id,
160 }
161
162 err = n.db.CreateNotification(ctx, notification)
163 if err != nil {
164 log.Printf("NewIssueComment: failed to create notification for %s: %v", recipientDid, err)
165 }
166 }
167}
168
169func (n *databaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) {
170 prefs, err := n.db.GetNotificationPreferences(ctx, follow.SubjectDid)
171 if err != nil {
172 log.Printf("NewFollow: failed to get notification preferences for %s: %v", follow.SubjectDid, err)
173 return
174 }
175 if !prefs.Followed {
176 return
177 }
178
179 notification := &models.Notification{
180 RecipientDid: follow.SubjectDid,
181 ActorDid: follow.UserDid,
182 Type: models.NotificationTypeFollowed,
183 EntityType: "follow",
184 EntityId: follow.UserDid,
185 }
186
187 err = n.db.CreateNotification(ctx, notification)
188 if err != nil {
189 log.Printf("NewFollow: failed to create notification: %v", err)
190 return
191 }
192}
193
194func (n *databaseNotifier) DeleteFollow(ctx context.Context, follow *models.Follow) {
195 // no-op
196}
197
198func (n *databaseNotifier) NewPull(ctx context.Context, pull *models.Pull) {
199 repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt)))
200 if err != nil {
201 log.Printf("NewPull: failed to get repos: %v", err)
202 return
203 }
204
205 if repo.Did == pull.OwnerDid {
206 return
207 }
208
209 prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did)
210 if err != nil {
211 log.Printf("NewPull: failed to get notification preferences for %s: %v", repo.Did, err)
212 return
213 }
214 if !prefs.PullCreated {
215 return
216 }
217
218 notification := &models.Notification{
219 RecipientDid: repo.Did,
220 ActorDid: pull.OwnerDid,
221 Type: models.NotificationTypePullCreated,
222 EntityType: "pull",
223 EntityId: string(pull.RepoAt),
224 RepoId: &repo.Id,
225 PullId: func() *int64 { id := int64(pull.ID); return &id }(),
226 }
227
228 err = n.db.CreateNotification(ctx, notification)
229 if err != nil {
230 log.Printf("NewPull: failed to create notification: %v", err)
231 return
232 }
233}
234
235func (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))
239 if err != nil {
240 log.Printf("NewPullComment: failed to get pulls: %v", err)
241 return
242 }
243 if len(pulls) == 0 {
244 log.Printf("NewPullComment: no pull found for %s PR %d", comment.RepoAt, comment.PullId)
245 return
246 }
247 pull := pulls[0]
248
249 repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", comment.RepoAt))
250 if err != nil {
251 log.Printf("NewPullComment: failed to get repos: %v", err)
252 return
253 }
254
255 recipients := make(map[string]bool)
256
257 // notify pull request author (if not the commenter)
258 if pull.OwnerDid != comment.OwnerDid {
259 prefs, err := n.db.GetNotificationPreferences(ctx, 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)
264 }
265 }
266
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 := n.db.GetNotificationPreferences(ctx, 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)
274 }
275 }
276
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,
284 RepoId: &repo.Id,
285 PullId: func() *int64 { id := int64(pull.ID); return &id }(),
286 }
287
288 err = n.db.CreateNotification(ctx, notification)
289 if err != nil {
290 log.Printf("NewPullComment: failed to create notification for %s: %v", recipientDid, err)
291 }
292 }
293}
294
295func (n *databaseNotifier) UpdateProfile(ctx context.Context, profile *models.Profile) {
296 // no-op
297}
298
299func (n *databaseNotifier) DeleteString(ctx context.Context, did, rkey string) {
300 // no-op
301}
302
303func (n *databaseNotifier) EditString(ctx context.Context, string *models.String) {
304 // no-op
305}
306
307func (n *databaseNotifier) NewString(ctx context.Context, string *models.String) {
308 // no-op
309}
310
311func (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)))
314 if err != nil {
315 log.Printf("NewIssueClosed: failed to get repos: %v", err)
316 return
317 }
318
319 // Don't notify yourself
320 if repo.Did == issue.Did {
321 return
322 }
323
324 // Check if user wants these notifications
325 prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did)
326 if err != nil {
327 log.Printf("NewIssueClosed: failed to get notification preferences for %s: %v", repo.Did, err)
328 return
329 }
330 if !prefs.IssueClosed {
331 return
332 }
333
334 notification := &models.Notification{
335 RecipientDid: repo.Did,
336 ActorDid: issue.Did,
337 Type: models.NotificationTypeIssueClosed,
338 EntityType: "issue",
339 EntityId: string(issue.AtUri()),
340 RepoId: &repo.Id,
341 IssueId: &issue.Id,
342 }
343
344 err = n.db.CreateNotification(ctx, notification)
345 if err != nil {
346 log.Printf("NewIssueClosed: failed to create notification: %v", err)
347 return
348 }
349}
350
351func (n *databaseNotifier) NewPullMerged(ctx context.Context, pull *models.Pull) {
352 // Get repo details
353 repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt)))
354 if err != nil {
355 log.Printf("NewPullMerged: failed to get repos: %v", err)
356 return
357 }
358
359 // Don't notify yourself
360 if repo.Did == pull.OwnerDid {
361 return
362 }
363
364 // Check if user wants these notifications
365 prefs, err := n.db.GetNotificationPreferences(ctx, pull.OwnerDid)
366 if err != nil {
367 log.Printf("NewPullMerged: failed to get notification preferences for %s: %v", pull.OwnerDid, err)
368 return
369 }
370 if !prefs.PullMerged {
371 return
372 }
373
374 notification := &models.Notification{
375 RecipientDid: pull.OwnerDid,
376 ActorDid: repo.Did,
377 Type: models.NotificationTypePullMerged,
378 EntityType: "pull",
379 EntityId: string(pull.RepoAt),
380 RepoId: &repo.Id,
381 PullId: func() *int64 { id := int64(pull.ID); return &id }(),
382 }
383
384 err = n.db.CreateNotification(ctx, notification)
385 if err != nil {
386 log.Printf("NewPullMerged: failed to create notification: %v", err)
387 return
388 }
389}
390
391func (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)))
394 if err != nil {
395 log.Printf("NewPullClosed: failed to get repos: %v", err)
396 return
397 }
398
399 // Don't notify yourself
400 if repo.Did == pull.OwnerDid {
401 return
402 }
403
404 // Check if user wants these notifications - reuse pull_merged preference for now
405 prefs, err := n.db.GetNotificationPreferences(ctx, pull.OwnerDid)
406 if err != nil {
407 log.Printf("NewPullClosed: failed to get notification preferences for %s: %v", pull.OwnerDid, err)
408 return
409 }
410 if !prefs.PullMerged {
411 return
412 }
413
414 notification := &models.Notification{
415 RecipientDid: pull.OwnerDid,
416 ActorDid: repo.Did,
417 Type: models.NotificationTypePullClosed,
418 EntityType: "pull",
419 EntityId: string(pull.RepoAt),
420 RepoId: &repo.Id,
421 PullId: func() *int64 { id := int64(pull.ID); return &id }(),
422 }
423
424 err = n.db.CreateNotification(ctx, notification)
425 if err != nil {
426 log.Printf("NewPullClosed: failed to create notification: %v", err)
427 return
428 }
429}