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
+557 -199
Diff #7
+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
··· 1181 return err 1182 }) 1183 1184 return &DB{ 1185 db, 1186 logger,
··· 1181 return err 1182 }) 1183 1184 + orm.RunMigration(conn, logger, "add-comments-table", func(tx *sql.Tx) error { 1185 + _, err := tx.Exec(` 1186 + drop table if exists comments; 1187 + 1188 + create table comments ( 1189 + -- identifiers 1190 + id integer primary key autoincrement, 1191 + did text not null, 1192 + collection text not null default 'sh.tangled.comment', 1193 + rkey text not null, 1194 + at_uri text generated always as ('at://' || did || '/' || collection || '/' || rkey) stored, 1195 + 1196 + -- at identifiers 1197 + subject_at text not null, 1198 + reply_to text, -- at_uri of parent comment 1199 + 1200 + pull_submission_id integer, -- dirty fix until we atprotate the pull-rounds 1201 + 1202 + -- content 1203 + body text not null, 1204 + created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 1205 + edited text, 1206 + deleted text, 1207 + 1208 + -- constraints 1209 + unique(did, collection, rkey) 1210 + ); 1211 + 1212 + insert into comments ( 1213 + did, 1214 + collection, 1215 + rkey, 1216 + subject_at, 1217 + reply_to, 1218 + body, 1219 + created, 1220 + edited, 1221 + deleted 1222 + ) 1223 + select 1224 + did, 1225 + 'sh.tangled.repo.issue.comment', 1226 + rkey, 1227 + issue_at, 1228 + reply_to, 1229 + body, 1230 + created, 1231 + edited, 1232 + deleted 1233 + from issue_comments 1234 + where rkey is not null; 1235 + 1236 + insert into comments ( 1237 + did, 1238 + collection, 1239 + rkey, 1240 + subject_at, 1241 + pull_submission_id, 1242 + body, 1243 + created 1244 + ) 1245 + select 1246 + c.owner_did, 1247 + 'sh.tangled.repo.pull.comment', 1248 + substr( 1249 + substr(c.comment_at, 6 + instr(substr(c.comment_at, 6), '/')), -- nsid/rkey 1250 + instr( 1251 + substr(c.comment_at, 6 + instr(substr(c.comment_at, 6), '/')), -- nsid/rkey 1252 + '/' 1253 + ) + 1 1254 + ), -- rkey 1255 + p.at_uri, 1256 + c.submission_id, 1257 + c.body, 1258 + c.created 1259 + from pull_comments c 1260 + join pulls p on c.repo_at = p.repo_at and c.pull_id = p.pull_id; 1261 + `) 1262 + return err 1263 + }) 1264 + 1265 return &DB{ 1266 db, 1267 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: ··· 934 return nil 935 } 936 937 func (i *Ingester) ingestLabelDefinition(e *jmodels.Event) error { 938 did := e.Did 939 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: ··· 936 return nil 937 } 938 939 + func (i *Ingester) ingestComment(e *jmodels.Event) error { 940 + did := e.Did 941 + rkey := e.Commit.RKey 942 + 943 + var err error 944 + 945 + l := i.Logger.With("handler", "ingestComment", "nsid", e.Commit.Collection, "did", did, "rkey", rkey) 946 + l.Info("ingesting record") 947 + 948 + ddb, ok := i.Db.Execer.(*db.DB) 949 + if !ok { 950 + return fmt.Errorf("failed to index issue comment record, invalid db cast") 951 + } 952 + 953 + switch e.Commit.Operation { 954 + case jmodels.CommitOperationCreate, jmodels.CommitOperationUpdate: 955 + raw := json.RawMessage(e.Commit.Record) 956 + record := tangled.Comment{} 957 + err = json.Unmarshal(raw, &record) 958 + if err != nil { 959 + return fmt.Errorf("invalid record: %w", err) 960 + } 961 + 962 + comment, err := models.CommentFromRecord(syntax.DID(did), syntax.RecordKey(rkey), record) 963 + if err != nil { 964 + return fmt.Errorf("failed to parse comment from record: %w", err) 965 + } 966 + 967 + // TODO: ingest pull comments 968 + // we aren't ingesting pull comments yet because pull itself isn't fully atprotated. 969 + // so we cannot know which round this comment is pointing to 970 + if comment.Subject.Collection().String() == tangled.RepoPullNSID { 971 + l.Info("skip ingesting pull comments") 972 + return nil 973 + } 974 + 975 + if err := comment.Validate(); err != nil { 976 + return fmt.Errorf("failed to validate comment: %w", err) 977 + } 978 + 979 + tx, err := ddb.Begin() 980 + if err != nil { 981 + return fmt.Errorf("failed to start transaction: %w", err) 982 + } 983 + defer tx.Rollback() 984 + 985 + err = db.PutComment(tx, comment) 986 + if err != nil { 987 + return fmt.Errorf("failed to create comment: %w", err) 988 + } 989 + 990 + return tx.Commit() 991 + 992 + case jmodels.CommitOperationDelete: 993 + if err := db.DeleteComments( 994 + ddb, 995 + orm.FilterEq("did", did), 996 + orm.FilterEq("collection", e.Commit.Collection), 997 + orm.FilterEq("rkey", rkey), 998 + ); err != nil { 999 + return fmt.Errorf("failed to delete comment record: %w", err) 1000 + } 1001 + 1002 + return nil 1003 + } 1004 + 1005 + return nil 1006 + } 1007 + 1008 func (i *Ingester) ingestLabelDefinition(e *jmodels.Event) error { 1009 did := e.Did 1010 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 })
+5 -4
appview/pages/templates/repo/pulls/pull.html
··· 561 {{ end }} 562 563 {{ define "submissionComment" }} 564 - <div id="comment-{{.ID}}" class="flex gap-2 -ml-4 py-4 w-full mx-auto"> 565 <!-- left column: profile picture --> 566 <div class="flex-shrink-0 h-fit relative"> 567 - {{ template "user/fragments/picLink" (list .OwnerDid "size-8") }} 568 </div> 569 <!-- right column: name and body in two rows --> 570 <div class="flex-1 min-w-0"> 571 <!-- Row 1: Author and timestamp --> 572 <div class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1"> 573 - {{ $handle := resolve .OwnerDid }} 574 <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="/{{ $handle }}">{{ $handle }}</a> 575 <span class="before:content-['路']"></span> 576 - <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="#comment-{{.ID}}"> 577 {{ template "repo/fragments/shortTime" .Created }} 578 </a> 579 </div>
··· 561 {{ end }} 562 563 {{ define "submissionComment" }} 564 + <div id="comment-{{.Id}}" class="flex gap-2 -ml-4 py-4 w-full mx-auto"> 565 <!-- left column: profile picture --> 566 <div class="flex-shrink-0 h-fit relative"> 567 + {{ template "user/fragments/picLink" (list .Did.String "size-8") }} 568 </div> 569 <!-- right column: name and body in two rows --> 570 <div class="flex-1 min-w-0"> 571 <!-- Row 1: Author and timestamp --> 572 <div class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1"> 573 + {{ $handle := resolve .Did.String }} 574 <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="/{{ $handle }}">{{ $handle }}</a> 575 <span class="before:content-['路']"></span> 576 + <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="#comment-{{.Id}}"> 577 + {{ template "repo/fragments/time" .Created }} 578 {{ template "repo/fragments/shortTime" .Created }} 579 </a> 580 </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
··· 727 } 728 defer tx.Rollback() 729 730 - createdAt := time.Now().Format(time.RFC3339) 731 732 client, err := s.oauth.AuthorizedClient(r) 733 if err != nil { ··· 735 s.pages.Notice(w, "pull-comment", "Failed to create comment.") 736 return 737 } 738 - atResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 739 - Collection: tangled.RepoPullCommentNSID, 740 - Repo: user.Active.Did, 741 - Rkey: tid.TID(), 742 Record: &lexutil.LexiconTypeDecoder{ 743 - Val: &tangled.RepoPullComment{ 744 - Pull: pull.AtUri().String(), 745 - Body: body, 746 - CreatedAt: createdAt, 747 - }, 748 }, 749 }) 750 if err != nil { ··· 753 return 754 } 755 756 - comment := &models.PullComment{ 757 - OwnerDid: user.Active.Did, 758 - RepoAt: f.RepoAt().String(), 759 - PullId: pull.PullId, 760 - Body: body, 761 - CommentAt: atResp.Uri, 762 - SubmissionId: pull.Submissions[roundNumber].ID, 763 - Mentions: mentions, 764 - References: references, 765 - } 766 - 767 // Create the pull comment in the database with the commentAt field 768 - commentId, err := db.NewPullComment(tx, comment) 769 if err != nil { 770 log.Println("failed to create pull comment", err) 771 s.pages.Notice(w, "pull-comment", "Failed to create comment.") ··· 779 return 780 } 781 782 - s.notifier.NewPullComment(r.Context(), comment, mentions) 783 784 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f) 785 - s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d#comment-%d", ownerSlashRepo, pull.PullId, commentId)) 786 return 787 } 788 }
··· 727 } 728 defer tx.Rollback() 729 730 + comment := models.Comment{ 731 + Did: syntax.DID(user.Active.Did), 732 + Collection: tangled.CommentNSID, 733 + Rkey: tid.TID(), 734 + Subject: pull.AtUri(), 735 + ReplyTo: nil, 736 + Body: body, 737 + Created: time.Now(), 738 + Mentions: mentions, 739 + References: references, 740 + PullSubmissionId: &pull.Submissions[roundNumber].ID, 741 + } 742 + if err = comment.Validate(); err != nil { 743 + log.Println("failed to validate comment", err) 744 + s.pages.Notice(w, "pull-comment", "Failed to create comment.") 745 + return 746 + } 747 748 client, err := s.oauth.AuthorizedClient(r) 749 if err != nil { ··· 751 s.pages.Notice(w, "pull-comment", "Failed to create comment.") 752 return 753 } 754 + 755 + _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 756 + Collection: comment.Collection.String(), 757 + Repo: comment.Did.String(), 758 + Rkey: comment.Rkey, 759 Record: &lexutil.LexiconTypeDecoder{ 760 + Val: comment.AsRecord(), 761 }, 762 }) 763 if err != nil { ··· 766 return 767 } 768 769 // Create the pull comment in the database with the commentAt field 770 + err = db.PutComment(tx, &comment) 771 if err != nil { 772 log.Println("failed to create pull comment", err) 773 s.pages.Notice(w, "pull-comment", "Failed to create comment.") ··· 781 return 782 } 783 784 + s.notifier.NewPullComment(r.Context(), &comment, mentions) 785 786 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f) 787 + s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d#comment-%d", ownerSlashRepo, pull.PullId, comment.Id)) 788 return 789 } 790 }
+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