this repo has no description
1package db 2 3import ( 4 "context" 5 "log" 6 "maps" 7 "slices" 8 9 "github.com/bluesky-social/indigo/atproto/syntax" 10 "tangled.org/core/appview/db" 11 "tangled.org/core/appview/models" 12 "tangled.org/core/appview/notify" 13 "tangled.org/core/idresolver" 14) 15 16type databaseNotifier struct { 17 db *db.DB 18 res *idresolver.Resolver 19} 20 21func NewDatabaseNotifier(database *db.DB, resolver *idresolver.Resolver) notify.Notifier { 22 return &databaseNotifier{ 23 db: database, 24 res: resolver, 25 } 26} 27 28var _ notify.Notifier = &databaseNotifier{} 29 30func (n *databaseNotifier) NewRepo(ctx context.Context, repo *models.Repo) { 31 // no-op for now 32} 33 34func (n *databaseNotifier) NewStar(ctx context.Context, star *models.Star) { 35 var err error 36 repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(star.RepoAt))) 37 if err != nil { 38 log.Printf("NewStar: failed to get repos: %v", err) 39 return 40 } 41 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() 47 repoId := &repo.Id 48 var issueId *int64 49 var pullId *int64 50 51 n.notifyEvent( 52 actorDid, 53 recipients, 54 eventType, 55 entityType, 56 entityId, 57 repoId, 58 issueId, 59 pullId, 60 ) 61} 62 63func (n *databaseNotifier) DeleteStar(ctx context.Context, star *models.Star) { 64 // no-op 65} 66 67func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue) { 68 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())) 75 if err != nil { 76 log.Printf("failed to fetch collaborators: %v", err) 77 return 78 } 79 for _, c := range collaborators { 80 recipients = append(recipients, c.SubjectDid) 81 } 82 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 89 var pullId *int64 90 91 n.notifyEvent( 92 actorDid, 93 recipients, 94 eventType, 95 entityType, 96 entityId, 97 repoId, 98 issueId, 99 pullId, 100 ) 101} 102 103func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment) { 104 issues, err := db.GetIssues(n.db, db.FilterEq("at_uri", comment.IssueAt)) 105 if err != nil { 106 log.Printf("NewIssueComment: failed to get issues: %v", err) 107 return 108 } 109 if len(issues) == 0 { 110 log.Printf("NewIssueComment: no issue found for %s", comment.IssueAt) 111 return 112 } 113 issue := issues[0] 114 115 var recipients []syntax.DID 116 recipients = append(recipients, syntax.DID(issue.Repo.Did)) 117 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() 122 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()...) 127 } 128 } 129 } else { 130 // not a reply, notify just the issue author 131 recipients = append(recipients, syntax.DID(issue.Did)) 132 } 133 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 140 var pullId *int64 141 142 n.notifyEvent( 143 actorDid, 144 recipients, 145 eventType, 146 entityType, 147 entityId, 148 repoId, 149 issueId, 150 pullId, 151 ) 152} 153 154func (n *databaseNotifier) DeleteIssue(ctx context.Context, issue *models.Issue) { 155 // no-op for now 156} 157 158func (n *databaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) { 159 actorDid := syntax.DID(follow.UserDid) 160 recipients := []syntax.DID{syntax.DID(follow.SubjectDid)} 161 eventType := models.NotificationTypeFollowed 162 entityType := "follow" 163 entityId := follow.UserDid 164 var repoId, issueId, pullId *int64 165 166 n.notifyEvent( 167 actorDid, 168 recipients, 169 eventType, 170 entityType, 171 entityId, 172 repoId, 173 issueId, 174 pullId, 175 ) 176} 177 178func (n *databaseNotifier) DeleteFollow(ctx context.Context, follow *models.Follow) { 179 // no-op 180} 181 182func (n *databaseNotifier) NewPull(ctx context.Context, pull *models.Pull) { 183 repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt))) 184 if err != nil { 185 log.Printf("NewPull: failed to get repos: %v", err) 186 return 187 } 188 189 // build the recipients list 190 // - owner of the repo 191 // - collaborators in the repo 192 var recipients []syntax.DID 193 recipients = append(recipients, syntax.DID(repo.Did)) 194 collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", repo.RepoAt())) 195 if err != nil { 196 log.Printf("failed to fetch collaborators: %v", err) 197 return 198 } 199 for _, c := range collaborators { 200 recipients = append(recipients, c.SubjectDid) 201 } 202 203 actorDid := syntax.DID(pull.OwnerDid) 204 eventType := models.NotificationTypePullCreated 205 entityType := "pull" 206 entityId := pull.PullAt().String() 207 repoId := &repo.Id 208 var issueId *int64 209 p := int64(pull.ID) 210 pullId := &p 211 212 n.notifyEvent( 213 actorDid, 214 recipients, 215 eventType, 216 entityType, 217 entityId, 218 repoId, 219 issueId, 220 pullId, 221 ) 222} 223 224func (n *databaseNotifier) NewPullComment(ctx context.Context, comment *models.PullComment) { 225 pull, err := db.GetPull(n.db, 226 syntax.ATURI(comment.RepoAt), 227 comment.PullId, 228 ) 229 if err != nil { 230 log.Printf("NewPullComment: failed to get pulls: %v", err) 231 return 232 } 233 234 repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", comment.RepoAt)) 235 if err != nil { 236 log.Printf("NewPullComment: failed to get repos: %v", err) 237 return 238 } 239 240 // build up the recipients list: 241 // - repo owner 242 // - all pull participants 243 var recipients []syntax.DID 244 recipients = append(recipients, syntax.DID(repo.Did)) 245 for _, p := range pull.Participants() { 246 recipients = append(recipients, syntax.DID(p)) 247 } 248 249 actorDid := syntax.DID(comment.OwnerDid) 250 eventType := models.NotificationTypePullCommented 251 entityType := "pull" 252 entityId := pull.PullAt().String() 253 repoId := &repo.Id 254 var issueId *int64 255 p := int64(pull.ID) 256 pullId := &p 257 258 n.notifyEvent( 259 actorDid, 260 recipients, 261 eventType, 262 entityType, 263 entityId, 264 repoId, 265 issueId, 266 pullId, 267 ) 268} 269 270func (n *databaseNotifier) UpdateProfile(ctx context.Context, profile *models.Profile) { 271 // no-op 272} 273 274func (n *databaseNotifier) DeleteString(ctx context.Context, did, rkey string) { 275 // no-op 276} 277 278func (n *databaseNotifier) EditString(ctx context.Context, string *models.String) { 279 // no-op 280} 281 282func (n *databaseNotifier) NewString(ctx context.Context, string *models.String) { 283 // no-op 284} 285 286func (n *databaseNotifier) NewIssueClosed(ctx context.Context, issue *models.Issue) { 287 // build up the recipients list: 288 // - repo owner 289 // - repo collaborators 290 // - all issue participants 291 var recipients []syntax.DID 292 recipients = append(recipients, syntax.DID(issue.Repo.Did)) 293 collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", issue.Repo.RepoAt())) 294 if err != nil { 295 log.Printf("failed to fetch collaborators: %v", err) 296 return 297 } 298 for _, c := range collaborators { 299 recipients = append(recipients, c.SubjectDid) 300 } 301 for _, p := range issue.Participants() { 302 recipients = append(recipients, syntax.DID(p)) 303 } 304 305 actorDid := syntax.DID(issue.Repo.Did) 306 eventType := models.NotificationTypeIssueClosed 307 entityType := "pull" 308 entityId := issue.AtUri().String() 309 repoId := &issue.Repo.Id 310 issueId := &issue.Id 311 var pullId *int64 312 313 n.notifyEvent( 314 actorDid, 315 recipients, 316 eventType, 317 entityType, 318 entityId, 319 repoId, 320 issueId, 321 pullId, 322 ) 323} 324 325func (n *databaseNotifier) NewPullMerged(ctx context.Context, pull *models.Pull) { 326 // Get repo details 327 repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt))) 328 if err != nil { 329 log.Printf("NewPullMerged: failed to get repos: %v", err) 330 return 331 } 332 333 // build up the recipients list: 334 // - repo owner 335 // - all pull participants 336 var recipients []syntax.DID 337 recipients = append(recipients, syntax.DID(repo.Did)) 338 collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", repo.RepoAt())) 339 if err != nil { 340 log.Printf("failed to fetch collaborators: %v", err) 341 return 342 } 343 for _, c := range collaborators { 344 recipients = append(recipients, c.SubjectDid) 345 } 346 for _, p := range pull.Participants() { 347 recipients = append(recipients, syntax.DID(p)) 348 } 349 350 actorDid := syntax.DID(repo.Did) 351 eventType := models.NotificationTypePullMerged 352 entityType := "pull" 353 entityId := pull.PullAt().String() 354 repoId := &repo.Id 355 var issueId *int64 356 p := int64(pull.ID) 357 pullId := &p 358 359 n.notifyEvent( 360 actorDid, 361 recipients, 362 eventType, 363 entityType, 364 entityId, 365 repoId, 366 issueId, 367 pullId, 368 ) 369} 370 371func (n *databaseNotifier) NewPullClosed(ctx context.Context, pull *models.Pull) { 372 // Get repo details 373 repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt))) 374 if err != nil { 375 log.Printf("NewPullMerged: failed to get repos: %v", err) 376 return 377 } 378 379 // build up the recipients list: 380 // - repo owner 381 // - all pull participants 382 var recipients []syntax.DID 383 recipients = append(recipients, syntax.DID(repo.Did)) 384 collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", repo.RepoAt())) 385 if err != nil { 386 log.Printf("failed to fetch collaborators: %v", err) 387 return 388 } 389 for _, c := range collaborators { 390 recipients = append(recipients, c.SubjectDid) 391 } 392 for _, p := range pull.Participants() { 393 recipients = append(recipients, syntax.DID(p)) 394 } 395 396 actorDid := syntax.DID(repo.Did) 397 eventType := models.NotificationTypePullClosed 398 entityType := "pull" 399 entityId := pull.PullAt().String() 400 repoId := &repo.Id 401 var issueId *int64 402 p := int64(pull.ID) 403 pullId := &p 404 405 n.notifyEvent( 406 actorDid, 407 recipients, 408 eventType, 409 entityType, 410 entityId, 411 repoId, 412 issueId, 413 pullId, 414 ) 415} 416 417func (n *databaseNotifier) notifyEvent( 418 actorDid syntax.DID, 419 recipients []syntax.DID, 420 eventType models.NotificationType, 421 entityType string, 422 entityId string, 423 repoId *int64, 424 issueId *int64, 425 pullId *int64, 426) { 427 recipientSet := make(map[syntax.DID]struct{}) 428 for _, did := range recipients { 429 // everybody except actor themselves 430 if did != actorDid { 431 recipientSet[did] = struct{}{} 432 } 433 } 434 435 prefMap, err := db.GetNotificationPreferences( 436 n.db, 437 db.FilterIn("user_did", slices.Collect(maps.Keys(recipientSet))), 438 ) 439 if err != nil { 440 // failed to get prefs for users 441 return 442 } 443 444 // create a transaction for bulk notification storage 445 tx, err := n.db.Begin() 446 if err != nil { 447 // failed to start tx 448 return 449 } 450 defer tx.Rollback() 451 452 // filter based on preferences 453 for recipientDid := range recipientSet { 454 prefs, ok := prefMap[recipientDid] 455 if !ok { 456 prefs = models.DefaultNotificationPreferences(recipientDid) 457 } 458 459 // skip users who don’t want this type 460 if !prefs.ShouldNotify(eventType) { 461 continue 462 } 463 464 // create notification 465 notif := &models.Notification{ 466 RecipientDid: recipientDid.String(), 467 ActorDid: actorDid.String(), 468 Type: eventType, 469 EntityType: entityType, 470 EntityId: entityId, 471 RepoId: repoId, 472 IssueId: issueId, 473 PullId: pullId, 474 } 475 476 if err := db.CreateNotification(tx, notif); err != nil { 477 log.Printf("notifyEvent: failed to create notification for %s: %v", recipientDid, err) 478 } 479 } 480 481 if err := tx.Commit(); err != nil { 482 // failed to commit 483 return 484 } 485}