Signed-off-by: Seongmin Lee git@boltless.me
+93
-380
Diff
round #0
+6
-185
appview/db/issues.go
+6
-185
appview/db/issues.go
···
99
99
}
100
100
101
101
func GetIssuesPaginated(e Execer, page pagination.Page, filters ...filter) ([]models.Issue, error) {
102
-
issueMap := make(map[string]*models.Issue) // at-uri -> issue
102
+
issueMap := make(map[syntax.ATURI]*models.Issue) // at-uri -> issue
103
103
104
104
var conditions []string
105
105
var args []any
···
195
195
}
196
196
}
197
197
198
-
atUri := issue.AtUri().String()
199
-
issueMap[atUri] = &issue
198
+
issueMap[issue.AtUri()] = &issue
200
199
}
201
200
202
201
// collect reverse repos
···
228
227
// collect comments
229
228
issueAts := slices.Collect(maps.Keys(issueMap))
230
229
231
-
comments, err := GetIssueComments(e, FilterIn("issue_at", issueAts))
230
+
comments, err := GetComments(e, FilterIn("subject_at", issueAts))
232
231
if err != nil {
233
232
return nil, fmt.Errorf("failed to query comments: %w", err)
234
233
}
235
234
for i := range comments {
236
-
issueAt := comments[i].IssueAt
235
+
issueAt := comments[i].Subject
237
236
if issue, ok := issueMap[issueAt]; ok {
238
237
issue.Comments = append(issue.Comments, comments[i])
239
238
}
···
245
244
return nil, fmt.Errorf("failed to query labels: %w", err)
246
245
}
247
246
for issueAt, labels := range allLabels {
248
-
if issue, ok := issueMap[issueAt.String()]; ok {
247
+
if issue, ok := issueMap[issueAt]; ok {
249
248
issue.Labels = labels
250
249
}
251
250
}
···
256
255
return nil, fmt.Errorf("failed to query reference_links: %w", err)
257
256
}
258
257
for issueAt, references := range allReferencs {
259
-
if issue, ok := issueMap[issueAt.String()]; ok {
258
+
if issue, ok := issueMap[issueAt]; ok {
260
259
issue.References = references
261
260
}
262
261
}
···
350
349
return ids, nil
351
350
}
352
351
353
-
func AddIssueComment(tx *sql.Tx, c models.IssueComment) (int64, error) {
354
-
result, err := tx.Exec(
355
-
`insert into issue_comments (
356
-
did,
357
-
rkey,
358
-
issue_at,
359
-
body,
360
-
reply_to,
361
-
created,
362
-
edited
363
-
)
364
-
values (?, ?, ?, ?, ?, ?, null)
365
-
on conflict(did, rkey) do update set
366
-
issue_at = excluded.issue_at,
367
-
body = excluded.body,
368
-
edited = case
369
-
when
370
-
issue_comments.issue_at != excluded.issue_at
371
-
or issue_comments.body != excluded.body
372
-
or issue_comments.reply_to != excluded.reply_to
373
-
then ?
374
-
else issue_comments.edited
375
-
end`,
376
-
c.Did,
377
-
c.Rkey,
378
-
c.IssueAt,
379
-
c.Body,
380
-
c.ReplyTo,
381
-
c.Created.Format(time.RFC3339),
382
-
time.Now().Format(time.RFC3339),
383
-
)
384
-
if err != nil {
385
-
return 0, err
386
-
}
387
-
388
-
id, err := result.LastInsertId()
389
-
if err != nil {
390
-
return 0, err
391
-
}
392
-
393
-
if err := putReferences(tx, c.AtUri(), c.References); err != nil {
394
-
return 0, fmt.Errorf("put reference_links: %w", err)
395
-
}
396
-
397
-
return id, nil
398
-
}
399
-
400
-
func DeleteIssueComments(e Execer, filters ...filter) error {
401
-
var conditions []string
402
-
var args []any
403
-
for _, filter := range filters {
404
-
conditions = append(conditions, filter.Condition())
405
-
args = append(args, filter.Arg()...)
406
-
}
407
-
408
-
whereClause := ""
409
-
if conditions != nil {
410
-
whereClause = " where " + strings.Join(conditions, " and ")
411
-
}
412
-
413
-
query := fmt.Sprintf(`update issue_comments set body = "", deleted = strftime('%%Y-%%m-%%dT%%H:%%M:%%SZ', 'now') %s`, whereClause)
414
-
415
-
_, err := e.Exec(query, args...)
416
-
return err
417
-
}
418
-
419
-
func GetIssueComments(e Execer, filters ...filter) ([]models.IssueComment, error) {
420
-
commentMap := make(map[string]*models.IssueComment)
421
-
422
-
var conditions []string
423
-
var args []any
424
-
for _, filter := range filters {
425
-
conditions = append(conditions, filter.Condition())
426
-
args = append(args, filter.Arg()...)
427
-
}
428
-
429
-
whereClause := ""
430
-
if conditions != nil {
431
-
whereClause = " where " + strings.Join(conditions, " and ")
432
-
}
433
-
434
-
query := fmt.Sprintf(`
435
-
select
436
-
id,
437
-
did,
438
-
rkey,
439
-
issue_at,
440
-
reply_to,
441
-
body,
442
-
created,
443
-
edited,
444
-
deleted
445
-
from
446
-
issue_comments
447
-
%s
448
-
`, whereClause)
449
-
450
-
rows, err := e.Query(query, args...)
451
-
if err != nil {
452
-
return nil, err
453
-
}
454
-
455
-
for rows.Next() {
456
-
var comment models.IssueComment
457
-
var created string
458
-
var rkey, edited, deleted, replyTo sql.Null[string]
459
-
err := rows.Scan(
460
-
&comment.Id,
461
-
&comment.Did,
462
-
&rkey,
463
-
&comment.IssueAt,
464
-
&replyTo,
465
-
&comment.Body,
466
-
&created,
467
-
&edited,
468
-
&deleted,
469
-
)
470
-
if err != nil {
471
-
return nil, err
472
-
}
473
-
474
-
// this is a remnant from old times, newer comments always have rkey
475
-
if rkey.Valid {
476
-
comment.Rkey = rkey.V
477
-
}
478
-
479
-
if t, err := time.Parse(time.RFC3339, created); err == nil {
480
-
comment.Created = t
481
-
}
482
-
483
-
if edited.Valid {
484
-
if t, err := time.Parse(time.RFC3339, edited.V); err == nil {
485
-
comment.Edited = &t
486
-
}
487
-
}
488
-
489
-
if deleted.Valid {
490
-
if t, err := time.Parse(time.RFC3339, deleted.V); err == nil {
491
-
comment.Deleted = &t
492
-
}
493
-
}
494
-
495
-
if replyTo.Valid {
496
-
comment.ReplyTo = &replyTo.V
497
-
}
498
-
499
-
atUri := comment.AtUri().String()
500
-
commentMap[atUri] = &comment
501
-
}
502
-
503
-
if err = rows.Err(); err != nil {
504
-
return nil, err
505
-
}
506
-
507
-
// collect references for each comments
508
-
commentAts := slices.Collect(maps.Keys(commentMap))
509
-
allReferencs, err := GetReferencesAll(e, FilterIn("from_at", commentAts))
510
-
if err != nil {
511
-
return nil, fmt.Errorf("failed to query reference_links: %w", err)
512
-
}
513
-
for commentAt, references := range allReferencs {
514
-
if comment, ok := commentMap[commentAt.String()]; ok {
515
-
comment.References = references
516
-
}
517
-
}
518
-
519
-
var comments []models.IssueComment
520
-
for _, c := range commentMap {
521
-
comments = append(comments, *c)
522
-
}
523
-
524
-
sort.Slice(comments, func(i, j int) bool {
525
-
return comments[i].Created.After(comments[j].Created)
526
-
})
527
-
528
-
return comments, nil
529
-
}
530
-
531
352
func DeleteIssues(tx *sql.Tx, did, rkey string) error {
532
353
_, err := tx.Exec(
533
354
`delete from issues
+13
-24
appview/db/reference.go
+13
-24
appview/db/reference.go
···
10
10
"tangled.org/core/appview/models"
11
11
)
12
12
13
-
// ValidateReferenceLinks resolves refLinks to Issue/PR/IssueComment/PullComment ATURIs.
13
+
// ValidateReferenceLinks resolves refLinks to Issue/PR/Comment ATURIs.
14
14
// It will ignore missing refLinks.
15
15
func ValidateReferenceLinks(e Execer, refLinks []models.ReferenceLink) ([]syntax.ATURI, error) {
16
16
var (
···
52
52
values %s
53
53
)
54
54
select
55
-
i.did, i.rkey,
56
-
c.did, c.rkey
55
+
i.at_uri, c.at_uri
57
56
from input inp
58
57
join repos r
59
58
on r.did = inp.owner_did
···
61
60
join issues i
62
61
on i.repo_at = r.at_uri
63
62
and i.issue_id = inp.issue_id
64
-
left join issue_comments c
63
+
left join comments c
65
64
on inp.comment_id is not null
66
-
and c.issue_at = i.at_uri
65
+
and c.subject_at = i.at_uri
67
66
and c.id = inp.comment_id
68
67
`,
69
68
strings.Join(vals, ","),
···
78
77
79
78
for rows.Next() {
80
79
// Scan rows
81
-
var issueOwner, issueRkey string
82
-
var commentOwner, commentRkey sql.NullString
80
+
var issueUri string
81
+
var commentUri sql.NullString
83
82
var uri syntax.ATURI
84
-
if err := rows.Scan(&issueOwner, &issueRkey, &commentOwner, &commentRkey); err != nil {
83
+
if err := rows.Scan(&issueUri, &commentUri); err != nil {
85
84
return nil, err
86
85
}
87
-
if commentOwner.Valid && commentRkey.Valid {
88
-
uri = syntax.ATURI(fmt.Sprintf(
89
-
"at://%s/%s/%s",
90
-
commentOwner.String,
91
-
tangled.RepoIssueCommentNSID,
92
-
commentRkey.String,
93
-
))
86
+
if commentUri.Valid {
87
+
uri = syntax.ATURI(commentUri.String)
94
88
} else {
95
-
uri = syntax.ATURI(fmt.Sprintf(
96
-
"at://%s/%s/%s",
97
-
issueOwner,
98
-
tangled.RepoIssueNSID,
99
-
issueRkey,
100
-
))
89
+
uri = syntax.ATURI(issueUri)
101
90
}
102
91
uris = append(uris, uri)
103
92
}
···
281
270
return nil, fmt.Errorf("get issue backlinks: %w", err)
282
271
}
283
272
backlinks = append(backlinks, ls...)
284
-
ls, err = getIssueCommentBacklinks(e, backlinksMap[tangled.RepoIssueCommentNSID])
273
+
ls, err = getIssueCommentBacklinks(e, backlinksMap[tangled.CommentNSID])
285
274
if err != nil {
286
275
return nil, fmt.Errorf("get issue_comment backlinks: %w", err)
287
276
}
···
350
339
rows, err := e.Query(
351
340
fmt.Sprintf(
352
341
`select r.did, r.name, i.issue_id, c.id, i.title, i.open
353
-
from issue_comments c
342
+
from comments c
354
343
join issues i
355
-
on i.at_uri = c.issue_at
344
+
on i.at_uri = c.subject_at
356
345
join repos r
357
346
on r.at_uri = i.repo_at
358
347
where %s`,
+19
-11
appview/ingester.go
+19
-11
appview/ingester.go
···
78
78
err = i.ingestString(e)
79
79
case tangled.RepoIssueNSID:
80
80
err = i.ingestIssue(ctx, e)
81
-
case tangled.RepoIssueCommentNSID:
82
-
err = i.ingestIssueComment(e)
81
+
case tangled.CommentNSID:
82
+
err = i.ingestComment(e)
83
83
case tangled.LabelDefinitionNSID:
84
84
err = i.ingestLabelDefinition(e)
85
85
case tangled.LabelOpNSID:
···
867
867
return nil
868
868
}
869
869
870
-
func (i *Ingester) ingestIssueComment(e *jmodels.Event) error {
870
+
func (i *Ingester) ingestComment(e *jmodels.Event) error {
871
871
did := e.Did
872
872
rkey := e.Commit.RKey
873
873
874
874
var err error
875
875
876
-
l := i.Logger.With("handler", "ingestIssueComment", "nsid", e.Commit.Collection, "did", did, "rkey", rkey)
876
+
l := i.Logger.With("handler", "ingestComment", "nsid", e.Commit.Collection, "did", did, "rkey", rkey)
877
877
l.Info("ingesting record")
878
878
879
879
ddb, ok := i.Db.Execer.(*db.DB)
···
884
884
switch e.Commit.Operation {
885
885
case jmodels.CommitOperationCreate, jmodels.CommitOperationUpdate:
886
886
raw := json.RawMessage(e.Commit.Record)
887
-
record := tangled.RepoIssueComment{}
887
+
record := tangled.Comment{}
888
888
err = json.Unmarshal(raw, &record)
889
889
if err != nil {
890
890
return fmt.Errorf("invalid record: %w", err)
891
891
}
892
892
893
-
comment, err := models.IssueCommentFromRecord(did, rkey, record)
893
+
comment, err := models.CommentFromRecord(did, rkey, record)
894
894
if err != nil {
895
895
return fmt.Errorf("failed to parse comment from record: %w", err)
896
896
}
897
897
898
-
if err := i.Validator.ValidateIssueComment(comment); err != nil {
898
+
// TODO: ingest pull comments
899
+
// we aren't ingesting pull comments yet because pull itself isn't fully atprotated.
900
+
// so we cannot know which round this comment is pointing to
901
+
if comment.Subject.Collection().String() == tangled.RepoPullNSID {
902
+
l.Info("skip ingesting pull comments")
903
+
return nil
904
+
}
905
+
906
+
if err := comment.Validate(); err != nil {
899
907
return fmt.Errorf("failed to validate comment: %w", err)
900
908
}
901
909
···
905
913
}
906
914
defer tx.Rollback()
907
915
908
-
_, err = db.AddIssueComment(tx, *comment)
916
+
err = db.PutComment(tx, comment)
909
917
if err != nil {
910
-
return fmt.Errorf("failed to create issue comment: %w", err)
918
+
return fmt.Errorf("failed to create comment: %w", err)
911
919
}
912
920
913
921
return tx.Commit()
914
922
915
923
case jmodels.CommitOperationDelete:
916
-
if err := db.DeleteIssueComments(
924
+
if err := db.DeleteComments(
917
925
ddb,
918
926
db.FilterEq("did", did),
919
927
db.FilterEq("rkey", rkey),
920
928
); err != nil {
921
-
return fmt.Errorf("failed to delete issue comment record: %w", err)
929
+
return fmt.Errorf("failed to delete comment record: %w", err)
922
930
}
923
931
924
932
return nil
+30
-28
appview/issues/issues.go
+30
-28
appview/issues/issues.go
···
402
402
403
403
body := r.FormValue("body")
404
404
if body == "" {
405
-
rp.pages.Notice(w, "issue", "Body is required")
405
+
rp.pages.Notice(w, "issue-comment", "Body is required")
406
406
return
407
407
}
408
408
409
-
replyToUri := r.FormValue("reply-to")
410
-
var replyTo *string
411
-
if replyToUri != "" {
412
-
replyTo = &replyToUri
409
+
var replyTo *syntax.ATURI
410
+
replyToRaw := r.FormValue("reply-to")
411
+
if replyToRaw != "" {
412
+
aturi, err := syntax.ParseATURI(r.FormValue("reply-to"))
413
+
if err != nil {
414
+
rp.pages.Notice(w, "issue-comment", "reply-to should be valid AT-URI")
415
+
return
416
+
}
417
+
replyTo = &aturi
413
418
}
414
419
415
420
mentions, references := rp.refResolver.Resolve(r.Context(), body)
416
421
417
-
comment := models.IssueComment{
418
-
Did: user.Did,
422
+
comment := models.Comment{
423
+
Did: syntax.DID(user.Did),
419
424
Rkey: tid.TID(),
420
-
IssueAt: issue.AtUri().String(),
425
+
Subject: issue.AtUri(),
421
426
ReplyTo: replyTo,
422
427
Body: body,
423
428
Created: time.Now(),
424
429
Mentions: mentions,
425
430
References: references,
426
431
}
427
-
if err = rp.validator.ValidateIssueComment(&comment); err != nil {
432
+
if err = comment.Validate(); err != nil {
428
433
l.Error("failed to validate comment", "err", err)
429
434
rp.pages.Notice(w, "issue-comment", "Failed to create comment.")
430
435
return
···
440
445
441
446
// create a record first
442
447
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
443
-
Collection: tangled.RepoIssueCommentNSID,
444
-
Repo: comment.Did,
448
+
Collection: tangled.CommentNSID,
449
+
Repo: user.Did,
445
450
Rkey: comment.Rkey,
446
451
Record: &lexutil.LexiconTypeDecoder{
447
452
Val: &record,
···
467
472
}
468
473
defer tx.Rollback()
469
474
470
-
commentId, err := db.AddIssueComment(tx, comment)
475
+
err = db.PutComment(tx, &comment)
471
476
if err != nil {
472
477
l.Error("failed to create comment", "err", err)
473
478
rp.pages.Notice(w, "issue-comment", "Failed to create comment.")
···
483
488
// reset atUri to make rollback a no-op
484
489
atUri = ""
485
490
486
-
// notify about the new comment
487
-
comment.Id = commentId
488
-
489
491
rp.notifier.NewIssueComment(r.Context(), &comment, mentions)
490
492
491
493
ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
492
-
rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d#comment-%d", ownerSlashRepo, issue.IssueId, commentId))
494
+
rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d#comment-%d", ownerSlashRepo, issue.IssueId, comment.Id))
493
495
}
494
496
495
497
func (rp *Issues) IssueComment(w http.ResponseWriter, r *http.Request) {
···
504
506
}
505
507
506
508
commentId := chi.URLParam(r, "commentId")
507
-
comments, err := db.GetIssueComments(
509
+
comments, err := db.GetComments(
508
510
rp.db,
509
511
db.FilterEq("id", commentId),
510
512
)
···
540
542
}
541
543
542
544
commentId := chi.URLParam(r, "commentId")
543
-
comments, err := db.GetIssueComments(
545
+
comments, err := db.GetComments(
544
546
rp.db,
545
547
db.FilterEq("id", commentId),
546
548
)
···
556
558
}
557
559
comment := comments[0]
558
560
559
-
if comment.Did != user.Did {
561
+
if comment.Did.String() != user.Did {
560
562
l.Error("unauthorized comment edit", "expectedDid", comment.Did, "gotDid", user.Did)
561
563
http.Error(w, "you are not the author of this comment", http.StatusUnauthorized)
562
564
return
···
596
598
}
597
599
defer tx.Rollback()
598
600
599
-
_, err = db.AddIssueComment(tx, newComment)
601
+
err = db.PutComment(tx, &newComment)
600
602
if err != nil {
601
603
l.Error("failed to perferom update-description query", "err", err)
602
604
rp.pages.Notice(w, "repo-notice", "Failed to update description, try again later.")
···
607
609
// rkey is optional, it was introduced later
608
610
if newComment.Rkey != "" {
609
611
// update the record on pds
610
-
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoIssueCommentNSID, user.Did, comment.Rkey)
612
+
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.CommentNSID, user.Did, comment.Rkey)
611
613
if err != nil {
612
614
l.Error("failed to get record", "err", err, "did", newComment.Did, "rkey", newComment.Rkey)
613
615
rp.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "Failed to update description, no record found on PDS.")
···
615
617
}
616
618
617
619
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
618
-
Collection: tangled.RepoIssueCommentNSID,
620
+
Collection: tangled.CommentNSID,
619
621
Repo: user.Did,
620
622
Rkey: newComment.Rkey,
621
623
SwapRecord: ex.Cid,
···
650
652
}
651
653
652
654
commentId := chi.URLParam(r, "commentId")
653
-
comments, err := db.GetIssueComments(
655
+
comments, err := db.GetComments(
654
656
rp.db,
655
657
db.FilterEq("id", commentId),
656
658
)
···
686
688
}
687
689
688
690
commentId := chi.URLParam(r, "commentId")
689
-
comments, err := db.GetIssueComments(
691
+
comments, err := db.GetComments(
690
692
rp.db,
691
693
db.FilterEq("id", commentId),
692
694
)
···
722
724
}
723
725
724
726
commentId := chi.URLParam(r, "commentId")
725
-
comments, err := db.GetIssueComments(
727
+
comments, err := db.GetComments(
726
728
rp.db,
727
729
db.FilterEq("id", commentId),
728
730
)
···
738
740
}
739
741
comment := comments[0]
740
742
741
-
if comment.Did != user.Did {
743
+
if comment.Did.String() != user.Did {
742
744
l.Error("unauthorized action", "expectedDid", comment.Did, "gotDid", user.Did)
743
745
http.Error(w, "you are not the author of this comment", http.StatusUnauthorized)
744
746
return
···
751
753
752
754
// optimistic deletion
753
755
deleted := time.Now()
754
-
err = db.DeleteIssueComments(rp.db, db.FilterEq("id", comment.Id))
756
+
err = db.DeleteComments(rp.db, db.FilterEq("id", comment.Id))
755
757
if err != nil {
756
758
l.Error("failed to delete comment", "err", err)
757
759
rp.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "failed to delete comment")
···
767
769
return
768
770
}
769
771
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
770
-
Collection: tangled.RepoIssueCommentNSID,
772
+
Collection: tangled.CommentNSID,
771
773
Repo: user.Did,
772
774
Rkey: comment.Rkey,
773
775
})
+8
-89
appview/models/issue.go
+8
-89
appview/models/issue.go
···
26
26
27
27
// optionally, populate this when querying for reverse mappings
28
28
// like comment counts, parent repo etc.
29
-
Comments []IssueComment
29
+
Comments []Comment
30
30
Labels LabelState
31
31
Repo *Repo
32
32
}
···
62
62
}
63
63
64
64
type CommentListItem struct {
65
-
Self *IssueComment
66
-
Replies []*IssueComment
65
+
Self *Comment
66
+
Replies []*Comment
67
67
}
68
68
69
69
func (it *CommentListItem) Participants() []syntax.DID {
···
88
88
89
89
func (i *Issue) CommentList() []CommentListItem {
90
90
// Create a map to quickly find comments by their aturi
91
-
toplevel := make(map[string]*CommentListItem)
92
-
var replies []*IssueComment
91
+
toplevel := make(map[syntax.ATURI]*CommentListItem)
92
+
var replies []*Comment
93
93
94
94
// collect top level comments into the map
95
95
for _, comment := range i.Comments {
96
96
if comment.IsTopLevel() {
97
-
toplevel[comment.AtUri().String()] = &CommentListItem{
97
+
toplevel[comment.AtUri()] = &CommentListItem{
98
98
Self: &comment,
99
99
}
100
100
} else {
···
115
115
}
116
116
117
117
// sort everything
118
-
sortFunc := func(a, b *IssueComment) bool {
118
+
sortFunc := func(a, b *Comment) bool {
119
119
return a.Created.Before(b.Created)
120
120
}
121
121
sort.Slice(listing, func(i, j int) bool {
···
144
144
addParticipant(i.Did)
145
145
146
146
for _, c := range i.Comments {
147
-
addParticipant(c.Did)
147
+
addParticipant(c.Did.String())
148
148
}
149
149
150
150
return participants
···
171
171
Open: true, // new issues are open by default
172
172
}
173
173
}
174
-
175
-
type IssueComment struct {
176
-
Id int64
177
-
Did string
178
-
Rkey string
179
-
IssueAt string
180
-
ReplyTo *string
181
-
Body string
182
-
Created time.Time
183
-
Edited *time.Time
184
-
Deleted *time.Time
185
-
Mentions []syntax.DID
186
-
References []syntax.ATURI
187
-
}
188
-
189
-
func (i *IssueComment) AtUri() syntax.ATURI {
190
-
return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", i.Did, tangled.RepoIssueCommentNSID, i.Rkey))
191
-
}
192
-
193
-
func (i *IssueComment) AsRecord() tangled.RepoIssueComment {
194
-
mentions := make([]string, len(i.Mentions))
195
-
for i, did := range i.Mentions {
196
-
mentions[i] = string(did)
197
-
}
198
-
references := make([]string, len(i.References))
199
-
for i, uri := range i.References {
200
-
references[i] = string(uri)
201
-
}
202
-
return tangled.RepoIssueComment{
203
-
Body: i.Body,
204
-
Issue: i.IssueAt,
205
-
CreatedAt: i.Created.Format(time.RFC3339),
206
-
ReplyTo: i.ReplyTo,
207
-
Mentions: mentions,
208
-
References: references,
209
-
}
210
-
}
211
-
212
-
func (i *IssueComment) IsTopLevel() bool {
213
-
return i.ReplyTo == nil
214
-
}
215
-
216
-
func (i *IssueComment) IsReply() bool {
217
-
return i.ReplyTo != nil
218
-
}
219
-
220
-
func IssueCommentFromRecord(did, rkey string, record tangled.RepoIssueComment) (*IssueComment, error) {
221
-
created, err := time.Parse(time.RFC3339, record.CreatedAt)
222
-
if err != nil {
223
-
created = time.Now()
224
-
}
225
-
226
-
ownerDid := did
227
-
228
-
if _, err = syntax.ParseATURI(record.Issue); err != nil {
229
-
return nil, err
230
-
}
231
-
232
-
i := record
233
-
mentions := make([]syntax.DID, len(record.Mentions))
234
-
for i, did := range record.Mentions {
235
-
mentions[i] = syntax.DID(did)
236
-
}
237
-
references := make([]syntax.ATURI, len(record.References))
238
-
for i, uri := range i.References {
239
-
references[i] = syntax.ATURI(uri)
240
-
}
241
-
242
-
comment := IssueComment{
243
-
Did: ownerDid,
244
-
Rkey: rkey,
245
-
Body: record.Body,
246
-
IssueAt: record.Issue,
247
-
ReplyTo: record.ReplyTo,
248
-
Created: created,
249
-
Mentions: mentions,
250
-
References: references,
251
-
}
252
-
253
-
return &comment, nil
254
-
}
+4
-4
appview/notify/db/db.go
+4
-4
appview/notify/db/db.go
···
118
118
)
119
119
}
120
120
121
-
func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) {
122
-
issues, err := db.GetIssues(n.db, db.FilterEq("at_uri", comment.IssueAt))
121
+
func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) {
122
+
issues, err := db.GetIssues(n.db, db.FilterEq("at_uri", comment.Subject))
123
123
if err != nil {
124
124
log.Printf("NewIssueComment: failed to get issues: %v", err)
125
125
return
126
126
}
127
127
if len(issues) == 0 {
128
-
log.Printf("NewIssueComment: no issue found for %s", comment.IssueAt)
128
+
log.Printf("NewIssueComment: no issue found for %s", comment.Subject)
129
129
return
130
130
}
131
131
issue := issues[0]
···
140
140
141
141
// find the parent thread, and add all DIDs from here to the recipient list
142
142
for _, t := range allThreads {
143
-
if t.Self.AtUri().String() == parentAtUri {
143
+
if t.Self.AtUri() == parentAtUri {
144
144
recipients = append(recipients, t.Participants()...)
145
145
}
146
146
}
+1
-1
appview/notify/merged_notifier.go
+1
-1
appview/notify/merged_notifier.go
···
58
58
m.fanout("NewIssue", ctx, issue, mentions)
59
59
}
60
60
61
-
func (m *mergedNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) {
61
+
func (m *mergedNotifier) NewIssueComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) {
62
62
m.fanout("NewIssueComment", ctx, comment, mentions)
63
63
}
64
64
+2
-2
appview/notify/notifier.go
+2
-2
appview/notify/notifier.go
···
14
14
DeleteStar(ctx context.Context, star *models.Star)
15
15
16
16
NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID)
17
-
NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID)
17
+
NewIssueComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID)
18
18
NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue)
19
19
DeleteIssue(ctx context.Context, issue *models.Issue)
20
20
···
43
43
func (m *BaseNotifier) DeleteStar(ctx context.Context, star *models.Star) {}
44
44
45
45
func (m *BaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) {}
46
-
func (m *BaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) {
46
+
func (m *BaseNotifier) NewIssueComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) {
47
47
}
48
48
func (m *BaseNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) {}
49
49
func (m *BaseNotifier) DeleteIssue(ctx context.Context, issue *models.Issue) {}
+3
-3
appview/notify/posthog/notifier.go
+3
-3
appview/notify/posthog/notifier.go
···
179
179
}
180
180
}
181
181
182
-
func (n *posthogNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) {
182
+
func (n *posthogNotifier) NewIssueComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) {
183
183
err := n.client.Enqueue(posthog.Capture{
184
-
DistinctId: comment.Did,
184
+
DistinctId: comment.Did.String(),
185
185
Event: "new_issue_comment",
186
186
Properties: posthog.Properties{
187
-
"issue_at": comment.IssueAt,
187
+
"issue_at": comment.Subject,
188
188
"mentions": mentions,
189
189
},
190
190
})
+4
-4
appview/pages/pages.go
+4
-4
appview/pages/pages.go
···
989
989
LoggedInUser *oauth.User
990
990
RepoInfo repoinfo.RepoInfo
991
991
Issue *models.Issue
992
-
Comment *models.IssueComment
992
+
Comment *models.Comment
993
993
}
994
994
995
995
func (p *Pages) EditIssueCommentFragment(w io.Writer, params EditIssueCommentParams) error {
···
1000
1000
LoggedInUser *oauth.User
1001
1001
RepoInfo repoinfo.RepoInfo
1002
1002
Issue *models.Issue
1003
-
Comment *models.IssueComment
1003
+
Comment *models.Comment
1004
1004
}
1005
1005
1006
1006
func (p *Pages) ReplyIssueCommentPlaceholderFragment(w io.Writer, params ReplyIssueCommentPlaceholderParams) error {
···
1011
1011
LoggedInUser *oauth.User
1012
1012
RepoInfo repoinfo.RepoInfo
1013
1013
Issue *models.Issue
1014
-
Comment *models.IssueComment
1014
+
Comment *models.Comment
1015
1015
}
1016
1016
1017
1017
func (p *Pages) ReplyIssueCommentFragment(w io.Writer, params ReplyIssueCommentParams) error {
···
1022
1022
LoggedInUser *oauth.User
1023
1023
RepoInfo repoinfo.RepoInfo
1024
1024
Issue *models.Issue
1025
-
Comment *models.IssueComment
1025
+
Comment *models.Comment
1026
1026
}
1027
1027
1028
1028
func (p *Pages) IssueCommentBodyFragment(w io.Writer, params IssueCommentBodyParams) error {
+2
-2
appview/pages/templates/repo/issues/fragments/issueCommentHeader.html
+2
-2
appview/pages/templates/repo/issues/fragments/issueCommentHeader.html
···
1
1
{{ define "repo/issues/fragments/issueCommentHeader" }}
2
2
<div class="flex flex-wrap items-center gap-2 text-sm text-gray-500 dark:text-gray-400 ">
3
-
{{ template "user/fragments/picHandleLink" .Comment.Did }}
3
+
{{ template "user/fragments/picHandleLink" .Comment.Did.String }}
4
4
{{ template "hats" $ }}
5
5
{{ template "timestamp" . }}
6
-
{{ $isCommentOwner := and .LoggedInUser (eq .LoggedInUser.Did .Comment.Did) }}
6
+
{{ $isCommentOwner := and .LoggedInUser (eq .LoggedInUser.Did .Comment.Did.String) }}
7
7
{{ if and $isCommentOwner (not .Comment.Deleted) }}
8
8
{{ template "editIssueComment" . }}
9
9
{{ template "deleteIssueComment" . }}
+1
-1
appview/state/state.go
+1
-1
appview/state/state.go
-26
appview/validator/issue.go
-26
appview/validator/issue.go
···
4
4
"fmt"
5
5
"strings"
6
6
7
-
"tangled.org/core/appview/db"
8
7
"tangled.org/core/appview/models"
9
8
)
10
9
11
-
func (v *Validator) ValidateIssueComment(comment *models.IssueComment) error {
12
-
// if comments have parents, only ingest ones that are 1 level deep
13
-
if comment.ReplyTo != nil {
14
-
parents, err := db.GetIssueComments(v.db, db.FilterEq("at_uri", *comment.ReplyTo))
15
-
if err != nil {
16
-
return fmt.Errorf("failed to fetch parent comment: %w", err)
17
-
}
18
-
if len(parents) != 1 {
19
-
return fmt.Errorf("incorrect number of parent comments returned: %d", len(parents))
20
-
}
21
-
22
-
// depth check
23
-
parent := parents[0]
24
-
if parent.ReplyTo != nil {
25
-
return fmt.Errorf("incorrect depth, this comment is replying at depth >1")
26
-
}
27
-
}
28
-
29
-
if sb := strings.TrimSpace(v.sanitizer.SanitizeDefault(comment.Body)); sb == "" {
30
-
return fmt.Errorf("body is empty after HTML sanitization")
31
-
}
32
-
33
-
return nil
34
-
}
35
-
36
10
func (v *Validator) ValidateIssue(issue *models.Issue) error {
37
11
if issue.Title == "" {
38
12
return fmt.Errorf("issue title is empty")
History
8 rounds
1 comment
boltless.me
submitted
#7
1 commit
expand
collapse
appview: replace
IssueComment to Comment
Signed-off-by: Seongmin Lee <git@boltless.me>
2/3 failed, 1/3 success
expand
collapse
merge conflicts detected
expand
collapse
expand
collapse
- appview/db/reference.go:293
- 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
boltless.me
submitted
#6
1 commit
expand
collapse
appview: replace
IssueComment to Comment
Signed-off-by: Seongmin Lee <git@boltless.me>
2/3 failed, 1/3 success
expand
collapse
expand 0 comments
boltless.me
submitted
#5
1 commit
expand
collapse
appview: replace
IssueComment to Comment
Signed-off-by: Seongmin Lee <git@boltless.me>
3/3 success
expand
collapse
expand 0 comments
boltless.me
submitted
#4
1 commit
expand
collapse
appview: replace
IssueComment to Comment
Signed-off-by: Seongmin Lee <git@boltless.me>
3/3 success
expand
collapse
expand 0 comments
boltless.me
submitted
#3
1 commit
expand
collapse
appview: replace
IssueComment to Comment
Signed-off-by: Seongmin Lee <git@boltless.me>
1/3 failed, 2/3 success
expand
collapse
expand 0 comments
boltless.me
submitted
#2
1 commit
expand
collapse
appview: replace
IssueComment to Comment
Signed-off-by: Seongmin Lee <git@boltless.me>
1/3 failed, 2/3 success
expand
collapse
expand 0 comments
boltless.me
submitted
#1
1 commit
expand
collapse
appview: replace
IssueComment to Comment
Signed-off-by: Seongmin Lee <git@boltless.me>
3/3 success
expand
collapse
expand 1 comment
boltless.me
submitted
#0
1 commit
expand
collapse
appview: replace
IssueComment to Comment
Signed-off-by: Seongmin Lee <git@boltless.me>
imo: we should still continue to ingest
sh.tangled.issue.comment(and pull comments), but just try to convert to the regular lexicon. this would only be used for backfill purposes, newer appviews should ideally not create the old NSID anymore.the way i see it, the new lexicon is just an alias for the old ones, so the ingesters should be backwards compatible.
i will do a deeper dive into the code itself shortly.