appview: replace PullComment to Comment #865

open
opened by boltless.me targeting master from sl/wnrvrwyvrlzo
Changed files
+406 -215
appview
+197
appview/db/comments.go
···
··· 1 + package db 2 + 3 + import ( 4 + "database/sql" 5 + "fmt" 6 + "maps" 7 + "slices" 8 + "sort" 9 + "strings" 10 + "time" 11 + 12 + "github.com/bluesky-social/indigo/atproto/syntax" 13 + "tangled.org/core/appview/models" 14 + ) 15 + 16 + func PutComment(tx *sql.Tx, c *models.Comment) error { 17 + result, err := tx.Exec( 18 + `insert into comments ( 19 + did, 20 + rkey, 21 + subject_at, 22 + reply_to, 23 + body, 24 + pull_submission_id, 25 + created 26 + ) 27 + values (?, ?, ?, ?, ?, ?, ?) 28 + on conflict(did, rkey) do update set 29 + subject_at = excluded.subject_at, 30 + reply_to = excluded.reply_to, 31 + body = excluded.body, 32 + edited = case 33 + when 34 + comments.subject_at != excluded.subject_at 35 + or comments.body != excluded.body 36 + or comments.reply_to != excluded.reply_to 37 + then ? 38 + else comments.edited 39 + end`, 40 + c.Did, 41 + c.Rkey, 42 + c.Subject, 43 + c.ReplyTo, 44 + c.Body, 45 + c.PullSubmissionId, 46 + c.Created.Format(time.RFC3339), 47 + time.Now().Format(time.RFC3339), 48 + ) 49 + if err != nil { 50 + return err 51 + } 52 + 53 + c.Id, err = result.LastInsertId() 54 + if err != nil { 55 + return err 56 + } 57 + 58 + if err := putReferences(tx, c.AtUri(), c.References); err != nil { 59 + return fmt.Errorf("put reference_links: %w", err) 60 + } 61 + 62 + return nil 63 + } 64 + 65 + func DeleteComments(e Execer, filters ...filter) error { 66 + var conditions []string 67 + var args []any 68 + for _, filter := range filters { 69 + conditions = append(conditions, filter.Condition()) 70 + args = append(args, filter.Arg()...) 71 + } 72 + 73 + whereClause := "" 74 + if conditions != nil { 75 + whereClause = " where " + strings.Join(conditions, " and ") 76 + } 77 + 78 + query := fmt.Sprintf(`update comments set body = "", deleted = strftime('%%Y-%%m-%%dT%%H:%%M:%%SZ', 'now') %s`, whereClause) 79 + 80 + _, err := e.Exec(query, args...) 81 + return err 82 + } 83 + 84 + func GetComments(e Execer, filters ...filter) ([]models.Comment, error) { 85 + commentMap := make(map[string]*models.Comment) 86 + 87 + var conditions []string 88 + var args []any 89 + for _, filter := range filters { 90 + conditions = append(conditions, filter.Condition()) 91 + args = append(args, filter.Arg()...) 92 + } 93 + 94 + whereClause := "" 95 + if conditions != nil { 96 + whereClause = " where " + strings.Join(conditions, " and ") 97 + } 98 + 99 + query := fmt.Sprintf(` 100 + select 101 + id, 102 + did, 103 + rkey, 104 + subject_at, 105 + reply_to, 106 + body, 107 + pull_submission_id, 108 + created, 109 + edited, 110 + deleted 111 + from 112 + comments 113 + %s 114 + `, whereClause) 115 + 116 + rows, err := e.Query(query, args...) 117 + if err != nil { 118 + return nil, err 119 + } 120 + 121 + for rows.Next() { 122 + var comment models.Comment 123 + var created string 124 + var rkey, edited, deleted, replyTo sql.Null[string] 125 + err := rows.Scan( 126 + &comment.Id, 127 + &comment.Did, 128 + &rkey, 129 + &comment.Subject, 130 + &replyTo, 131 + &comment.Body, 132 + &comment.PullSubmissionId, 133 + &created, 134 + &edited, 135 + &deleted, 136 + ) 137 + if err != nil { 138 + return nil, err 139 + } 140 + 141 + // this is a remnant from old times, newer comments always have rkey 142 + if rkey.Valid { 143 + comment.Rkey = rkey.V 144 + } 145 + 146 + if t, err := time.Parse(time.RFC3339, created); err == nil { 147 + comment.Created = t 148 + } 149 + 150 + if edited.Valid { 151 + if t, err := time.Parse(time.RFC3339, edited.V); err == nil { 152 + comment.Edited = &t 153 + } 154 + } 155 + 156 + if deleted.Valid { 157 + if t, err := time.Parse(time.RFC3339, deleted.V); err == nil { 158 + comment.Deleted = &t 159 + } 160 + } 161 + 162 + if replyTo.Valid { 163 + rt := syntax.ATURI(replyTo.V) 164 + comment.ReplyTo = &rt 165 + } 166 + 167 + atUri := comment.AtUri().String() 168 + commentMap[atUri] = &comment 169 + } 170 + 171 + if err := rows.Err(); err != nil { 172 + return nil, err 173 + } 174 + 175 + // collect references from each comments 176 + commentAts := slices.Collect(maps.Keys(commentMap)) 177 + allReferencs, err := GetReferencesAll(e, FilterIn("from_at", commentAts)) 178 + if err != nil { 179 + return nil, fmt.Errorf("failed to query reference_links: %w", err) 180 + } 181 + for commentAt, references := range allReferencs { 182 + if comment, ok := commentMap[commentAt.String()]; ok { 183 + comment.References = references 184 + } 185 + } 186 + 187 + var comments []models.Comment 188 + for _, c := range commentMap { 189 + comments = append(comments, *c) 190 + } 191 + 192 + sort.Slice(comments, func(i, j int) bool { 193 + return comments[i].Created.After(comments[j].Created) 194 + }) 195 + 196 + return comments, nil 197 + }
+32
appview/db/db.go
··· 1174 return err 1175 }) 1176 1177 return &DB{ 1178 db, 1179 logger,
··· 1174 return err 1175 }) 1176 1177 + // not migrating existing comments here 1178 + // all legacy comments will be dropped 1179 + runMigration(conn, logger, "add-comments-table", func(tx *sql.Tx) error { 1180 + _, err := tx.Exec(` 1181 + drop table comments; 1182 + 1183 + create table comments ( 1184 + -- identifiers 1185 + id integer primary key autoincrement, 1186 + did text not null, 1187 + rkey text not null, 1188 + at_uri text generated always as ('at://' || did || '/' || 'sh.tangled.comment' || '/' || rkey) stored, 1189 + 1190 + -- at identifiers 1191 + subject_at text not null, 1192 + reply_to text, -- at_uri of parent comment 1193 + 1194 + pull_submission_id integer, -- dirty fix until we atprotate the pull-rounds 1195 + 1196 + -- content 1197 + body text not null, 1198 + created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 1199 + edited text, 1200 + deleted text, 1201 + 1202 + -- constraints 1203 + unique(did, rkey) 1204 + ); 1205 + `) 1206 + return err 1207 + }) 1208 + 1209 return &DB{ 1210 db, 1211 logger,
+6 -121
appview/db/pulls.go
··· 446 return nil, err 447 } 448 449 - // Get comments for all submissions using GetPullComments 450 submissionIds := slices.Collect(maps.Keys(submissionMap)) 451 - comments, err := GetPullComments(e, FilterIn("submission_id", submissionIds)) 452 if err != nil { 453 return nil, fmt.Errorf("failed to get pull comments: %w", err) 454 } 455 for _, comment := range comments { 456 - if submission, ok := submissionMap[comment.SubmissionId]; ok { 457 - submission.Comments = append(submission.Comments, comment) 458 } 459 } 460 ··· 474 return m, nil 475 } 476 477 - func GetPullComments(e Execer, filters ...filter) ([]models.PullComment, error) { 478 - var conditions []string 479 - var args []any 480 - for _, filter := range filters { 481 - conditions = append(conditions, filter.Condition()) 482 - args = append(args, filter.Arg()...) 483 - } 484 - 485 - whereClause := "" 486 - if conditions != nil { 487 - whereClause = " where " + strings.Join(conditions, " and ") 488 - } 489 - 490 - query := fmt.Sprintf(` 491 - select 492 - id, 493 - pull_id, 494 - submission_id, 495 - repo_at, 496 - owner_did, 497 - comment_at, 498 - body, 499 - created 500 - from 501 - pull_comments 502 - %s 503 - order by 504 - created asc 505 - `, whereClause) 506 - 507 - rows, err := e.Query(query, args...) 508 - if err != nil { 509 - return nil, err 510 - } 511 - defer rows.Close() 512 - 513 - commentMap := make(map[string]*models.PullComment) 514 - for rows.Next() { 515 - var comment models.PullComment 516 - var createdAt string 517 - err := rows.Scan( 518 - &comment.ID, 519 - &comment.PullId, 520 - &comment.SubmissionId, 521 - &comment.RepoAt, 522 - &comment.OwnerDid, 523 - &comment.CommentAt, 524 - &comment.Body, 525 - &createdAt, 526 - ) 527 - if err != nil { 528 - return nil, err 529 - } 530 - 531 - if t, err := time.Parse(time.RFC3339, createdAt); err == nil { 532 - comment.Created = t 533 - } 534 - 535 - atUri := comment.AtUri().String() 536 - commentMap[atUri] = &comment 537 - } 538 - 539 - if err := rows.Err(); err != nil { 540 - return nil, err 541 - } 542 - 543 - // collect references for each comments 544 - commentAts := slices.Collect(maps.Keys(commentMap)) 545 - allReferencs, err := GetReferencesAll(e, FilterIn("from_at", commentAts)) 546 - if err != nil { 547 - return nil, fmt.Errorf("failed to query reference_links: %w", err) 548 - } 549 - for commentAt, references := range allReferencs { 550 - if comment, ok := commentMap[commentAt.String()]; ok { 551 - comment.References = references 552 - } 553 - } 554 - 555 - var comments []models.PullComment 556 - for _, c := range commentMap { 557 - comments = append(comments, *c) 558 - } 559 - 560 - sort.Slice(comments, func(i, j int) bool { 561 - return comments[i].Created.Before(comments[j].Created) 562 - }) 563 - 564 - return comments, nil 565 - } 566 - 567 // timeframe here is directly passed into the sql query filter, and any 568 // timeframe in the past should be negative; e.g.: "-3 months" 569 func GetPullsByOwnerDid(e Execer, did, timeframe string) ([]models.Pull, error) { ··· 640 return pulls, nil 641 } 642 643 - func NewPullComment(tx *sql.Tx, comment *models.PullComment) (int64, error) { 644 - query := `insert into pull_comments (owner_did, repo_at, submission_id, comment_at, pull_id, body) values (?, ?, ?, ?, ?, ?)` 645 - res, err := tx.Exec( 646 - query, 647 - comment.OwnerDid, 648 - comment.RepoAt, 649 - comment.SubmissionId, 650 - comment.CommentAt, 651 - comment.PullId, 652 - comment.Body, 653 - ) 654 - if err != nil { 655 - return 0, err 656 - } 657 - 658 - i, err := res.LastInsertId() 659 - if err != nil { 660 - return 0, err 661 - } 662 - 663 - if err := putReferences(tx, comment.AtUri(), comment.References); err != nil { 664 - return 0, fmt.Errorf("put reference_links: %w", err) 665 - } 666 - 667 - return i, nil 668 - } 669 - 670 func SetPullState(e Execer, repoAt syntax.ATURI, pullId int, pullState models.PullState) error { 671 _, err := e.Exec( 672 `update pulls set state = ? where repo_at = ? and pull_id = ? and (state <> ? or state <> ?)`,
··· 446 return nil, err 447 } 448 449 + // Get comments for all submissions using GetComments 450 submissionIds := slices.Collect(maps.Keys(submissionMap)) 451 + comments, err := GetComments(e, FilterIn("pull_submission_id", submissionIds)) 452 if err != nil { 453 return nil, fmt.Errorf("failed to get pull comments: %w", err) 454 } 455 for _, comment := range comments { 456 + if comment.PullSubmissionId != nil { 457 + if submission, ok := submissionMap[*comment.PullSubmissionId]; ok { 458 + submission.Comments = append(submission.Comments, comment) 459 + } 460 } 461 } 462 ··· 476 return m, nil 477 } 478 479 // timeframe here is directly passed into the sql query filter, and any 480 // timeframe in the past should be negative; e.g.: "-3 months" 481 func GetPullsByOwnerDid(e Execer, did, timeframe string) ([]models.Pull, error) { ··· 552 return pulls, nil 553 } 554 555 func SetPullState(e Execer, repoAt syntax.ATURI, pullId int, pullState models.PullState) error { 556 _, err := e.Exec( 557 `update pulls set state = ? where repo_at = ? and pull_id = ? and (state <> ? or state <> ?)`,
+7 -8
appview/db/reference.go
··· 123 values %s 124 ) 125 select 126 - p.owner_did, p.rkey, 127 - c.comment_at 128 from input inp 129 join repos r 130 on r.did = inp.owner_did ··· 132 join pulls p 133 on p.repo_at = r.at_uri 134 and p.pull_id = inp.pull_id 135 - left join pull_comments c 136 on inp.comment_id is not null 137 - and c.repo_at = r.at_uri and c.pull_id = p.pull_id 138 and c.id = inp.comment_id 139 `, 140 strings.Join(vals, ","), ··· 292 return nil, fmt.Errorf("get pull backlinks: %w", err) 293 } 294 backlinks = append(backlinks, ls...) 295 - ls, err = getPullCommentBacklinks(e, backlinksMap[tangled.RepoPullCommentNSID]) 296 if err != nil { 297 return nil, fmt.Errorf("get pull_comment backlinks: %w", err) 298 } ··· 427 if len(aturis) == 0 { 428 return nil, nil 429 } 430 - filter := FilterIn("c.comment_at", aturis) 431 rows, err := e.Query( 432 fmt.Sprintf( 433 `select r.did, r.name, p.pull_id, c.id, p.title, p.state 434 from repos r 435 join pulls p 436 on r.at_uri = p.repo_at 437 - join pull_comments c 438 - on r.at_uri = c.repo_at and p.pull_id = c.pull_id 439 where %s`, 440 filter.Condition(), 441 ),
··· 123 values %s 124 ) 125 select 126 + p.owner_did, p.rkey, c.at_uri 127 from input inp 128 join repos r 129 on r.did = inp.owner_did ··· 131 join pulls p 132 on p.repo_at = r.at_uri 133 and p.pull_id = inp.pull_id 134 + left join comments c 135 on inp.comment_id is not null 136 + and c.subject_at = ('at://' || p.owner_did || '/' || 'sh.tangled.repo.pull' || '/' || p.rkey) 137 and c.id = inp.comment_id 138 `, 139 strings.Join(vals, ","), ··· 291 return nil, fmt.Errorf("get pull backlinks: %w", err) 292 } 293 backlinks = append(backlinks, ls...) 294 + ls, err = getPullCommentBacklinks(e, backlinksMap[tangled.CommentNSID]) 295 if err != nil { 296 return nil, fmt.Errorf("get pull_comment backlinks: %w", err) 297 } ··· 426 if len(aturis) == 0 { 427 return nil, nil 428 } 429 + filter := FilterIn("c.at_uri", aturis) 430 rows, err := e.Query( 431 fmt.Sprintf( 432 `select r.did, r.name, p.pull_id, c.id, p.title, p.state 433 from repos r 434 join pulls p 435 on r.at_uri = p.repo_at 436 + join comments c 437 + on ('at://' || p.owner_did || '/' || 'sh.tangled.repo.pull' || '/' || p.rkey) = c.subject_at 438 where %s`, 439 filter.Condition(), 440 ),
+117
appview/models/comment.go
···
··· 1 + package models 2 + 3 + import ( 4 + "fmt" 5 + "strings" 6 + "time" 7 + 8 + "github.com/bluesky-social/indigo/atproto/syntax" 9 + "tangled.org/core/api/tangled" 10 + ) 11 + 12 + type Comment struct { 13 + Id int64 14 + Did syntax.DID 15 + Rkey string 16 + Subject syntax.ATURI 17 + ReplyTo *syntax.ATURI 18 + Body string 19 + Created time.Time 20 + Edited *time.Time 21 + Deleted *time.Time 22 + Mentions []syntax.DID 23 + References []syntax.ATURI 24 + PullSubmissionId *int 25 + } 26 + 27 + func (c *Comment) AtUri() syntax.ATURI { 28 + return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", c.Did, tangled.CommentNSID, c.Rkey)) 29 + } 30 + 31 + func (c *Comment) AsRecord() tangled.Comment { 32 + mentions := make([]string, len(c.Mentions)) 33 + for i, did := range c.Mentions { 34 + mentions[i] = string(did) 35 + } 36 + references := make([]string, len(c.References)) 37 + for i, uri := range c.References { 38 + references[i] = string(uri) 39 + } 40 + var replyTo *string 41 + if c.ReplyTo != nil { 42 + replyToStr := c.ReplyTo.String() 43 + replyTo = &replyToStr 44 + } 45 + return tangled.Comment{ 46 + Subject: c.Subject.String(), 47 + Body: c.Body, 48 + CreatedAt: c.Created.Format(time.RFC3339), 49 + ReplyTo: replyTo, 50 + Mentions: mentions, 51 + References: references, 52 + } 53 + } 54 + 55 + func (c *Comment) IsTopLevel() bool { 56 + return c.ReplyTo == nil 57 + } 58 + 59 + func (c *Comment) IsReply() bool { 60 + return c.ReplyTo != nil 61 + } 62 + 63 + func (c *Comment) Validate() error { 64 + // TODO: sanitize the body and then trim space 65 + if sb := strings.TrimSpace(c.Body); sb == "" { 66 + return fmt.Errorf("body is empty after HTML sanitization") 67 + } 68 + 69 + // if it's for PR, PullSubmissionId should not be nil 70 + if c.Subject.Collection().String() == tangled.RepoPullNSID { 71 + if c.PullSubmissionId == nil { 72 + return fmt.Errorf("PullSubmissionId should not be nil") 73 + } 74 + } 75 + return nil 76 + } 77 + 78 + func CommentFromRecord(did, rkey string, record tangled.Comment) (*Comment, error) { 79 + created, err := time.Parse(time.RFC3339, record.CreatedAt) 80 + if err != nil { 81 + created = time.Now() 82 + } 83 + 84 + ownerDid := did 85 + 86 + if _, err = syntax.ParseATURI(record.Subject); err != nil { 87 + return nil, err 88 + } 89 + 90 + i := record 91 + mentions := make([]syntax.DID, len(record.Mentions)) 92 + for i, did := range record.Mentions { 93 + mentions[i] = syntax.DID(did) 94 + } 95 + references := make([]syntax.ATURI, len(record.References)) 96 + for i, uri := range i.References { 97 + references[i] = syntax.ATURI(uri) 98 + } 99 + var replyTo *syntax.ATURI 100 + if record.ReplyTo != nil { 101 + replyToAtUri := syntax.ATURI(*record.ReplyTo) 102 + replyTo = &replyToAtUri 103 + } 104 + 105 + comment := Comment{ 106 + Did: syntax.DID(ownerDid), 107 + Rkey: rkey, 108 + Body: record.Body, 109 + Subject: syntax.ATURI(record.Subject), 110 + ReplyTo: replyTo, 111 + Created: created, 112 + Mentions: mentions, 113 + References: references, 114 + } 115 + 116 + return &comment, nil 117 + }
+2 -46
appview/models/pull.go
··· 138 RoundNumber int 139 Patch string 140 Combined string 141 - Comments []PullComment 142 SourceRev string // include the rev that was used to create this submission: only for branch/fork PRs 143 144 // meta 145 Created time.Time 146 } 147 148 - type PullComment struct { 149 - // ids 150 - ID int 151 - PullId int 152 - SubmissionId int 153 - 154 - // at ids 155 - RepoAt string 156 - OwnerDid string 157 - CommentAt string 158 - 159 - // content 160 - Body string 161 - 162 - // meta 163 - Mentions []syntax.DID 164 - References []syntax.ATURI 165 - 166 - // meta 167 - Created time.Time 168 - } 169 - 170 - func (p *PullComment) AtUri() syntax.ATURI { 171 - return syntax.ATURI(p.CommentAt) 172 - } 173 - 174 - // func (p *PullComment) AsRecord() tangled.RepoPullComment { 175 - // mentions := make([]string, len(p.Mentions)) 176 - // for i, did := range p.Mentions { 177 - // mentions[i] = string(did) 178 - // } 179 - // references := make([]string, len(p.References)) 180 - // for i, uri := range p.References { 181 - // references[i] = string(uri) 182 - // } 183 - // return tangled.RepoPullComment{ 184 - // Pull: p.PullAt, 185 - // Body: p.Body, 186 - // Mentions: mentions, 187 - // References: references, 188 - // CreatedAt: p.Created.Format(time.RFC3339), 189 - // } 190 - // } 191 - 192 func (p *Pull) LastRoundNumber() int { 193 return len(p.Submissions) - 1 194 } ··· 289 addParticipant(s.PullAt.Authority().String()) 290 291 for _, c := range s.Comments { 292 - addParticipant(c.OwnerDid) 293 } 294 295 return participants
··· 138 RoundNumber int 139 Patch string 140 Combined string 141 + Comments []Comment 142 SourceRev string // include the rev that was used to create this submission: only for branch/fork PRs 143 144 // meta 145 Created time.Time 146 } 147 148 func (p *Pull) LastRoundNumber() int { 149 return len(p.Submissions) - 1 150 } ··· 245 addParticipant(s.PullAt.Authority().String()) 246 247 for _, c := range s.Comments { 248 + addParticipant(c.Did.String()) 249 } 250 251 return participants
+11 -6
appview/notify/db/db.go
··· 248 ) 249 } 250 251 - func (n *databaseNotifier) NewPullComment(ctx context.Context, comment *models.PullComment, mentions []syntax.DID) { 252 - pull, err := db.GetPull(n.db, 253 - syntax.ATURI(comment.RepoAt), 254 - comment.PullId, 255 ) 256 if err != nil { 257 log.Printf("NewPullComment: failed to get pulls: %v", err) 258 return 259 } 260 261 - repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", comment.RepoAt)) 262 if err != nil { 263 log.Printf("NewPullComment: failed to get repos: %v", err) 264 return ··· 273 recipients = append(recipients, syntax.DID(p)) 274 } 275 276 - actorDid := syntax.DID(comment.OwnerDid) 277 eventType := models.NotificationTypePullCommented 278 entityType := "pull" 279 entityId := pull.AtUri().String()
··· 248 ) 249 } 250 251 + func (n *databaseNotifier) NewPullComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { 252 + pulls, err := db.GetPulls(n.db, 253 + db.FilterEq("owner_did", comment.Subject.Authority()), 254 + db.FilterEq("rkey", comment.Subject.RecordKey()), 255 ) 256 if err != nil { 257 log.Printf("NewPullComment: failed to get pulls: %v", err) 258 return 259 } 260 + if len(pulls) == 0 { 261 + log.Printf("NewPullComment: no pull found for %s", comment.Subject) 262 + return 263 + } 264 + pull := pulls[0] 265 266 + repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", pull.RepoAt)) 267 if err != nil { 268 log.Printf("NewPullComment: failed to get repos: %v", err) 269 return ··· 278 recipients = append(recipients, syntax.DID(p)) 279 } 280 281 + actorDid := comment.Did 282 eventType := models.NotificationTypePullCommented 283 entityType := "pull" 284 entityId := pull.AtUri().String()
+1 -1
appview/notify/merged_notifier.go
··· 82 m.fanout("NewPull", ctx, pull) 83 } 84 85 - func (m *mergedNotifier) NewPullComment(ctx context.Context, comment *models.PullComment, mentions []syntax.DID) { 86 m.fanout("NewPullComment", ctx, comment, mentions) 87 } 88
··· 82 m.fanout("NewPull", ctx, pull) 83 } 84 85 + func (m *mergedNotifier) NewPullComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { 86 m.fanout("NewPullComment", ctx, comment, mentions) 87 } 88
+2 -2
appview/notify/notifier.go
··· 22 DeleteFollow(ctx context.Context, follow *models.Follow) 23 24 NewPull(ctx context.Context, pull *models.Pull) 25 - NewPullComment(ctx context.Context, comment *models.PullComment, mentions []syntax.DID) 26 NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) 27 28 UpdateProfile(ctx context.Context, profile *models.Profile) ··· 52 func (m *BaseNotifier) DeleteFollow(ctx context.Context, follow *models.Follow) {} 53 54 func (m *BaseNotifier) NewPull(ctx context.Context, pull *models.Pull) {} 55 - func (m *BaseNotifier) NewPullComment(ctx context.Context, models *models.PullComment, mentions []syntax.DID) { 56 } 57 func (m *BaseNotifier) NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) {} 58
··· 22 DeleteFollow(ctx context.Context, follow *models.Follow) 23 24 NewPull(ctx context.Context, pull *models.Pull) 25 + NewPullComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) 26 NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) 27 28 UpdateProfile(ctx context.Context, profile *models.Profile) ··· 52 func (m *BaseNotifier) DeleteFollow(ctx context.Context, follow *models.Follow) {} 53 54 func (m *BaseNotifier) NewPull(ctx context.Context, pull *models.Pull) {} 55 + func (m *BaseNotifier) NewPullComment(ctx context.Context, models *models.Comment, mentions []syntax.DID) { 56 } 57 func (m *BaseNotifier) NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) {} 58
+3 -4
appview/notify/posthog/notifier.go
··· 86 } 87 } 88 89 - func (n *posthogNotifier) NewPullComment(ctx context.Context, comment *models.PullComment, mentions []syntax.DID) { 90 err := n.client.Enqueue(posthog.Capture{ 91 - DistinctId: comment.OwnerDid, 92 Event: "new_pull_comment", 93 Properties: posthog.Properties{ 94 - "repo_at": comment.RepoAt, 95 - "pull_id": comment.PullId, 96 "mentions": mentions, 97 }, 98 })
··· 86 } 87 } 88 89 + func (n *posthogNotifier) NewPullComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { 90 err := n.client.Enqueue(posthog.Capture{ 91 + DistinctId: comment.Did.String(), 92 Event: "new_pull_comment", 93 Properties: posthog.Properties{ 94 + "pull_at": comment.Subject, 95 "mentions": mentions, 96 }, 97 })
+3 -3
appview/pages/templates/repo/pulls/pull.html
··· 165 166 <div class="md:pl-[3.5rem] flex flex-col gap-2 mt-2 relative"> 167 {{ range $cidx, $c := .Comments }} 168 - <div id="comment-{{$c.ID}}" class="bg-white dark:bg-gray-800 rounded drop-shadow-sm py-2 px-4 relative w-full"> 169 {{ if gt $cidx 0 }} 170 <div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div> 171 {{ end }} 172 <div class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1"> 173 - {{ template "user/fragments/picHandleLink" $c.OwnerDid }} 174 <span class="before:content-['·']"></span> 175 - <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="#comment-{{.ID}}">{{ template "repo/fragments/time" $c.Created }}</a> 176 </div> 177 <div class="prose dark:prose-invert"> 178 {{ $c.Body | markdown }}
··· 165 166 <div class="md:pl-[3.5rem] flex flex-col gap-2 mt-2 relative"> 167 {{ range $cidx, $c := .Comments }} 168 + <div id="comment-{{$c.Id}}" class="bg-white dark:bg-gray-800 rounded drop-shadow-sm py-2 px-4 relative w-full"> 169 {{ if gt $cidx 0 }} 170 <div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div> 171 {{ end }} 172 <div class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1"> 173 + {{ template "user/fragments/picHandleLink" $c.Did.String }} 174 <span class="before:content-['·']"></span> 175 + <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="#comment-{{.Id}}">{{ template "repo/fragments/time" $c.Created }}</a> 176 </div> 177 <div class="prose dark:prose-invert"> 178 {{ $c.Body | markdown }}
+1 -1
appview/pulls/opengraph.go
··· 276 } 277 278 // Get comment count from database 279 - comments, err := db.GetPullComments(s.db, db.FilterEq("pull_id", pull.ID)) 280 if err != nil { 281 log.Printf("failed to get pull comments: %v", err) 282 }
··· 276 } 277 278 // Get comment count from database 279 + comments, err := db.GetComments(s.db, db.FilterEq("subject_at", pull.AtUri())) 280 if err != nil { 281 log.Printf("failed to get pull comments: %v", err) 282 }
+24 -23
appview/pulls/pulls.go
··· 740 } 741 defer tx.Rollback() 742 743 - createdAt := time.Now().Format(time.RFC3339) 744 745 client, err := s.oauth.AuthorizedClient(r) 746 if err != nil { ··· 748 s.pages.Notice(w, "pull-comment", "Failed to create comment.") 749 return 750 } 751 - atResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 752 - Collection: tangled.RepoPullCommentNSID, 753 Repo: user.Did, 754 - Rkey: tid.TID(), 755 Record: &lexutil.LexiconTypeDecoder{ 756 - Val: &tangled.RepoPullComment{ 757 - Pull: pull.AtUri().String(), 758 - Body: body, 759 - CreatedAt: createdAt, 760 - }, 761 }, 762 }) 763 if err != nil { ··· 766 return 767 } 768 769 - comment := &models.PullComment{ 770 - OwnerDid: user.Did, 771 - RepoAt: f.RepoAt().String(), 772 - PullId: pull.PullId, 773 - Body: body, 774 - CommentAt: atResp.Uri, 775 - SubmissionId: pull.Submissions[roundNumber].ID, 776 - Mentions: mentions, 777 - References: references, 778 - } 779 - 780 // Create the pull comment in the database with the commentAt field 781 - commentId, err := db.NewPullComment(tx, comment) 782 if err != nil { 783 log.Println("failed to create pull comment", err) 784 s.pages.Notice(w, "pull-comment", "Failed to create comment.") ··· 792 return 793 } 794 795 - s.notifier.NewPullComment(r.Context(), comment, mentions) 796 797 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f) 798 - s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d#comment-%d", ownerSlashRepo, pull.PullId, commentId)) 799 return 800 } 801 }
··· 740 } 741 defer tx.Rollback() 742 743 + comment := models.Comment{ 744 + Did: syntax.DID(user.Did), 745 + Rkey: tid.TID(), 746 + Subject: pull.AtUri(), 747 + ReplyTo: nil, 748 + Body: body, 749 + Created: time.Now(), 750 + Mentions: mentions, 751 + References: references, 752 + PullSubmissionId: &pull.Submissions[roundNumber].ID, 753 + } 754 + if err = comment.Validate(); err != nil { 755 + log.Println("failed to validate comment", err) 756 + s.pages.Notice(w, "pull-comment", "Failed to create comment.") 757 + return 758 + } 759 + record := comment.AsRecord() 760 761 client, err := s.oauth.AuthorizedClient(r) 762 if err != nil { ··· 764 s.pages.Notice(w, "pull-comment", "Failed to create comment.") 765 return 766 } 767 + _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 768 + Collection: tangled.CommentNSID, 769 Repo: user.Did, 770 + Rkey: comment.Rkey, 771 Record: &lexutil.LexiconTypeDecoder{ 772 + Val: &record, 773 }, 774 }) 775 if err != nil { ··· 778 return 779 } 780 781 // Create the pull comment in the database with the commentAt field 782 + err = db.PutComment(tx, &comment) 783 if err != nil { 784 log.Println("failed to create pull comment", err) 785 s.pages.Notice(w, "pull-comment", "Failed to create comment.") ··· 793 return 794 } 795 796 + s.notifier.NewPullComment(r.Context(), &comment, mentions) 797 798 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f) 799 + s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d#comment-%d", ownerSlashRepo, pull.PullId, comment.Id)) 800 return 801 } 802 }