Monorepo for Tangled tangled.org

appview: replace PullComment to Comment #865

open opened by boltless.me targeting master from sl/wnrvrwyvrlzo
Labels

None yet.

assignee

None yet.

Participants 2
AT URI
at://did:plc:xasnlahkri4ewmbuzly2rlc5/sh.tangled.repo.pull/3m7iohv2yb422
+556 -199
Diff #5
+202
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/api/tangled" 14 + "tangled.org/core/appview/models" 15 + "tangled.org/core/orm" 16 + ) 17 + 18 + func PutComment(tx *sql.Tx, c *models.Comment) error { 19 + if c.Collection == "" { 20 + c.Collection = tangled.CommentNSID 21 + } 22 + result, err := tx.Exec( 23 + `insert into comments ( 24 + did, 25 + collection, 26 + rkey, 27 + subject_at, 28 + reply_to, 29 + body, 30 + pull_submission_id, 31 + created 32 + ) 33 + values (?, ?, ?, ?, ?, ?, ?, ?) 34 + on conflict(did, collection, rkey) do update set 35 + subject_at = excluded.subject_at, 36 + reply_to = excluded.reply_to, 37 + body = excluded.body, 38 + edited = case 39 + when 40 + comments.subject_at != excluded.subject_at 41 + or comments.body != excluded.body 42 + or comments.reply_to != excluded.reply_to 43 + then ? 44 + else comments.edited 45 + end`, 46 + c.Did, 47 + c.Collection, 48 + c.Rkey, 49 + c.Subject, 50 + c.ReplyTo, 51 + c.Body, 52 + c.PullSubmissionId, 53 + c.Created.Format(time.RFC3339), 54 + time.Now().Format(time.RFC3339), 55 + ) 56 + if err != nil { 57 + return err 58 + } 59 + 60 + c.Id, err = result.LastInsertId() 61 + if err != nil { 62 + return err 63 + } 64 + 65 + if err := putReferences(tx, c.AtUri(), c.References); err != nil { 66 + return fmt.Errorf("put reference_links: %w", err) 67 + } 68 + 69 + return nil 70 + } 71 + 72 + func DeleteComments(e Execer, filters ...orm.Filter) error { 73 + var conditions []string 74 + var args []any 75 + for _, filter := range filters { 76 + conditions = append(conditions, filter.Condition()) 77 + args = append(args, filter.Arg()...) 78 + } 79 + 80 + whereClause := "" 81 + if conditions != nil { 82 + whereClause = " where " + strings.Join(conditions, " and ") 83 + } 84 + 85 + query := fmt.Sprintf(`update comments set body = "", deleted = strftime('%%Y-%%m-%%dT%%H:%%M:%%SZ', 'now') %s`, whereClause) 86 + 87 + _, err := e.Exec(query, args...) 88 + return err 89 + } 90 + 91 + func GetComments(e Execer, filters ...orm.Filter) ([]models.Comment, error) { 92 + commentMap := make(map[string]*models.Comment) 93 + 94 + var conditions []string 95 + var args []any 96 + for _, filter := range filters { 97 + conditions = append(conditions, filter.Condition()) 98 + args = append(args, filter.Arg()...) 99 + } 100 + 101 + whereClause := "" 102 + if conditions != nil { 103 + whereClause = " where " + strings.Join(conditions, " and ") 104 + } 105 + 106 + query := fmt.Sprintf(` 107 + select 108 + id, 109 + did, 110 + collection, 111 + rkey, 112 + subject_at, 113 + reply_to, 114 + body, 115 + pull_submission_id, 116 + created, 117 + edited, 118 + deleted 119 + from 120 + comments 121 + %s 122 + `, whereClause) 123 + 124 + rows, err := e.Query(query, args...) 125 + if err != nil { 126 + return nil, err 127 + } 128 + 129 + for rows.Next() { 130 + var comment models.Comment 131 + var created string 132 + var edited, deleted, replyTo sql.Null[string] 133 + err := rows.Scan( 134 + &comment.Id, 135 + &comment.Did, 136 + &comment.Collection, 137 + &comment.Rkey, 138 + &comment.Subject, 139 + &replyTo, 140 + &comment.Body, 141 + &comment.PullSubmissionId, 142 + &created, 143 + &edited, 144 + &deleted, 145 + ) 146 + if err != nil { 147 + return nil, err 148 + } 149 + 150 + if t, err := time.Parse(time.RFC3339, created); err == nil { 151 + comment.Created = t 152 + } 153 + 154 + if edited.Valid { 155 + if t, err := time.Parse(time.RFC3339, edited.V); err == nil { 156 + comment.Edited = &t 157 + } 158 + } 159 + 160 + if deleted.Valid { 161 + if t, err := time.Parse(time.RFC3339, deleted.V); err == nil { 162 + comment.Deleted = &t 163 + } 164 + } 165 + 166 + if replyTo.Valid { 167 + rt := syntax.ATURI(replyTo.V) 168 + comment.ReplyTo = &rt 169 + } 170 + 171 + atUri := comment.AtUri().String() 172 + commentMap[atUri] = &comment 173 + } 174 + 175 + if err := rows.Err(); err != nil { 176 + return nil, err 177 + } 178 + defer rows.Close() 179 + 180 + // collect references from each comments 181 + commentAts := slices.Collect(maps.Keys(commentMap)) 182 + allReferencs, err := GetReferencesAll(e, orm.FilterIn("from_at", commentAts)) 183 + if err != nil { 184 + return nil, fmt.Errorf("failed to query reference_links: %w", err) 185 + } 186 + for commentAt, references := range allReferencs { 187 + if comment, ok := commentMap[commentAt.String()]; ok { 188 + comment.References = references 189 + } 190 + } 191 + 192 + var comments []models.Comment 193 + for _, c := range commentMap { 194 + comments = append(comments, *c) 195 + } 196 + 197 + sort.Slice(comments, func(i, j int) bool { 198 + return comments[i].Created.Before(comments[j].Created) 199 + }) 200 + 201 + return comments, nil 202 + }
+81
appview/db/db.go
··· 1173 return err 1174 }) 1175 1176 return &DB{ 1177 db, 1178 logger,
··· 1173 return err 1174 }) 1175 1176 + orm.RunMigration(conn, logger, "add-comments-table", func(tx *sql.Tx) error { 1177 + _, err := tx.Exec(` 1178 + drop table if exists comments; 1179 + 1180 + create table comments ( 1181 + -- identifiers 1182 + id integer primary key autoincrement, 1183 + did text not null, 1184 + collection text not null default 'sh.tangled.comment', 1185 + rkey text not null, 1186 + at_uri text generated always as ('at://' || did || '/' || collection || '/' || rkey) stored, 1187 + 1188 + -- at identifiers 1189 + subject_at text not null, 1190 + reply_to text, -- at_uri of parent comment 1191 + 1192 + pull_submission_id integer, -- dirty fix until we atprotate the pull-rounds 1193 + 1194 + -- content 1195 + body text not null, 1196 + created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 1197 + edited text, 1198 + deleted text, 1199 + 1200 + -- constraints 1201 + unique(did, collection, rkey) 1202 + ); 1203 + 1204 + insert into comments ( 1205 + did, 1206 + collection, 1207 + rkey, 1208 + subject_at, 1209 + reply_to, 1210 + body, 1211 + created, 1212 + edited, 1213 + deleted 1214 + ) 1215 + select 1216 + did, 1217 + 'sh.tangled.repo.issue.comment', 1218 + rkey, 1219 + issue_at, 1220 + reply_to, 1221 + body, 1222 + created, 1223 + edited, 1224 + deleted 1225 + from issue_comments 1226 + where rkey is not null; 1227 + 1228 + insert into comments ( 1229 + did, 1230 + collection, 1231 + rkey, 1232 + subject_at, 1233 + pull_submission_id, 1234 + body, 1235 + created 1236 + ) 1237 + select 1238 + c.owner_did, 1239 + 'sh.tangled.repo.pull.comment', 1240 + substr( 1241 + substr(c.comment_at, 6 + instr(substr(c.comment_at, 6), '/')), -- nsid/rkey 1242 + instr( 1243 + substr(c.comment_at, 6 + instr(substr(c.comment_at, 6), '/')), -- nsid/rkey 1244 + '/' 1245 + ) + 1 1246 + ), -- rkey 1247 + p.at_uri, 1248 + c.submission_id, 1249 + c.body, 1250 + c.created 1251 + from pull_comments c 1252 + join pulls p on c.repo_at = p.repo_at and c.pull_id = p.pull_id; 1253 + `) 1254 + return err 1255 + }) 1256 + 1257 return &DB{ 1258 db, 1259 logger,
+6 -121
appview/db/pulls.go
··· 391 return nil, err 392 } 393 394 - // Get comments for all submissions using GetPullComments 395 submissionIds := slices.Collect(maps.Keys(submissionMap)) 396 - comments, err := GetPullComments(e, orm.FilterIn("submission_id", submissionIds)) 397 if err != nil { 398 return nil, fmt.Errorf("failed to get pull comments: %w", err) 399 } 400 for _, comment := range comments { 401 - if submission, ok := submissionMap[comment.SubmissionId]; ok { 402 - submission.Comments = append(submission.Comments, comment) 403 } 404 } 405 ··· 419 return m, nil 420 } 421 422 - func GetPullComments(e Execer, filters ...orm.Filter) ([]models.PullComment, error) { 423 - var conditions []string 424 - var args []any 425 - for _, filter := range filters { 426 - conditions = append(conditions, filter.Condition()) 427 - args = append(args, filter.Arg()...) 428 - } 429 - 430 - whereClause := "" 431 - if conditions != nil { 432 - whereClause = " where " + strings.Join(conditions, " and ") 433 - } 434 - 435 - query := fmt.Sprintf(` 436 - select 437 - id, 438 - pull_id, 439 - submission_id, 440 - repo_at, 441 - owner_did, 442 - comment_at, 443 - body, 444 - created 445 - from 446 - pull_comments 447 - %s 448 - order by 449 - created asc 450 - `, whereClause) 451 - 452 - rows, err := e.Query(query, args...) 453 - if err != nil { 454 - return nil, err 455 - } 456 - defer rows.Close() 457 - 458 - commentMap := make(map[string]*models.PullComment) 459 - for rows.Next() { 460 - var comment models.PullComment 461 - var createdAt string 462 - err := rows.Scan( 463 - &comment.ID, 464 - &comment.PullId, 465 - &comment.SubmissionId, 466 - &comment.RepoAt, 467 - &comment.OwnerDid, 468 - &comment.CommentAt, 469 - &comment.Body, 470 - &createdAt, 471 - ) 472 - if err != nil { 473 - return nil, err 474 - } 475 - 476 - if t, err := time.Parse(time.RFC3339, createdAt); err == nil { 477 - comment.Created = t 478 - } 479 - 480 - atUri := comment.AtUri().String() 481 - commentMap[atUri] = &comment 482 - } 483 - 484 - if err := rows.Err(); err != nil { 485 - return nil, err 486 - } 487 - 488 - // collect references for each comments 489 - commentAts := slices.Collect(maps.Keys(commentMap)) 490 - allReferencs, err := GetReferencesAll(e, orm.FilterIn("from_at", commentAts)) 491 - if err != nil { 492 - return nil, fmt.Errorf("failed to query reference_links: %w", err) 493 - } 494 - for commentAt, references := range allReferencs { 495 - if comment, ok := commentMap[commentAt.String()]; ok { 496 - comment.References = references 497 - } 498 - } 499 - 500 - var comments []models.PullComment 501 - for _, c := range commentMap { 502 - comments = append(comments, *c) 503 - } 504 - 505 - sort.Slice(comments, func(i, j int) bool { 506 - return comments[i].Created.Before(comments[j].Created) 507 - }) 508 - 509 - return comments, nil 510 - } 511 - 512 // timeframe here is directly passed into the sql query filter, and any 513 // timeframe in the past should be negative; e.g.: "-3 months" 514 func GetPullsByOwnerDid(e Execer, did, timeframe string) ([]models.Pull, error) { ··· 585 return pulls, nil 586 } 587 588 - func NewPullComment(tx *sql.Tx, comment *models.PullComment) (int64, error) { 589 - query := `insert into pull_comments (owner_did, repo_at, submission_id, comment_at, pull_id, body) values (?, ?, ?, ?, ?, ?)` 590 - res, err := tx.Exec( 591 - query, 592 - comment.OwnerDid, 593 - comment.RepoAt, 594 - comment.SubmissionId, 595 - comment.CommentAt, 596 - comment.PullId, 597 - comment.Body, 598 - ) 599 - if err != nil { 600 - return 0, err 601 - } 602 - 603 - i, err := res.LastInsertId() 604 - if err != nil { 605 - return 0, err 606 - } 607 - 608 - if err := putReferences(tx, comment.AtUri(), comment.References); err != nil { 609 - return 0, fmt.Errorf("put reference_links: %w", err) 610 - } 611 - 612 - return i, nil 613 - } 614 - 615 func SetPullState(e Execer, repoAt syntax.ATURI, pullId int, pullState models.PullState) error { 616 _, err := e.Exec( 617 `update pulls set state = ? where repo_at = ? and pull_id = ? and (state <> ? or state <> ?)`,
··· 391 return nil, err 392 } 393 394 + // Get comments for all submissions using GetComments 395 submissionIds := slices.Collect(maps.Keys(submissionMap)) 396 + comments, err := GetComments(e, orm.FilterIn("pull_submission_id", submissionIds)) 397 if err != nil { 398 return nil, fmt.Errorf("failed to get pull comments: %w", err) 399 } 400 for _, comment := range comments { 401 + if comment.PullSubmissionId != nil { 402 + if submission, ok := submissionMap[*comment.PullSubmissionId]; ok { 403 + submission.Comments = append(submission.Comments, comment) 404 + } 405 } 406 } 407 ··· 421 return m, nil 422 } 423 424 // timeframe here is directly passed into the sql query filter, and any 425 // timeframe in the past should be negative; e.g.: "-3 months" 426 func GetPullsByOwnerDid(e Execer, did, timeframe string) ([]models.Pull, error) { ··· 497 return pulls, nil 498 } 499 500 func SetPullState(e Execer, repoAt syntax.ATURI, pullId int, pullState models.PullState) error { 501 _, err := e.Exec( 502 `update pulls set state = ? where repo_at = ? and pull_id = ? and (state <> ? or state <> ?)`,
+7 -8
appview/db/reference.go
··· 124 values %s 125 ) 126 select 127 - p.owner_did, p.rkey, 128 - c.comment_at 129 from input inp 130 join repos r 131 on r.did = inp.owner_did ··· 133 join pulls p 134 on p.repo_at = r.at_uri 135 and p.pull_id = inp.pull_id 136 - left join pull_comments c 137 on inp.comment_id is not null 138 - and c.repo_at = r.at_uri and c.pull_id = p.pull_id 139 and c.id = inp.comment_id 140 `, 141 strings.Join(vals, ","), ··· 293 return nil, fmt.Errorf("get pull backlinks: %w", err) 294 } 295 backlinks = append(backlinks, ls...) 296 - ls, err = getPullCommentBacklinks(e, backlinksMap[tangled.RepoPullCommentNSID]) 297 if err != nil { 298 return nil, fmt.Errorf("get pull_comment backlinks: %w", err) 299 } ··· 428 if len(aturis) == 0 { 429 return nil, nil 430 } 431 - filter := orm.FilterIn("c.comment_at", aturis) 432 rows, err := e.Query( 433 fmt.Sprintf( 434 `select r.did, r.name, p.pull_id, c.id, p.title, p.state 435 from repos r 436 join pulls p 437 on r.at_uri = p.repo_at 438 - join pull_comments c 439 - on r.at_uri = c.repo_at and p.pull_id = c.pull_id 440 where %s`, 441 filter.Condition(), 442 ),
··· 124 values %s 125 ) 126 select 127 + p.owner_did, p.rkey, c.at_uri 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 comments c 136 on inp.comment_id is not null 137 + and c.subject_at = ('at://' || p.owner_did || '/' || 'sh.tangled.repo.pull' || '/' || p.rkey) 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.CommentNSID]) 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 := orm.FilterIn("c.at_uri", 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 comments c 438 + on ('at://' || p.owner_did || '/' || 'sh.tangled.repo.pull' || '/' || p.rkey) = c.subject_at 439 where %s`, 440 filter.Condition(), 441 ),
+71
appview/ingester.go
··· 79 err = i.ingestString(e) 80 case tangled.RepoIssueNSID: 81 err = i.ingestIssue(ctx, e) 82 case tangled.RepoIssueCommentNSID: 83 err = i.ingestIssueComment(e) 84 case tangled.LabelDefinitionNSID: ··· 928 return nil 929 } 930 931 func (i *Ingester) ingestLabelDefinition(e *jmodels.Event) error { 932 did := e.Did 933 rkey := e.Commit.RKey
··· 79 err = i.ingestString(e) 80 case tangled.RepoIssueNSID: 81 err = i.ingestIssue(ctx, e) 82 + case tangled.CommentNSID: 83 + err = i.ingestComment(e) 84 case tangled.RepoIssueCommentNSID: 85 err = i.ingestIssueComment(e) 86 case tangled.LabelDefinitionNSID: ··· 930 return nil 931 } 932 933 + func (i *Ingester) ingestComment(e *jmodels.Event) error { 934 + did := e.Did 935 + rkey := e.Commit.RKey 936 + 937 + var err error 938 + 939 + l := i.Logger.With("handler", "ingestComment", "nsid", e.Commit.Collection, "did", did, "rkey", rkey) 940 + l.Info("ingesting record") 941 + 942 + ddb, ok := i.Db.Execer.(*db.DB) 943 + if !ok { 944 + return fmt.Errorf("failed to index issue comment record, invalid db cast") 945 + } 946 + 947 + switch e.Commit.Operation { 948 + case jmodels.CommitOperationCreate, jmodels.CommitOperationUpdate: 949 + raw := json.RawMessage(e.Commit.Record) 950 + record := tangled.Comment{} 951 + err = json.Unmarshal(raw, &record) 952 + if err != nil { 953 + return fmt.Errorf("invalid record: %w", err) 954 + } 955 + 956 + comment, err := models.CommentFromRecord(syntax.DID(did), syntax.RecordKey(rkey), record) 957 + if err != nil { 958 + return fmt.Errorf("failed to parse comment from record: %w", err) 959 + } 960 + 961 + // TODO: ingest pull comments 962 + // we aren't ingesting pull comments yet because pull itself isn't fully atprotated. 963 + // so we cannot know which round this comment is pointing to 964 + if comment.Subject.Collection().String() == tangled.RepoPullNSID { 965 + l.Info("skip ingesting pull comments") 966 + return nil 967 + } 968 + 969 + if err := comment.Validate(); err != nil { 970 + return fmt.Errorf("failed to validate comment: %w", err) 971 + } 972 + 973 + tx, err := ddb.Begin() 974 + if err != nil { 975 + return fmt.Errorf("failed to start transaction: %w", err) 976 + } 977 + defer tx.Rollback() 978 + 979 + err = db.PutComment(tx, comment) 980 + if err != nil { 981 + return fmt.Errorf("failed to create comment: %w", err) 982 + } 983 + 984 + return tx.Commit() 985 + 986 + case jmodels.CommitOperationDelete: 987 + if err := db.DeleteComments( 988 + ddb, 989 + orm.FilterEq("did", did), 990 + orm.FilterEq("collection", e.Commit.Collection), 991 + orm.FilterEq("rkey", rkey), 992 + ); err != nil { 993 + return fmt.Errorf("failed to delete comment record: %w", err) 994 + } 995 + 996 + return nil 997 + } 998 + 999 + return nil 1000 + } 1001 + 1002 func (i *Ingester) ingestLabelDefinition(e *jmodels.Event) error { 1003 did := e.Did 1004 rkey := e.Commit.RKey
+138
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 + "github.com/whyrusleeping/cbor-gen" 10 + "tangled.org/core/api/tangled" 11 + ) 12 + 13 + type Comment struct { 14 + Id int64 15 + Did syntax.DID 16 + Collection syntax.NSID 17 + Rkey string 18 + Subject syntax.ATURI 19 + ReplyTo *syntax.ATURI 20 + Body string 21 + Created time.Time 22 + Edited *time.Time 23 + Deleted *time.Time 24 + Mentions []syntax.DID 25 + References []syntax.ATURI 26 + PullSubmissionId *int 27 + } 28 + 29 + func (c *Comment) AtUri() syntax.ATURI { 30 + return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", c.Did, c.Collection, c.Rkey)) 31 + } 32 + 33 + func (c *Comment) AsRecord() typegen.CBORMarshaler { 34 + mentions := make([]string, len(c.Mentions)) 35 + for i, did := range c.Mentions { 36 + mentions[i] = string(did) 37 + } 38 + references := make([]string, len(c.References)) 39 + for i, uri := range c.References { 40 + references[i] = string(uri) 41 + } 42 + var replyTo *string 43 + if c.ReplyTo != nil { 44 + replyToStr := c.ReplyTo.String() 45 + replyTo = &replyToStr 46 + } 47 + switch c.Collection { 48 + case tangled.RepoIssueCommentNSID: 49 + return &tangled.RepoIssueComment{ 50 + Issue: c.Subject.String(), 51 + Body: c.Body, 52 + CreatedAt: c.Created.Format(time.RFC3339), 53 + ReplyTo: replyTo, 54 + Mentions: mentions, 55 + References: references, 56 + } 57 + case tangled.RepoPullCommentNSID: 58 + return &tangled.RepoPullComment{ 59 + Pull: c.Subject.String(), 60 + Body: c.Body, 61 + CreatedAt: c.Created.Format(time.RFC3339), 62 + Mentions: mentions, 63 + References: references, 64 + } 65 + default: // default to CommentNSID 66 + return &tangled.Comment{ 67 + Subject: c.Subject.String(), 68 + Body: c.Body, 69 + CreatedAt: c.Created.Format(time.RFC3339), 70 + ReplyTo: replyTo, 71 + Mentions: mentions, 72 + References: references, 73 + } 74 + } 75 + } 76 + 77 + func (c *Comment) IsTopLevel() bool { 78 + return c.ReplyTo == nil 79 + } 80 + 81 + func (c *Comment) IsReply() bool { 82 + return c.ReplyTo != nil 83 + } 84 + 85 + func (c *Comment) Validate() error { 86 + // TODO: sanitize the body and then trim space 87 + if sb := strings.TrimSpace(c.Body); sb == "" { 88 + return fmt.Errorf("body is empty after HTML sanitization") 89 + } 90 + 91 + // if it's for PR, PullSubmissionId should not be nil 92 + if c.Subject.Collection().String() == tangled.RepoPullNSID { 93 + if c.PullSubmissionId == nil { 94 + return fmt.Errorf("PullSubmissionId should not be nil") 95 + } 96 + } 97 + return nil 98 + } 99 + 100 + func CommentFromRecord(did syntax.DID, rkey syntax.RecordKey, record tangled.Comment) (*Comment, error) { 101 + created, err := time.Parse(time.RFC3339, record.CreatedAt) 102 + if err != nil { 103 + created = time.Now() 104 + } 105 + 106 + if _, err = syntax.ParseATURI(record.Subject); err != nil { 107 + return nil, err 108 + } 109 + 110 + i := record 111 + mentions := make([]syntax.DID, len(record.Mentions)) 112 + for i, did := range record.Mentions { 113 + mentions[i] = syntax.DID(did) 114 + } 115 + references := make([]syntax.ATURI, len(record.References)) 116 + for i, uri := range i.References { 117 + references[i] = syntax.ATURI(uri) 118 + } 119 + var replyTo *syntax.ATURI 120 + if record.ReplyTo != nil { 121 + replyToAtUri := syntax.ATURI(*record.ReplyTo) 122 + replyTo = &replyToAtUri 123 + } 124 + 125 + comment := Comment{ 126 + Did: did, 127 + Collection: tangled.CommentNSID, 128 + Rkey: rkey.String(), 129 + Body: record.Body, 130 + Subject: syntax.ATURI(record.Subject), 131 + ReplyTo: replyTo, 132 + Created: created, 133 + Mentions: mentions, 134 + References: references, 135 + } 136 + 137 + return &comment, nil 138 + }
+2 -28
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 *Pull) TotalComments() int { 175 total := 0 176 for _, s := range p.Submissions { ··· 279 addParticipant(s.PullAt.Authority().String()) 280 281 for _, c := range s.Comments { 282 - addParticipant(c.OwnerDid) 283 } 284 285 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) TotalComments() int { 149 total := 0 150 for _, s := range p.Submissions { ··· 253 addParticipant(s.PullAt.Authority().String()) 254 255 for _, c := range s.Comments { 256 + addParticipant(c.Did.String()) 257 } 258 259 return participants
+11 -6
appview/notify/db/db.go
··· 260 ) 261 } 262 263 - func (n *databaseNotifier) NewPullComment(ctx context.Context, comment *models.PullComment, mentions []syntax.DID) { 264 - pull, err := db.GetPull(n.db, 265 - syntax.ATURI(comment.RepoAt), 266 - comment.PullId, 267 ) 268 if err != nil { 269 log.Printf("NewPullComment: failed to get pulls: %v", err) 270 return 271 } 272 273 - repo, err := db.GetRepo(n.db, orm.FilterEq("at_uri", comment.RepoAt)) 274 if err != nil { 275 log.Printf("NewPullComment: failed to get repos: %v", err) 276 return ··· 288 recipients.Remove(m) 289 } 290 291 - actorDid := syntax.DID(comment.OwnerDid) 292 eventType := models.NotificationTypePullCommented 293 entityType := "pull" 294 entityId := pull.AtUri().String()
··· 260 ) 261 } 262 263 + func (n *databaseNotifier) NewPullComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { 264 + pulls, err := db.GetPulls(n.db, 265 + orm.FilterEq("owner_did", comment.Subject.Authority()), 266 + orm.FilterEq("rkey", comment.Subject.RecordKey()), 267 ) 268 if err != nil { 269 log.Printf("NewPullComment: failed to get pulls: %v", err) 270 return 271 } 272 + if len(pulls) == 0 { 273 + log.Printf("NewPullComment: no pull found for %s", comment.Subject) 274 + return 275 + } 276 + pull := pulls[0] 277 278 + repo, err := db.GetRepo(n.db, orm.FilterEq("at_uri", pull.RepoAt)) 279 if err != nil { 280 log.Printf("NewPullComment: failed to get repos: %v", err) 281 return ··· 293 recipients.Remove(m) 294 } 295 296 + actorDid := comment.Did 297 eventType := models.NotificationTypePullCommented 298 entityType := "pull" 299 entityId := pull.AtUri().String()
+1 -1
appview/notify/merged_notifier.go
··· 81 m.fanout("NewPull", ctx, pull) 82 } 83 84 - func (m *mergedNotifier) NewPullComment(ctx context.Context, comment *models.PullComment, mentions []syntax.DID) { 85 m.fanout("NewPullComment", ctx, comment, mentions) 86 } 87
··· 81 m.fanout("NewPull", ctx, pull) 82 } 83 84 + func (m *mergedNotifier) NewPullComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) { 85 m.fanout("NewPullComment", ctx, comment, mentions) 86 } 87
+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 })
+4 -4
appview/pages/templates/repo/pulls/pull.html
··· 505 {{ end }} 506 507 {{ define "submissionComment" }} 508 - <div id="comment-{{.ID}}" class="flex gap-2 -ml-4 py-4 w-full mx-auto"> 509 <!-- left column: profile picture --> 510 <div class="flex-shrink-0"> 511 <img 512 - src="{{ tinyAvatar .OwnerDid }}" 513 alt="" 514 class="rounded-full size-8 mr-1 border-2 border-gray-100 dark:border-gray-900" 515 /> ··· 518 <div class="flex-1 min-w-0"> 519 <!-- Row 1: Author and timestamp --> 520 <div class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1"> 521 - <span>{{ resolve .OwnerDid }}</span> 522 <span class="before:content-['路']"></span> 523 - <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="#comment-{{.ID}}"> 524 {{ template "repo/fragments/time" .Created }} 525 </a> 526 </div>
··· 505 {{ end }} 506 507 {{ define "submissionComment" }} 508 + <div id="comment-{{.Id}}" class="flex gap-2 -ml-4 py-4 w-full mx-auto"> 509 <!-- left column: profile picture --> 510 <div class="flex-shrink-0"> 511 <img 512 + src="{{ tinyAvatar .Did.String }}" 513 alt="" 514 class="rounded-full size-8 mr-1 border-2 border-gray-100 dark:border-gray-900" 515 /> ··· 518 <div class="flex-1 min-w-0"> 519 <!-- Row 1: Author and timestamp --> 520 <div class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1"> 521 + <span>{{ resolve .Did.String }}</span> 522 <span class="before:content-['路']"></span> 523 + <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="#comment-{{.Id}}"> 524 {{ template "repo/fragments/time" .Created }} 525 </a> 526 </div>
+1 -1
appview/pulls/opengraph.go
··· 277 } 278 279 // Get comment count from database 280 - comments, err := db.GetPullComments(s.db, orm.FilterEq("pull_id", pull.ID)) 281 if err != nil { 282 log.Printf("failed to get pull comments: %v", err) 283 }
··· 277 } 278 279 // Get comment count from database 280 + comments, err := db.GetComments(s.db, orm.FilterEq("subject_at", pull.AtUri())) 281 if err != nil { 282 log.Printf("failed to get pull comments: %v", err) 283 }
+26 -24
appview/pulls/pulls.go
··· 722 } 723 defer tx.Rollback() 724 725 - createdAt := time.Now().Format(time.RFC3339) 726 727 client, err := s.oauth.AuthorizedClient(r) 728 if err != nil { ··· 730 s.pages.Notice(w, "pull-comment", "Failed to create comment.") 731 return 732 } 733 - atResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 734 - Collection: tangled.RepoPullCommentNSID, 735 - Repo: user.Active.Did, 736 - Rkey: tid.TID(), 737 Record: &lexutil.LexiconTypeDecoder{ 738 - Val: &tangled.RepoPullComment{ 739 - Pull: pull.AtUri().String(), 740 - Body: body, 741 - CreatedAt: createdAt, 742 - }, 743 }, 744 }) 745 if err != nil { ··· 748 return 749 } 750 751 - comment := &models.PullComment{ 752 - OwnerDid: user.Active.Did, 753 - RepoAt: f.RepoAt().String(), 754 - PullId: pull.PullId, 755 - Body: body, 756 - CommentAt: atResp.Uri, 757 - SubmissionId: pull.Submissions[roundNumber].ID, 758 - Mentions: mentions, 759 - References: references, 760 - } 761 - 762 // Create the pull comment in the database with the commentAt field 763 - commentId, err := db.NewPullComment(tx, comment) 764 if err != nil { 765 log.Println("failed to create pull comment", err) 766 s.pages.Notice(w, "pull-comment", "Failed to create comment.") ··· 774 return 775 } 776 777 - s.notifier.NewPullComment(r.Context(), comment, mentions) 778 779 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f) 780 - s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d#comment-%d", ownerSlashRepo, pull.PullId, commentId)) 781 return 782 } 783 }
··· 722 } 723 defer tx.Rollback() 724 725 + comment := models.Comment{ 726 + Did: syntax.DID(user.Active.Did), 727 + Collection: tangled.CommentNSID, 728 + Rkey: tid.TID(), 729 + Subject: pull.AtUri(), 730 + ReplyTo: nil, 731 + Body: body, 732 + Created: time.Now(), 733 + Mentions: mentions, 734 + References: references, 735 + PullSubmissionId: &pull.Submissions[roundNumber].ID, 736 + } 737 + if err = comment.Validate(); err != nil { 738 + log.Println("failed to validate comment", err) 739 + s.pages.Notice(w, "pull-comment", "Failed to create comment.") 740 + return 741 + } 742 743 client, err := s.oauth.AuthorizedClient(r) 744 if err != nil { ··· 746 s.pages.Notice(w, "pull-comment", "Failed to create comment.") 747 return 748 } 749 + 750 + _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 751 + Collection: comment.Collection.String(), 752 + Repo: comment.Did.String(), 753 + Rkey: comment.Rkey, 754 Record: &lexutil.LexiconTypeDecoder{ 755 + Val: comment.AsRecord(), 756 }, 757 }) 758 if err != nil { ··· 761 return 762 } 763 764 // Create the pull comment in the database with the commentAt field 765 + err = db.PutComment(tx, &comment) 766 if err != nil { 767 log.Println("failed to create pull comment", err) 768 s.pages.Notice(w, "pull-comment", "Failed to create comment.") ··· 776 return 777 } 778 779 + s.notifier.NewPullComment(r.Context(), &comment, mentions) 780 781 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f) 782 + s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d#comment-%d", ownerSlashRepo, pull.PullId, comment.Id)) 783 return 784 } 785 }
+1
appview/state/state.go
··· 118 tangled.StringNSID, 119 tangled.RepoIssueNSID, 120 tangled.RepoIssueCommentNSID, 121 tangled.LabelDefinitionNSID, 122 tangled.LabelOpNSID, 123 },
··· 118 tangled.StringNSID, 119 tangled.RepoIssueNSID, 120 tangled.RepoIssueCommentNSID, 121 + tangled.CommentNSID, 122 tangled.LabelDefinitionNSID, 123 tangled.LabelOpNSID, 124 },

History

8 rounds 4 comments
sign up or login to add to the discussion
1 commit
expand
appview: replace PullComment to Comment
2/3 timeout, 1/3 success
expand
merge conflicts detected
expand
  • appview/notify/db/db.go:260
  • appview/notify/merged_notifier.go:81
  • appview/notify/notifier.go:22
  • appview/pulls/opengraph.go:277
expand 0 comments
1 commit
expand
appview: replace PullComment to Comment
2/3 failed, 1/3 success
expand
expand 0 comments
1 commit
expand
appview: replace PullComment to Comment
3/3 success
expand
expand 0 comments
1 commit
expand
appview: replace PullComment to Comment
3/3 success
expand
expand 1 comment

here, the sort order is reversed, it should be .Before.

1 commit
expand
appview: replace PullComment to Comment
1/3 failed, 2/3 success
expand
expand 0 comments
1 commit
expand
appview: replace PullComment to Comment
1/3 failed, 2/3 success
expand
expand 0 comments
1 commit
expand
appview: replace PullComment to Comment
3/3 success
expand
expand 3 comments

here will this delete all comments made on tangled thus far? don't think this is the right approach if so.

I should've been more precise about my expression. Existing comments won't be dropped as they are stored in issue_comments and pull_comments table (I'm just dropping legacy table with same name there), but yeah this PR itself doesn't include the backward compatibility layer yet so old comments won't be rendered.

Honestly this PR hasn't been updated since you two suggested to just ingest old records. I'll push more commits on top of this stack.

As a side note: I'd suggest to not publish the new sh.tangled.comment lexicon yet.

This unified comment record can be used for PR review comments and it might make us to modify the schema again. I think it would be safest to publish this when PR redesign discussion is finished.

1 commit
expand
appview: replace PullComment to Comment
3/3 success
expand
expand 0 comments