Monorepo for Tangled tangled.org

appview/notify: notify users mentioned in issues

pass mentioned DIDs on `NewIssue*` events

Signed-off-by: Seongmin Lee <boltlessengineer@proton.me>

Changed files
+64 -19
appview
+2 -1
appview/indexer/notifier.go
··· 3 3 import ( 4 4 "context" 5 5 6 + "github.com/bluesky-social/indigo/atproto/syntax" 6 7 "tangled.org/core/appview/models" 7 8 "tangled.org/core/appview/notify" 8 9 "tangled.org/core/log" ··· 10 11 11 12 var _ notify.Notifier = &Indexer{} 12 13 13 - func (ix *Indexer) NewIssue(ctx context.Context, issue *models.Issue) { 14 + func (ix *Indexer) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { 14 15 l := log.FromContext(ctx).With("notifier", "indexer", "issue", issue) 15 16 l.Debug("indexing new issue") 16 17 err := ix.Issues.Index(ctx, *issue)
+23 -2
appview/issues/issues.go
··· 24 24 "tangled.org/core/appview/notify" 25 25 "tangled.org/core/appview/oauth" 26 26 "tangled.org/core/appview/pages" 27 + "tangled.org/core/appview/pages/markup" 27 28 "tangled.org/core/appview/pagination" 28 29 "tangled.org/core/appview/reporesolver" 29 30 "tangled.org/core/appview/validator" ··· 453 454 454 455 // notify about the new comment 455 456 comment.Id = commentId 456 - rp.notifier.NewIssueComment(r.Context(), &comment) 457 + 458 + rawMentions := markup.FindUserMentions(comment.Body) 459 + idents := rp.idResolver.ResolveIdents(r.Context(), rawMentions) 460 + l.Debug("parsed mentions", "raw", rawMentions, "idents", idents) 461 + var mentions []syntax.DID 462 + for _, ident := range idents { 463 + if ident != nil && !ident.Handle.IsInvalidHandle() { 464 + mentions = append(mentions, ident.DID) 465 + } 466 + } 467 + rp.notifier.NewIssueComment(r.Context(), &comment, mentions) 457 468 458 469 rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d#comment-%d", f.OwnerSlashRepo(), issue.IssueId, commentId)) 459 470 } ··· 948 959 949 960 // everything is successful, do not rollback the atproto record 950 961 atUri = "" 951 - rp.notifier.NewIssue(r.Context(), issue) 962 + 963 + rawMentions := markup.FindUserMentions(issue.Body) 964 + idents := rp.idResolver.ResolveIdents(r.Context(), rawMentions) 965 + l.Debug("parsed mentions", "raw", rawMentions, "idents", idents) 966 + var mentions []syntax.DID 967 + for _, ident := range idents { 968 + if ident != nil && !ident.Handle.IsInvalidHandle() { 969 + mentions = append(mentions, ident.DID) 970 + } 971 + } 972 + rp.notifier.NewIssue(r.Context(), issue, mentions) 952 973 rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issue.IssueId)) 953 974 return 954 975 }
+24 -6
appview/notify/db/db.go
··· 64 64 // no-op 65 65 } 66 66 67 - func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue) { 67 + func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { 68 68 69 69 // build the recipients list 70 70 // - owner of the repo ··· 81 81 } 82 82 83 83 actorDid := syntax.DID(issue.Did) 84 - eventType := models.NotificationTypeIssueCreated 85 84 entityType := "issue" 86 85 entityId := issue.AtUri().String() 87 86 repoId := &issue.Repo.Id ··· 91 90 n.notifyEvent( 92 91 actorDid, 93 92 recipients, 94 - eventType, 93 + models.NotificationTypeIssueCreated, 94 + entityType, 95 + entityId, 96 + repoId, 97 + issueId, 98 + pullId, 99 + ) 100 + n.notifyEvent( 101 + actorDid, 102 + mentions, 103 + models.NotificationTypeUserMentioned, 95 104 entityType, 96 105 entityId, 97 106 repoId, ··· 100 109 ) 101 110 } 102 111 103 - func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment) { 112 + func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) { 104 113 issues, err := db.GetIssues(n.db, db.FilterEq("at_uri", comment.IssueAt)) 105 114 if err != nil { 106 115 log.Printf("NewIssueComment: failed to get issues: %v", err) ··· 132 141 } 133 142 134 143 actorDid := syntax.DID(comment.Did) 135 - eventType := models.NotificationTypeIssueCommented 136 144 entityType := "issue" 137 145 entityId := issue.AtUri().String() 138 146 repoId := &issue.Repo.Id ··· 142 150 n.notifyEvent( 143 151 actorDid, 144 152 recipients, 145 - eventType, 153 + models.NotificationTypeIssueCommented, 154 + entityType, 155 + entityId, 156 + repoId, 157 + issueId, 158 + pullId, 159 + ) 160 + n.notifyEvent( 161 + actorDid, 162 + mentions, 163 + models.NotificationTypeUserMentioned, 146 164 entityType, 147 165 entityId, 148 166 repoId,
+5 -4
appview/notify/merged_notifier.go
··· 6 6 "reflect" 7 7 "sync" 8 8 9 + "github.com/bluesky-social/indigo/atproto/syntax" 9 10 "tangled.org/core/appview/models" 10 11 "tangled.org/core/log" 11 12 ) ··· 53 54 m.fanout("DeleteStar", ctx, star) 54 55 } 55 56 56 - func (m *mergedNotifier) NewIssue(ctx context.Context, issue *models.Issue) { 57 - m.fanout("NewIssue", ctx, issue) 57 + func (m *mergedNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { 58 + m.fanout("NewIssue", ctx, issue, mentions) 58 59 } 59 60 60 - func (m *mergedNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment) { 61 - m.fanout("NewIssueComment", ctx, comment) 61 + func (m *mergedNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) { 62 + m.fanout("NewIssueComment", ctx, comment, mentions) 62 63 } 63 64 64 65 func (m *mergedNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) {
+5 -4
appview/notify/notifier.go
··· 13 13 NewStar(ctx context.Context, star *models.Star) 14 14 DeleteStar(ctx context.Context, star *models.Star) 15 15 16 - NewIssue(ctx context.Context, issue *models.Issue) 17 - NewIssueComment(ctx context.Context, comment *models.IssueComment) 16 + NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) 17 + NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) 18 18 NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) 19 19 DeleteIssue(ctx context.Context, issue *models.Issue) 20 20 ··· 42 42 func (m *BaseNotifier) NewStar(ctx context.Context, star *models.Star) {} 43 43 func (m *BaseNotifier) DeleteStar(ctx context.Context, star *models.Star) {} 44 44 45 - func (m *BaseNotifier) NewIssue(ctx context.Context, issue *models.Issue) {} 46 - func (m *BaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment) {} 45 + func (m *BaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) {} 46 + func (m *BaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) { 47 + } 47 48 func (m *BaseNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) {} 48 49 func (m *BaseNotifier) DeleteIssue(ctx context.Context, issue *models.Issue) {} 49 50
+5 -2
appview/notify/posthog/notifier.go
··· 4 4 "context" 5 5 "log" 6 6 7 + "github.com/bluesky-social/indigo/atproto/syntax" 7 8 "github.com/posthog/posthog-go" 8 9 "tangled.org/core/appview/models" 9 10 "tangled.org/core/appview/notify" ··· 56 57 } 57 58 } 58 59 59 - func (n *posthogNotifier) NewIssue(ctx context.Context, issue *models.Issue) { 60 + func (n *posthogNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { 60 61 err := n.client.Enqueue(posthog.Capture{ 61 62 DistinctId: issue.Did, 62 63 Event: "new_issue", 63 64 Properties: posthog.Properties{ 64 65 "repo_at": issue.RepoAt.String(), 65 66 "issue_id": issue.IssueId, 67 + "mentions": mentions, 66 68 }, 67 69 }) 68 70 if err != nil { ··· 177 179 } 178 180 } 179 181 180 - func (n *posthogNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment) { 182 + func (n *posthogNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) { 181 183 err := n.client.Enqueue(posthog.Capture{ 182 184 DistinctId: comment.Did, 183 185 Event: "new_issue_comment", 184 186 Properties: posthog.Properties{ 185 187 "issue_at": comment.IssueAt, 188 + "mentions": mentions, 186 189 }, 187 190 }) 188 191 if err != nil {