From 2eedcf5d784529336c8cb04aefadefc909a561ab Mon Sep 17 00:00:00 2001 From: Seongmin Lee Date: Sat, 26 Jul 2025 10:59:02 +0900 Subject: [PATCH] appview/notify: notify users mentioned in issues Change-Id: knkwrvolorspoxoqttxurpplqqxvoyxo pass mentioned DIDs on `NewIssue*` events Signed-off-by: Seongmin Lee --- appview/indexer/notifier.go | 3 ++- appview/issues/issues.go | 25 +++++++++++++++++++++++-- appview/notify/db/db.go | 30 ++++++++++++++++++++++++------ appview/notify/merged_notifier.go | 9 +++++---- appview/notify/notifier.go | 12 ++++++------ appview/notify/posthog/notifier.go | 7 +++++-- 6 files changed, 65 insertions(+), 21 deletions(-) diff --git a/appview/indexer/notifier.go b/appview/indexer/notifier.go index d3125394..e23e7e50 100644 --- a/appview/indexer/notifier.go +++ b/appview/indexer/notifier.go @@ -3,6 +3,7 @@ package indexer import ( "context" + "github.com/bluesky-social/indigo/atproto/syntax" "tangled.org/core/appview/models" "tangled.org/core/appview/notify" "tangled.org/core/log" @@ -10,7 +11,7 @@ import ( var _ notify.Notifier = &Indexer{} -func (ix *Indexer) NewIssue(ctx context.Context, issue *models.Issue) { +func (ix *Indexer) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { l := log.FromContext(ctx).With("notifier", "indexer", "issue", issue) l.Debug("indexing new issue") err := ix.Issues.Index(ctx, *issue) diff --git a/appview/issues/issues.go b/appview/issues/issues.go index 15b5fad1..5bf5199a 100644 --- a/appview/issues/issues.go +++ b/appview/issues/issues.go @@ -24,6 +24,7 @@ import ( "tangled.org/core/appview/notify" "tangled.org/core/appview/oauth" "tangled.org/core/appview/pages" + "tangled.org/core/appview/pages/markup" "tangled.org/core/appview/pagination" "tangled.org/core/appview/reporesolver" "tangled.org/core/appview/validator" @@ -453,7 +454,17 @@ func (rp *Issues) NewIssueComment(w http.ResponseWriter, r *http.Request) { // notify about the new comment comment.Id = commentId - rp.notifier.NewIssueComment(r.Context(), &comment) + + rawMentions := markup.FindUserMentions(comment.Body) + idents := rp.idResolver.ResolveIdents(r.Context(), rawMentions) + l.Debug("parsed mentions", "raw", rawMentions, "idents", idents) + var mentions []syntax.DID + for _, ident := range idents { + if ident != nil && !ident.Handle.IsInvalidHandle() { + mentions = append(mentions, ident.DID) + } + } + rp.notifier.NewIssueComment(r.Context(), &comment, mentions) rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d#comment-%d", f.OwnerSlashRepo(), issue.IssueId, commentId)) } @@ -948,7 +959,17 @@ func (rp *Issues) NewIssue(w http.ResponseWriter, r *http.Request) { // everything is successful, do not rollback the atproto record atUri = "" - rp.notifier.NewIssue(r.Context(), issue) + + rawMentions := markup.FindUserMentions(issue.Body) + idents := rp.idResolver.ResolveIdents(r.Context(), rawMentions) + l.Debug("parsed mentions", "raw", rawMentions, "idents", idents) + var mentions []syntax.DID + for _, ident := range idents { + if ident != nil && !ident.Handle.IsInvalidHandle() { + mentions = append(mentions, ident.DID) + } + } + rp.notifier.NewIssue(r.Context(), issue, mentions) rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issue.IssueId)) return } diff --git a/appview/notify/db/db.go b/appview/notify/db/db.go index b5c76fab..56591ed0 100644 --- a/appview/notify/db/db.go +++ b/appview/notify/db/db.go @@ -64,7 +64,7 @@ func (n *databaseNotifier) DeleteStar(ctx context.Context, star *models.Star) { // no-op } -func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue) { +func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { // build the recipients list // - owner of the repo @@ -81,7 +81,6 @@ func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue) { } actorDid := syntax.DID(issue.Did) - eventType := models.NotificationTypeIssueCreated entityType := "issue" entityId := issue.AtUri().String() repoId := &issue.Repo.Id @@ -91,7 +90,17 @@ func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue) { n.notifyEvent( actorDid, recipients, - eventType, + models.NotificationTypeIssueCreated, + entityType, + entityId, + repoId, + issueId, + pullId, + ) + n.notifyEvent( + actorDid, + mentions, + models.NotificationTypeUserMentioned, entityType, entityId, repoId, @@ -100,7 +109,7 @@ func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue) { ) } -func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment) { +func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) { issues, err := db.GetIssues(n.db, db.FilterEq("at_uri", comment.IssueAt)) if err != nil { log.Printf("NewIssueComment: failed to get issues: %v", err) @@ -132,7 +141,6 @@ func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models. } actorDid := syntax.DID(comment.Did) - eventType := models.NotificationTypeIssueCommented entityType := "issue" entityId := issue.AtUri().String() repoId := &issue.Repo.Id @@ -142,7 +150,17 @@ func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models. n.notifyEvent( actorDid, recipients, - eventType, + models.NotificationTypeIssueCommented, + entityType, + entityId, + repoId, + issueId, + pullId, + ) + n.notifyEvent( + actorDid, + mentions, + models.NotificationTypeUserMentioned, entityType, entityId, repoId, diff --git a/appview/notify/merged_notifier.go b/appview/notify/merged_notifier.go index 494c3aab..93809863 100644 --- a/appview/notify/merged_notifier.go +++ b/appview/notify/merged_notifier.go @@ -6,6 +6,7 @@ import ( "reflect" "sync" + "github.com/bluesky-social/indigo/atproto/syntax" "tangled.org/core/appview/models" "tangled.org/core/log" ) @@ -53,12 +54,12 @@ func (m *mergedNotifier) DeleteStar(ctx context.Context, star *models.Star) { m.fanout("DeleteStar", ctx, star) } -func (m *mergedNotifier) NewIssue(ctx context.Context, issue *models.Issue) { - m.fanout("NewIssue", ctx, issue) +func (m *mergedNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { + m.fanout("NewIssue", ctx, issue, mentions) } -func (m *mergedNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment) { - m.fanout("NewIssueComment", ctx, comment) +func (m *mergedNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) { + m.fanout("NewIssueComment", ctx, comment, mentions) } func (m *mergedNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) { diff --git a/appview/notify/notifier.go b/appview/notify/notifier.go index 691c5865..60997220 100644 --- a/appview/notify/notifier.go +++ b/appview/notify/notifier.go @@ -13,8 +13,8 @@ type Notifier interface { NewStar(ctx context.Context, star *models.Star) DeleteStar(ctx context.Context, star *models.Star) - NewIssue(ctx context.Context, issue *models.Issue) - NewIssueComment(ctx context.Context, comment *models.IssueComment) + NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) + NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) DeleteIssue(ctx context.Context, issue *models.Issue) @@ -42,10 +42,10 @@ func (m *BaseNotifier) NewRepo(ctx context.Context, repo *models.Repo) {} func (m *BaseNotifier) NewStar(ctx context.Context, star *models.Star) {} func (m *BaseNotifier) DeleteStar(ctx context.Context, star *models.Star) {} -func (m *BaseNotifier) NewIssue(ctx context.Context, issue *models.Issue) {} -func (m *BaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment) {} -func (m *BaseNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) {} -func (m *BaseNotifier) DeleteIssue(ctx context.Context, issue *models.Issue) {} +func (m *BaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) {} +func (m *BaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) {} +func (m *BaseNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) {} +func (m *BaseNotifier) DeleteIssue(ctx context.Context, issue *models.Issue) {} func (m *BaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) {} func (m *BaseNotifier) DeleteFollow(ctx context.Context, follow *models.Follow) {} diff --git a/appview/notify/posthog/notifier.go b/appview/notify/posthog/notifier.go index 7dcc8ccc..aefd5d75 100644 --- a/appview/notify/posthog/notifier.go +++ b/appview/notify/posthog/notifier.go @@ -4,6 +4,7 @@ import ( "context" "log" + "github.com/bluesky-social/indigo/atproto/syntax" "github.com/posthog/posthog-go" "tangled.org/core/appview/models" "tangled.org/core/appview/notify" @@ -56,13 +57,14 @@ func (n *posthogNotifier) DeleteStar(ctx context.Context, star *models.Star) { } } -func (n *posthogNotifier) NewIssue(ctx context.Context, issue *models.Issue) { +func (n *posthogNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { err := n.client.Enqueue(posthog.Capture{ DistinctId: issue.Did, Event: "new_issue", Properties: posthog.Properties{ "repo_at": issue.RepoAt.String(), "issue_id": issue.IssueId, + "mentions": mentions, }, }) if err != nil { @@ -177,12 +179,13 @@ func (n *posthogNotifier) NewString(ctx context.Context, string *models.String) } } -func (n *posthogNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment) { +func (n *posthogNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) { err := n.client.Enqueue(posthog.Capture{ DistinctId: comment.Did, Event: "new_issue_comment", Properties: posthog.Properties{ "issue_at": comment.IssueAt, + "mentions": mentions, }, }) if err != nil { -- 2.43.0