Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

appview/notify: merge new comment events into one

Signed-off-by: Seongmin Lee <git@boltless.me>

author
Seongmin Lee
date (May 22, 2026, 7:55 PM +0900) commit d4073c24 parent d8f993c7 change-id lkyxomvs
+164 -176
+1 -1
appview/issues/issues.go
··· 551 551 return 552 552 } 553 553 554 - rp.notifier.NewIssueComment(r.Context(), &comment, mentions) 554 + rp.notifier.NewComment(r.Context(), &comment, mentions) 555 555 556 556 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f) 557 557 rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d#comment-%d", ownerSlashRepo, issue.IssueId, comment.Id))
+133 -131
appview/notify/db/db.go
··· 5 5 "slices" 6 6 7 7 "github.com/bluesky-social/indigo/atproto/syntax" 8 + "tangled.org/core/api/tangled" 8 9 "tangled.org/core/appview/db" 9 10 "tangled.org/core/appview/models" 10 11 "tangled.org/core/appview/notify" ··· 81 82 // no-op 82 83 } 83 84 84 - func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { 85 + func (n *databaseNotifier) NewComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { 85 86 l := log.FromContext(ctx) 86 87 87 - collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_did", string(issue.RepoDid))) 88 - if err != nil { 89 - l.Error("failed to fetch collaborators", "err", err) 90 - return 91 - } 88 + var ( 89 + // built the recipients list: 90 + // - the owner of the repo 91 + // - | if the comment is a reply -> everybody on that thread 92 + // | if the comment is a top level -> just the issue owner 93 + // - remove mentioned users from the recipients list 94 + recipients = sets.New[syntax.DID]() 95 + entityType string 96 + entityId string 97 + repoId *int64 98 + issueId *int64 99 + pullId *int64 100 + ) 92 101 93 - // build the recipients list 94 - // - owner of the repo 95 - // - collaborators in the repo 96 - // - remove users already mentioned 97 - recipients := sets.Singleton(syntax.DID(issue.Repo.Did)) 98 - for _, c := range collaborators { 99 - recipients.Insert(c.SubjectDid) 100 - } 101 - for _, m := range mentions { 102 - recipients.Remove(m) 103 - } 102 + subjectAt := syntax.ATURI(comment.Subject.Uri) 104 103 105 - actorDid := syntax.DID(issue.Did) 106 - entityType := "issue" 107 - entityId := issue.AtUri().String() 108 - repoId := &issue.Repo.Id 109 - issueId := &issue.Id 110 - var pullId *int64 104 + switch subjectAt.Collection() { 105 + case tangled.RepoIssueNSID: 106 + issues, err := db.GetIssues( 107 + n.db, 108 + orm.FilterEq("at_uri", subjectAt), 109 + ) 110 + if err != nil { 111 + l.Error("failed to get issues", "err", err) 112 + return 113 + } 114 + if len(issues) == 0 { 115 + l.Error("no issue found", "subject", comment.Subject) 116 + return 117 + } 118 + issue := issues[0] 111 119 112 - n.notifyEvent( 113 - ctx, 114 - actorDid, 115 - recipients, 116 - models.NotificationTypeIssueCreated, 117 - entityType, 118 - entityId, 119 - repoId, 120 - issueId, 121 - pullId, 122 - ) 120 + recipients.Insert(syntax.DID(issue.Repo.Did)) 121 + if comment.IsReply() { 122 + // if this comment is a reply, then notify everybody in that thread 123 + parent := *comment.ReplyTo 124 + 125 + // find the parent thread, and add all DIDs from here to the recipient list 126 + for _, t := range issue.CommentList() { 127 + if t.Self.AtUri() == syntax.ATURI(parent.Uri) { 128 + for _, p := range t.Participants() { 129 + recipients.Insert(p) 130 + } 131 + } 132 + } 133 + } else { 134 + // not a reply, notify just the issue author 135 + recipients.Insert(syntax.DID(issue.Did)) 136 + } 137 + 138 + entityType = "issue" 139 + entityId = issue.AtUri().String() 140 + repoId = &issue.Repo.Id 141 + issueId = &issue.Id 142 + 143 + for _, m := range mentions { 144 + recipients.Remove(m) 145 + } 146 + 147 + n.notifyEvent( 148 + ctx, 149 + comment.Did, 150 + recipients, 151 + models.NotificationTypeIssueCommented, 152 + entityType, 153 + entityId, 154 + repoId, 155 + issueId, 156 + pullId, 157 + ) 158 + 159 + case tangled.RepoPullNSID: 160 + pull, err := db.GetPull( 161 + n.db, 162 + orm.FilterEq("owner_did", subjectAt.Authority()), 163 + orm.FilterEq("rkey", subjectAt.RecordKey()), 164 + ) 165 + if err != nil { 166 + l.Error("NewComment: failed to get pull", "err", err) 167 + return 168 + } 169 + 170 + pull.Repo, err = db.GetRepo(n.db, orm.FilterEq("repo_did", pull.RepoDid)) 171 + if err != nil { 172 + l.Error("NewComment: failed to get repo", "err", err) 173 + return 174 + } 175 + 176 + recipients.Insert(syntax.DID(pull.Repo.Did)) 177 + for _, p := range pull.Participants() { 178 + recipients.Insert(syntax.DID(p)) 179 + } 180 + 181 + entityType = "pull" 182 + entityId = pull.AtUri().String() 183 + repoId = &pull.Repo.Id 184 + p := int64(pull.ID) 185 + pullId = &p 186 + 187 + for _, m := range mentions { 188 + recipients.Remove(m) 189 + } 190 + 191 + n.notifyEvent( 192 + ctx, 193 + comment.Did, 194 + recipients, 195 + models.NotificationTypePullCommented, 196 + entityType, 197 + entityId, 198 + repoId, 199 + issueId, 200 + pullId, 201 + ) 202 + default: 203 + return // no-op 204 + } 205 + 123 206 n.notifyEvent( 124 207 ctx, 125 - actorDid, 208 + comment.Did, 126 209 sets.Collect(slices.Values(mentions)), 127 210 models.NotificationTypeUserMentioned, 128 211 entityType, ··· 133 216 ) 134 217 } 135 218 136 - func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { 219 + func (n *databaseNotifier) DeleteComment(ctx context.Context, comment *models.Comment) { 220 + // no-op 221 + } 222 + 223 + func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { 137 224 l := log.FromContext(ctx) 138 225 139 - issues, err := db.GetIssues(n.db, orm.FilterEq("at_uri", comment.Subject)) 226 + collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_did", string(issue.RepoDid))) 140 227 if err != nil { 141 - l.Error("failed to get issues", "err", err) 142 - return 143 - } 144 - if len(issues) == 0 { 145 - l.Error("no issue found for", "err", comment.Subject) 228 + l.Error("failed to fetch collaborators", "err", err) 146 229 return 147 230 } 148 - issue := issues[0] 149 231 150 - // built the recipients list: 151 - // - the owner of the repo 152 - // - | if the comment is a reply -> everybody on that thread 153 - // | if the comment is a top level -> just the issue owner 154 - // - remove mentioned users from the recipients list 232 + // build the recipients list 233 + // - owner of the repo 234 + // - collaborators in the repo 235 + // - remove users already mentioned 155 236 recipients := sets.Singleton(syntax.DID(issue.Repo.Did)) 156 - 157 - if comment.IsReply() { 158 - // if this comment is a reply, then notify everybody in that thread 159 - parent := *comment.ReplyTo 160 - 161 - // find the parent thread, and add all DIDs from here to the recipient list 162 - for _, t := range issue.CommentList() { 163 - if t.Self.AtUri() == syntax.ATURI(parent.Uri) { 164 - for _, p := range t.Participants() { 165 - recipients.Insert(p) 166 - } 167 - } 168 - } 169 - } else { 170 - // not a reply, notify just the issue author 171 - recipients.Insert(syntax.DID(issue.Did)) 237 + for _, c := range collaborators { 238 + recipients.Insert(c.SubjectDid) 172 239 } 173 - 174 240 for _, m := range mentions { 175 241 recipients.Remove(m) 176 242 } 177 243 178 - actorDid := syntax.DID(comment.Did) 244 + actorDid := syntax.DID(issue.Did) 179 245 entityType := "issue" 180 246 entityId := issue.AtUri().String() 181 247 repoId := &issue.Repo.Id ··· 186 252 ctx, 187 253 actorDid, 188 254 recipients, 189 - models.NotificationTypeIssueCommented, 255 + models.NotificationTypeIssueCreated, 190 256 entityType, 191 257 entityId, 192 258 repoId, ··· 274 340 actorDid, 275 341 recipients, 276 342 eventType, 277 - entityType, 278 - entityId, 279 - repoId, 280 - issueId, 281 - pullId, 282 - ) 283 - } 284 - 285 - func (n *databaseNotifier) NewPullComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { 286 - l := log.FromContext(ctx) 287 - 288 - subjectAt := syntax.ATURI(comment.Subject.Uri) 289 - pull, err := db.GetPull(n.db, 290 - orm.FilterEq("owner_did", subjectAt.Authority()), 291 - orm.FilterEq("rkey", subjectAt.RecordKey()), 292 - ) 293 - if err != nil { 294 - l.Error("failed to get pull", "err", err) 295 - return 296 - } 297 - 298 - repo, err := db.GetRepo(n.db, orm.FilterEq("repo_did", pull.RepoDid)) 299 - if err != nil { 300 - l.Error("failed to get repos", "err", err) 301 - return 302 - } 303 - 304 - // build up the recipients list: 305 - // - repo owner 306 - // - all pull participants 307 - // - remove those already mentioned 308 - recipients := sets.Singleton(syntax.DID(repo.Did)) 309 - for _, p := range pull.Participants() { 310 - recipients.Insert(p) 311 - } 312 - for _, m := range mentions { 313 - recipients.Remove(m) 314 - } 315 - 316 - actorDid := comment.Did 317 - eventType := models.NotificationTypePullCommented 318 - entityType := "pull" 319 - entityId := pull.AtUri().String() 320 - repoId := &repo.Id 321 - var issueId *int64 322 - p := int64(pull.ID) 323 - pullId := &p 324 - 325 - n.notifyEvent( 326 - ctx, 327 - actorDid, 328 - recipients, 329 - eventType, 330 - entityType, 331 - entityId, 332 - repoId, 333 - issueId, 334 - pullId, 335 - ) 336 - n.notifyEvent( 337 - ctx, 338 - actorDid, 339 - sets.Collect(slices.Values(mentions)), 340 - models.NotificationTypeUserMentioned, 341 343 entityType, 342 344 entityId, 343 345 repoId,
+9 -10
appview/notify/logging/notifier.go
··· 46 46 l.inner.DeleteStar(ctx, star) 47 47 } 48 48 49 + func (l *loggingNotifier) NewComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { 50 + ctx = tlog.IntoContext(ctx, tlog.SubLogger(l.logger, "NewComment")) 51 + l.inner.NewComment(ctx, comment, mentions) 52 + } 53 + func (l *loggingNotifier) DeleteComment(ctx context.Context, comment *models.Comment) { 54 + ctx = tlog.IntoContext(ctx, tlog.SubLogger(l.logger, "DeleteComment")) 55 + l.inner.DeleteComment(ctx, comment) 56 + } 57 + 49 58 func (l *loggingNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { 50 59 ctx = tlog.IntoContext(ctx, tlog.SubLogger(l.logger, "NewIssue")) 51 60 l.inner.NewIssue(ctx, issue, mentions) 52 - } 53 - 54 - func (l *loggingNotifier) NewIssueComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { 55 - ctx = tlog.IntoContext(ctx, tlog.SubLogger(l.logger, "NewIssueComment")) 56 - l.inner.NewIssueComment(ctx, comment, mentions) 57 61 } 58 62 59 63 func (l *loggingNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) { ··· 89 93 func (l *loggingNotifier) NewPull(ctx context.Context, pull *models.Pull) { 90 94 ctx = tlog.IntoContext(ctx, tlog.SubLogger(l.logger, "NewPull")) 91 95 l.inner.NewPull(ctx, pull) 92 - } 93 - 94 - func (l *loggingNotifier) NewPullComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { 95 - ctx = tlog.IntoContext(ctx, tlog.SubLogger(l.logger, "NewPullComment")) 96 - l.inner.NewPullComment(ctx, comment, mentions) 97 96 } 98 97 99 98 func (l *loggingNotifier) NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) {
+8 -8
appview/notify/merged_notifier.go
··· 50 50 m.fanout(func(n Notifier) { n.DeleteStar(ctx, star) }) 51 51 } 52 52 53 - func (m *mergedNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { 54 - m.fanout(func(n Notifier) { n.NewIssue(ctx, issue, mentions) }) 53 + func (m *mergedNotifier) NewComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { 54 + m.fanout(func(n Notifier) { n.NewComment(ctx, comment, mentions) }) 55 55 } 56 56 57 - func (m *mergedNotifier) NewIssueComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { 58 - m.fanout(func(n Notifier) { n.NewIssueComment(ctx, comment, mentions) }) 57 + func (m *mergedNotifier) DeleteComment(ctx context.Context, comment *models.Comment) { 58 + m.fanout(func(n Notifier) { n.DeleteComment(ctx, comment) }) 59 + } 60 + 61 + func (m *mergedNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) { 62 + m.fanout(func(n Notifier) { n.NewIssue(ctx, issue, mentions) }) 59 63 } 60 64 61 65 func (m *mergedNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) { ··· 84 88 85 89 func (m *mergedNotifier) NewPull(ctx context.Context, pull *models.Pull) { 86 90 m.fanout(func(n Notifier) { n.NewPull(ctx, pull) }) 87 - } 88 - 89 - func (m *mergedNotifier) NewPullComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { 90 - m.fanout(func(n Notifier) { n.NewPullComment(ctx, comment, mentions) }) 91 91 } 92 92 93 93 func (m *mergedNotifier) NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) {
+8 -7
appview/notify/notifier.go
··· 15 15 NewStar(ctx context.Context, star *models.Star) 16 16 DeleteStar(ctx context.Context, star *models.Star) 17 17 18 + NewComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) 19 + DeleteComment(ctx context.Context, comment *models.Comment) 20 + 18 21 NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) 19 - NewIssueComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) 20 22 NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) 21 23 DeleteIssue(ctx context.Context, issue *models.Issue) 22 24 ··· 24 26 DeleteFollow(ctx context.Context, follow *models.Follow) 25 27 26 28 NewPull(ctx context.Context, pull *models.Pull) 27 - NewPullComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) 28 29 NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) 29 30 30 31 NewIssueLabelOp(ctx context.Context, issue *models.Issue) ··· 54 55 func (m *BaseNotifier) NewStar(ctx context.Context, star *models.Star) {} 55 56 func (m *BaseNotifier) DeleteStar(ctx context.Context, star *models.Star) {} 56 57 57 - func (m *BaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) {} 58 - func (m *BaseNotifier) NewIssueComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { 58 + func (m *BaseNotifier) NewComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { 59 59 } 60 + func (m *BaseNotifier) DeleteComment(ctx context.Context, comment *models.Comment) {} 61 + 62 + func (m *BaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) {} 60 63 func (m *BaseNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) {} 61 64 func (m *BaseNotifier) DeleteIssue(ctx context.Context, issue *models.Issue) {} 62 65 ··· 66 69 func (m *BaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) {} 67 70 func (m *BaseNotifier) DeleteFollow(ctx context.Context, follow *models.Follow) {} 68 71 69 - func (m *BaseNotifier) NewPull(ctx context.Context, pull *models.Pull) {} 70 - func (m *BaseNotifier) NewPullComment(ctx context.Context, models *models.Comment, mentions []syntax.DID) { 71 - } 72 + func (m *BaseNotifier) NewPull(ctx context.Context, pull *models.Pull) {} 72 73 func (m *BaseNotifier) NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) {} 73 74 74 75 func (m *BaseNotifier) UpdateProfile(ctx context.Context, profile *models.Profile) {}
+4 -18
appview/notify/posthog/notifier.go
··· 108 108 } 109 109 } 110 110 111 - func (n *posthogNotifier) NewPullComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { 112 - err := n.client.Enqueue(posthog.Capture{ 113 - DistinctId: comment.Did.String(), 114 - Event: "new_pull_comment", 115 - Properties: posthog.Properties{ 116 - "pull_at": comment.Subject, 117 - "mentions": mentions, 118 - }, 119 - }) 120 - if err != nil { 121 - log.Println("failed to enqueue posthog event:", err) 122 - } 123 - } 124 - 125 111 func (n *posthogNotifier) NewPullClosed(ctx context.Context, pull *models.Pull) { 126 112 err := n.client.Enqueue(posthog.Capture{ 127 113 DistinctId: pull.OwnerDid, ··· 212 198 } 213 199 } 214 200 215 - func (n *posthogNotifier) NewIssueComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { 201 + func (n *posthogNotifier) NewComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { 216 202 err := n.client.Enqueue(posthog.Capture{ 217 203 DistinctId: comment.Did.String(), 218 - Event: "new_issue_comment", 204 + Event: "new_comment", 219 205 Properties: posthog.Properties{ 220 - "issue_at": comment.Subject.Uri, 221 - "mentions": mentions, 206 + "subject_at": comment.Subject.Uri, 207 + "mentions": mentions, 222 208 }, 223 209 }) 224 210 if err != nil {
+1 -1
appview/pulls/comment.go
··· 172 172 return 173 173 } 174 174 175 - s.notifier.NewPullComment(r.Context(), &comment, mentions) 175 + s.notifier.NewComment(r.Context(), &comment, mentions) 176 176 177 177 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f) 178 178 s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d#comment-%d", ownerSlashRepo, pull.PullId, comment.Id))