diff --git a/appview/issues/issues.go b/appview/issues/issues.go --- a/appview/issues/issues.go +++ b/appview/issues/issues.go @@ -551,7 +551,7 @@ rp.pages.Notice(w, "issue-comment", "Failed to create comment, try again later.") return } - rp.notifier.NewIssueComment(r.Context(), &comment, mentions) + rp.notifier.NewComment(r.Context(), &comment, mentions) ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f) rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d#comment-%d", ownerSlashRepo, issue.IssueId, comment.Id)) diff --git a/appview/notify/db/db.go b/appview/notify/db/db.go --- a/appview/notify/db/db.go +++ b/appview/notify/db/db.go @@ -5,6 +5,7 @@ "context" "slices" "github.com/bluesky-social/indigo/atproto/syntax" + "tangled.org/core/api/tangled" "tangled.org/core/appview/db" "tangled.org/core/appview/models" "tangled.org/core/appview/notify" @@ -81,48 +82,130 @@ func (n *databaseNotifier) DeleteStar(ctx context.Context, star *models.Star) { // no-op } -func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { +func (n *databaseNotifier) NewComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { l := log.FromContext(ctx) - collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_did", string(issue.RepoDid))) - if err != nil { - l.Error("failed to fetch collaborators", "err", err) - return - } + var ( + // built the recipients list: + // - the owner of the repo + // - | if the comment is a reply -> everybody on that thread + // | if the comment is a top level -> just the issue owner + // - remove mentioned users from the recipients list + recipients = sets.New[syntax.DID]() + entityType string + entityId string + repoId *int64 + issueId *int64 + pullId *int64 + ) - // build the recipients list - // - owner of the repo - // - collaborators in the repo - // - remove users already mentioned - recipients := sets.Singleton(syntax.DID(issue.Repo.Did)) - for _, c := range collaborators { - recipients.Insert(c.SubjectDid) - } - for _, m := range mentions { - recipients.Remove(m) - } + subjectAt := syntax.ATURI(comment.Subject.Uri) - actorDid := syntax.DID(issue.Did) - entityType := "issue" - entityId := issue.AtUri().String() - repoId := &issue.Repo.Id - issueId := &issue.Id - var pullId *int64 + switch subjectAt.Collection() { + case tangled.RepoIssueNSID: + issues, err := db.GetIssues( + n.db, + orm.FilterEq("at_uri", subjectAt), + ) + if err != nil { + l.Error("failed to get issues", "err", err) + return + } + if len(issues) == 0 { + l.Error("no issue found", "subject", comment.Subject) + return + } + issue := issues[0] - n.notifyEvent( - ctx, - actorDid, - recipients, - models.NotificationTypeIssueCreated, - entityType, - entityId, - repoId, - issueId, - pullId, - ) + recipients.Insert(syntax.DID(issue.Repo.Did)) + if comment.IsReply() { + // if this comment is a reply, then notify everybody in that thread + parent := *comment.ReplyTo + + // find the parent thread, and add all DIDs from here to the recipient list + for _, t := range issue.CommentList() { + if t.Self.AtUri() == syntax.ATURI(parent.Uri) { + for _, p := range t.Participants() { + recipients.Insert(p) + } + } + } + } else { + // not a reply, notify just the issue author + recipients.Insert(syntax.DID(issue.Did)) + } + + entityType = "issue" + entityId = issue.AtUri().String() + repoId = &issue.Repo.Id + issueId = &issue.Id + + for _, m := range mentions { + recipients.Remove(m) + } + + n.notifyEvent( + ctx, + comment.Did, + recipients, + models.NotificationTypeIssueCommented, + entityType, + entityId, + repoId, + issueId, + pullId, + ) + + case tangled.RepoPullNSID: + pull, err := db.GetPull( + n.db, + orm.FilterEq("owner_did", subjectAt.Authority()), + orm.FilterEq("rkey", subjectAt.RecordKey()), + ) + if err != nil { + l.Error("NewComment: failed to get pull", "err", err) + return + } + + pull.Repo, err = db.GetRepo(n.db, orm.FilterEq("repo_did", pull.RepoDid)) + if err != nil { + l.Error("NewComment: failed to get repo", "err", err) + return + } + + recipients.Insert(syntax.DID(pull.Repo.Did)) + for _, p := range pull.Participants() { + recipients.Insert(syntax.DID(p)) + } + + entityType = "pull" + entityId = pull.AtUri().String() + repoId = &pull.Repo.Id + p := int64(pull.ID) + pullId = &p + + for _, m := range mentions { + recipients.Remove(m) + } + + n.notifyEvent( + ctx, + comment.Did, + recipients, + models.NotificationTypePullCommented, + entityType, + entityId, + repoId, + issueId, + pullId, + ) + default: + return // no-op + } + n.notifyEvent( ctx, - actorDid, + comment.Did, sets.Collect(slices.Values(mentions)), models.NotificationTypeUserMentioned, entityType, @@ -133,49 +216,32 @@ pullId, ) } -func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { +func (n *databaseNotifier) DeleteComment(ctx context.Context, comment *models.Comment) { + // no-op +} + +func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { l := log.FromContext(ctx) - issues, err := db.GetIssues(n.db, orm.FilterEq("at_uri", comment.Subject)) + collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_did", string(issue.RepoDid))) if err != nil { - l.Error("failed to get issues", "err", err) - return - } - if len(issues) == 0 { - l.Error("no issue found for", "err", comment.Subject) + l.Error("failed to fetch collaborators", "err", err) return } - issue := issues[0] - // built the recipients list: - // - the owner of the repo - // - | if the comment is a reply -> everybody on that thread - // | if the comment is a top level -> just the issue owner - // - remove mentioned users from the recipients list + // build the recipients list + // - owner of the repo + // - collaborators in the repo + // - remove users already mentioned recipients := sets.Singleton(syntax.DID(issue.Repo.Did)) - - if comment.IsReply() { - // if this comment is a reply, then notify everybody in that thread - parent := *comment.ReplyTo - - // find the parent thread, and add all DIDs from here to the recipient list - for _, t := range issue.CommentList() { - if t.Self.AtUri() == syntax.ATURI(parent.Uri) { - for _, p := range t.Participants() { - recipients.Insert(p) - } - } - } - } else { - // not a reply, notify just the issue author - recipients.Insert(syntax.DID(issue.Did)) + for _, c := range collaborators { + recipients.Insert(c.SubjectDid) } - for _, m := range mentions { recipients.Remove(m) } - actorDid := syntax.DID(comment.Did) + actorDid := syntax.DID(issue.Did) entityType := "issue" entityId := issue.AtUri().String() repoId := &issue.Repo.Id @@ -186,7 +252,7 @@ n.notifyEvent( ctx, actorDid, recipients, - models.NotificationTypeIssueCommented, + models.NotificationTypeIssueCreated, entityType, entityId, repoId, @@ -274,70 +340,6 @@ ctx, actorDid, recipients, eventType, - entityType, - entityId, - repoId, - issueId, - pullId, - ) -} - -func (n *databaseNotifier) NewPullComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { - l := log.FromContext(ctx) - - subjectAt := syntax.ATURI(comment.Subject.Uri) - pull, err := db.GetPull(n.db, - orm.FilterEq("owner_did", subjectAt.Authority()), - orm.FilterEq("rkey", subjectAt.RecordKey()), - ) - if err != nil { - l.Error("failed to get pull", "err", err) - return - } - - repo, err := db.GetRepo(n.db, orm.FilterEq("repo_did", pull.RepoDid)) - if err != nil { - l.Error("failed to get repos", "err", err) - return - } - - // build up the recipients list: - // - repo owner - // - all pull participants - // - remove those already mentioned - recipients := sets.Singleton(syntax.DID(repo.Did)) - for _, p := range pull.Participants() { - recipients.Insert(p) - } - for _, m := range mentions { - recipients.Remove(m) - } - - actorDid := comment.Did - eventType := models.NotificationTypePullCommented - entityType := "pull" - entityId := pull.AtUri().String() - repoId := &repo.Id - var issueId *int64 - p := int64(pull.ID) - pullId := &p - - n.notifyEvent( - ctx, - actorDid, - recipients, - eventType, - entityType, - entityId, - repoId, - issueId, - pullId, - ) - n.notifyEvent( - ctx, - actorDid, - sets.Collect(slices.Values(mentions)), - models.NotificationTypeUserMentioned, entityType, entityId, repoId, diff --git a/appview/notify/logging/notifier.go b/appview/notify/logging/notifier.go --- a/appview/notify/logging/notifier.go +++ b/appview/notify/logging/notifier.go @@ -46,14 +46,18 @@ ctx = tlog.IntoContext(ctx, tlog.SubLogger(l.logger, "DeleteStar")) l.inner.DeleteStar(ctx, star) } +func (l *loggingNotifier) NewComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { + ctx = tlog.IntoContext(ctx, tlog.SubLogger(l.logger, "NewComment")) + l.inner.NewComment(ctx, comment, mentions) +} +func (l *loggingNotifier) DeleteComment(ctx context.Context, comment *models.Comment) { + ctx = tlog.IntoContext(ctx, tlog.SubLogger(l.logger, "DeleteComment")) + l.inner.DeleteComment(ctx, comment) +} + func (l *loggingNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { ctx = tlog.IntoContext(ctx, tlog.SubLogger(l.logger, "NewIssue")) l.inner.NewIssue(ctx, issue, mentions) -} - -func (l *loggingNotifier) NewIssueComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { - ctx = tlog.IntoContext(ctx, tlog.SubLogger(l.logger, "NewIssueComment")) - l.inner.NewIssueComment(ctx, comment, mentions) } func (l *loggingNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) { @@ -89,11 +93,6 @@ func (l *loggingNotifier) NewPull(ctx context.Context, pull *models.Pull) { ctx = tlog.IntoContext(ctx, tlog.SubLogger(l.logger, "NewPull")) l.inner.NewPull(ctx, pull) -} - -func (l *loggingNotifier) NewPullComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { - ctx = tlog.IntoContext(ctx, tlog.SubLogger(l.logger, "NewPullComment")) - l.inner.NewPullComment(ctx, comment, mentions) } func (l *loggingNotifier) NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) { diff --git a/appview/notify/merged_notifier.go b/appview/notify/merged_notifier.go --- a/appview/notify/merged_notifier.go +++ b/appview/notify/merged_notifier.go @@ -50,12 +50,16 @@ func (m *mergedNotifier) DeleteStar(ctx context.Context, star *models.Star) { m.fanout(func(n Notifier) { n.DeleteStar(ctx, star) }) } -func (m *mergedNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { - m.fanout(func(n Notifier) { n.NewIssue(ctx, issue, mentions) }) +func (m *mergedNotifier) NewComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { + m.fanout(func(n Notifier) { n.NewComment(ctx, comment, mentions) }) } -func (m *mergedNotifier) NewIssueComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { - m.fanout(func(n Notifier) { n.NewIssueComment(ctx, comment, mentions) }) +func (m *mergedNotifier) DeleteComment(ctx context.Context, comment *models.Comment) { + m.fanout(func(n Notifier) { n.DeleteComment(ctx, comment) }) +} + +func (m *mergedNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { + m.fanout(func(n Notifier) { n.NewIssue(ctx, issue, mentions) }) } func (m *mergedNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) { @@ -84,10 +88,6 @@ } func (m *mergedNotifier) NewPull(ctx context.Context, pull *models.Pull) { m.fanout(func(n Notifier) { n.NewPull(ctx, pull) }) -} - -func (m *mergedNotifier) NewPullComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { - m.fanout(func(n Notifier) { n.NewPullComment(ctx, comment, mentions) }) } func (m *mergedNotifier) NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) { diff --git a/appview/notify/notifier.go b/appview/notify/notifier.go --- a/appview/notify/notifier.go +++ b/appview/notify/notifier.go @@ -15,8 +15,10 @@ NewStar(ctx context.Context, star *models.Star) DeleteStar(ctx context.Context, star *models.Star) + NewComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) + DeleteComment(ctx context.Context, comment *models.Comment) + NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) - NewIssueComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) DeleteIssue(ctx context.Context, issue *models.Issue) @@ -24,7 +26,6 @@ NewFollow(ctx context.Context, follow *models.Follow) DeleteFollow(ctx context.Context, follow *models.Follow) NewPull(ctx context.Context, pull *models.Pull) - NewPullComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) NewIssueLabelOp(ctx context.Context, issue *models.Issue) @@ -54,9 +55,11 @@ 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, mentions []syntax.DID) {} -func (m *BaseNotifier) NewIssueComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { +func (m *BaseNotifier) NewComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { } +func (m *BaseNotifier) DeleteComment(ctx context.Context, comment *models.Comment) {} + +func (m *BaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, 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) {} @@ -66,9 +69,7 @@ func (m *BaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) {} func (m *BaseNotifier) DeleteFollow(ctx context.Context, follow *models.Follow) {} -func (m *BaseNotifier) NewPull(ctx context.Context, pull *models.Pull) {} -func (m *BaseNotifier) NewPullComment(ctx context.Context, models *models.Comment, mentions []syntax.DID) { -} +func (m *BaseNotifier) NewPull(ctx context.Context, pull *models.Pull) {} func (m *BaseNotifier) NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) {} func (m *BaseNotifier) UpdateProfile(ctx context.Context, profile *models.Profile) {} diff --git a/appview/notify/posthog/notifier.go b/appview/notify/posthog/notifier.go --- a/appview/notify/posthog/notifier.go +++ b/appview/notify/posthog/notifier.go @@ -108,20 +108,6 @@ log.Println("failed to enqueue posthog event:", err) } } -func (n *posthogNotifier) NewPullComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { - err := n.client.Enqueue(posthog.Capture{ - DistinctId: comment.Did.String(), - Event: "new_pull_comment", - Properties: posthog.Properties{ - "pull_at": comment.Subject, - "mentions": mentions, - }, - }) - if err != nil { - log.Println("failed to enqueue posthog event:", err) - } -} - func (n *posthogNotifier) NewPullClosed(ctx context.Context, pull *models.Pull) { err := n.client.Enqueue(posthog.Capture{ DistinctId: pull.OwnerDid, @@ -212,13 +198,13 @@ log.Println("failed to enqueue posthog event:", err) } } -func (n *posthogNotifier) NewIssueComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { +func (n *posthogNotifier) NewComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { err := n.client.Enqueue(posthog.Capture{ DistinctId: comment.Did.String(), - Event: "new_issue_comment", + Event: "new_comment", Properties: posthog.Properties{ - "issue_at": comment.Subject.Uri, - "mentions": mentions, + "subject_at": comment.Subject.Uri, + "mentions": mentions, }, }) if err != nil { diff --git a/appview/pulls/comment.go b/appview/pulls/comment.go --- a/appview/pulls/comment.go +++ b/appview/pulls/comment.go @@ -172,7 +172,7 @@ s.pages.Notice(w, "pull-comment", "Failed to create comment.") return } - s.notifier.NewPullComment(r.Context(), &comment, mentions) + s.notifier.NewComment(r.Context(), &comment, mentions) ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f) s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d#comment-%d", ownerSlashRepo, pull.PullId, comment.Id))