Signed-off-by: Seongmin Lee git@boltless.me
+6
-186
appview/db/issues.go
+6
-186
appview/db/issues.go
···
100
100
}
101
101
102
102
func GetIssuesPaginated(e Execer, page pagination.Page, filters ...orm.Filter) ([]models.Issue, error) {
103
-
issueMap := make(map[string]*models.Issue) // at-uri -> issue
103
+
issueMap := make(map[syntax.ATURI]*models.Issue) // at-uri -> issue
104
104
105
105
var conditions []string
106
106
var args []any
···
196
196
}
197
197
}
198
198
199
-
atUri := issue.AtUri().String()
200
-
issueMap[atUri] = &issue
199
+
issueMap[issue.AtUri()] = &issue
201
200
}
202
201
203
202
// collect reverse repos
···
229
228
// collect comments
230
229
issueAts := slices.Collect(maps.Keys(issueMap))
231
230
232
-
comments, err := GetIssueComments(e, orm.FilterIn("issue_at", issueAts))
231
+
comments, err := GetComments(e, orm.FilterIn("subject_at", issueAts))
233
232
if err != nil {
234
233
return nil, fmt.Errorf("failed to query comments: %w", err)
235
234
}
236
235
for i := range comments {
237
-
issueAt := comments[i].IssueAt
236
+
issueAt := comments[i].Subject
238
237
if issue, ok := issueMap[issueAt]; ok {
239
238
issue.Comments = append(issue.Comments, comments[i])
240
239
}
···
246
245
return nil, fmt.Errorf("failed to query labels: %w", err)
247
246
}
248
247
for issueAt, labels := range allLabels {
249
-
if issue, ok := issueMap[issueAt.String()]; ok {
248
+
if issue, ok := issueMap[issueAt]; ok {
250
249
issue.Labels = labels
251
250
}
252
251
}
···
257
256
return nil, fmt.Errorf("failed to query reference_links: %w", err)
258
257
}
259
258
for issueAt, references := range allReferencs {
260
-
if issue, ok := issueMap[issueAt.String()]; ok {
259
+
if issue, ok := issueMap[issueAt]; ok {
261
260
issue.References = references
262
261
}
263
262
}
···
295
294
return GetIssuesPaginated(e, pagination.Page{}, filters...)
296
295
}
297
296
298
-
func AddIssueComment(tx *sql.Tx, c models.IssueComment) (int64, error) {
299
-
result, err := tx.Exec(
300
-
`insert into issue_comments (
301
-
did,
302
-
rkey,
303
-
issue_at,
304
-
body,
305
-
reply_to,
306
-
created,
307
-
edited
308
-
)
309
-
values (?, ?, ?, ?, ?, ?, null)
310
-
on conflict(did, rkey) do update set
311
-
issue_at = excluded.issue_at,
312
-
body = excluded.body,
313
-
edited = case
314
-
when
315
-
issue_comments.issue_at != excluded.issue_at
316
-
or issue_comments.body != excluded.body
317
-
or issue_comments.reply_to != excluded.reply_to
318
-
then ?
319
-
else issue_comments.edited
320
-
end`,
321
-
c.Did,
322
-
c.Rkey,
323
-
c.IssueAt,
324
-
c.Body,
325
-
c.ReplyTo,
326
-
c.Created.Format(time.RFC3339),
327
-
time.Now().Format(time.RFC3339),
328
-
)
329
-
if err != nil {
330
-
return 0, err
331
-
}
332
-
333
-
id, err := result.LastInsertId()
334
-
if err != nil {
335
-
return 0, err
336
-
}
337
-
338
-
if err := putReferences(tx, c.AtUri(), c.References); err != nil {
339
-
return 0, fmt.Errorf("put reference_links: %w", err)
340
-
}
341
-
342
-
return id, nil
343
-
}
344
-
345
-
func DeleteIssueComments(e Execer, filters ...orm.Filter) error {
346
-
var conditions []string
347
-
var args []any
348
-
for _, filter := range filters {
349
-
conditions = append(conditions, filter.Condition())
350
-
args = append(args, filter.Arg()...)
351
-
}
352
-
353
-
whereClause := ""
354
-
if conditions != nil {
355
-
whereClause = " where " + strings.Join(conditions, " and ")
356
-
}
357
-
358
-
query := fmt.Sprintf(`update issue_comments set body = "", deleted = strftime('%%Y-%%m-%%dT%%H:%%M:%%SZ', 'now') %s`, whereClause)
359
-
360
-
_, err := e.Exec(query, args...)
361
-
return err
362
-
}
363
-
364
-
func GetIssueComments(e Execer, filters ...orm.Filter) ([]models.IssueComment, error) {
365
-
commentMap := make(map[string]*models.IssueComment)
366
-
367
-
var conditions []string
368
-
var args []any
369
-
for _, filter := range filters {
370
-
conditions = append(conditions, filter.Condition())
371
-
args = append(args, filter.Arg()...)
372
-
}
373
-
374
-
whereClause := ""
375
-
if conditions != nil {
376
-
whereClause = " where " + strings.Join(conditions, " and ")
377
-
}
378
-
379
-
query := fmt.Sprintf(`
380
-
select
381
-
id,
382
-
did,
383
-
rkey,
384
-
issue_at,
385
-
reply_to,
386
-
body,
387
-
created,
388
-
edited,
389
-
deleted
390
-
from
391
-
issue_comments
392
-
%s
393
-
`, whereClause)
394
-
395
-
rows, err := e.Query(query, args...)
396
-
if err != nil {
397
-
return nil, err
398
-
}
399
-
defer rows.Close()
400
-
401
-
for rows.Next() {
402
-
var comment models.IssueComment
403
-
var created string
404
-
var rkey, edited, deleted, replyTo sql.Null[string]
405
-
err := rows.Scan(
406
-
&comment.Id,
407
-
&comment.Did,
408
-
&rkey,
409
-
&comment.IssueAt,
410
-
&replyTo,
411
-
&comment.Body,
412
-
&created,
413
-
&edited,
414
-
&deleted,
415
-
)
416
-
if err != nil {
417
-
return nil, err
418
-
}
419
-
420
-
// this is a remnant from old times, newer comments always have rkey
421
-
if rkey.Valid {
422
-
comment.Rkey = rkey.V
423
-
}
424
-
425
-
if t, err := time.Parse(time.RFC3339, created); err == nil {
426
-
comment.Created = t
427
-
}
428
-
429
-
if edited.Valid {
430
-
if t, err := time.Parse(time.RFC3339, edited.V); err == nil {
431
-
comment.Edited = &t
432
-
}
433
-
}
434
-
435
-
if deleted.Valid {
436
-
if t, err := time.Parse(time.RFC3339, deleted.V); err == nil {
437
-
comment.Deleted = &t
438
-
}
439
-
}
440
-
441
-
if replyTo.Valid {
442
-
comment.ReplyTo = &replyTo.V
443
-
}
444
-
445
-
atUri := comment.AtUri().String()
446
-
commentMap[atUri] = &comment
447
-
}
448
-
449
-
if err = rows.Err(); err != nil {
450
-
return nil, err
451
-
}
452
-
453
-
// collect references for each comments
454
-
commentAts := slices.Collect(maps.Keys(commentMap))
455
-
allReferencs, err := GetReferencesAll(e, orm.FilterIn("from_at", commentAts))
456
-
if err != nil {
457
-
return nil, fmt.Errorf("failed to query reference_links: %w", err)
458
-
}
459
-
for commentAt, references := range allReferencs {
460
-
if comment, ok := commentMap[commentAt.String()]; ok {
461
-
comment.References = references
462
-
}
463
-
}
464
-
465
-
var comments []models.IssueComment
466
-
for _, c := range commentMap {
467
-
comments = append(comments, *c)
468
-
}
469
-
470
-
sort.Slice(comments, func(i, j int) bool {
471
-
return comments[i].Created.After(comments[j].Created)
472
-
})
473
-
474
-
return comments, nil
475
-
}
476
-
477
297
func DeleteIssues(tx *sql.Tx, did, rkey string) error {
478
298
_, err := tx.Exec(
479
299
`delete from issues
+13
-24
appview/db/reference.go
+13
-24
appview/db/reference.go
···
11
11
"tangled.org/core/orm"
12
12
)
13
13
14
-
// ValidateReferenceLinks resolves refLinks to Issue/PR/IssueComment/PullComment ATURIs.
14
+
// ValidateReferenceLinks resolves refLinks to Issue/PR/Comment ATURIs.
15
15
// It will ignore missing refLinks.
16
16
func ValidateReferenceLinks(e Execer, refLinks []models.ReferenceLink) ([]syntax.ATURI, error) {
17
17
var (
···
53
53
values %s
54
54
)
55
55
select
56
-
i.did, i.rkey,
57
-
c.did, c.rkey
56
+
i.at_uri, c.at_uri
58
57
from input inp
59
58
join repos r
60
59
on r.did = inp.owner_did
···
62
61
join issues i
63
62
on i.repo_at = r.at_uri
64
63
and i.issue_id = inp.issue_id
65
-
left join issue_comments c
64
+
left join comments c
66
65
on inp.comment_id is not null
67
-
and c.issue_at = i.at_uri
66
+
and c.subject_at = i.at_uri
68
67
and c.id = inp.comment_id
69
68
`,
70
69
strings.Join(vals, ","),
···
79
78
80
79
for rows.Next() {
81
80
// Scan rows
82
-
var issueOwner, issueRkey string
83
-
var commentOwner, commentRkey sql.NullString
81
+
var issueUri string
82
+
var commentUri sql.NullString
84
83
var uri syntax.ATURI
85
-
if err := rows.Scan(&issueOwner, &issueRkey, &commentOwner, &commentRkey); err != nil {
84
+
if err := rows.Scan(&issueUri, &commentUri); err != nil {
86
85
return nil, err
87
86
}
88
-
if commentOwner.Valid && commentRkey.Valid {
89
-
uri = syntax.ATURI(fmt.Sprintf(
90
-
"at://%s/%s/%s",
91
-
commentOwner.String,
92
-
tangled.RepoIssueCommentNSID,
93
-
commentRkey.String,
94
-
))
87
+
if commentUri.Valid {
88
+
uri = syntax.ATURI(commentUri.String)
95
89
} else {
96
-
uri = syntax.ATURI(fmt.Sprintf(
97
-
"at://%s/%s/%s",
98
-
issueOwner,
99
-
tangled.RepoIssueNSID,
100
-
issueRkey,
101
-
))
90
+
uri = syntax.ATURI(issueUri)
102
91
}
103
92
uris = append(uris, uri)
104
93
}
···
282
271
return nil, fmt.Errorf("get issue backlinks: %w", err)
283
272
}
284
273
backlinks = append(backlinks, ls...)
285
-
ls, err = getIssueCommentBacklinks(e, backlinksMap[tangled.RepoIssueCommentNSID])
274
+
ls, err = getIssueCommentBacklinks(e, backlinksMap[tangled.CommentNSID])
286
275
if err != nil {
287
276
return nil, fmt.Errorf("get issue_comment backlinks: %w", err)
288
277
}
···
351
340
rows, err := e.Query(
352
341
fmt.Sprintf(
353
342
`select r.did, r.name, i.issue_id, c.id, i.title, i.open
354
-
from issue_comments c
343
+
from comments c
355
344
join issues i
356
-
on i.at_uri = c.issue_at
345
+
on i.at_uri = c.subject_at
357
346
join repos r
358
347
on r.at_uri = i.repo_at
359
348
where %s`,
+15
-6
appview/ingester.go
+15
-6
appview/ingester.go
···
891
891
}
892
892
893
893
switch e.Commit.Operation {
894
-
case jmodels.CommitOperationCreate, jmodels.CommitOperationUpdate:
894
+
case jmodels.CommitOperationUpdate:
895
895
raw := json.RawMessage(e.Commit.Record)
896
896
record := tangled.RepoIssueComment{}
897
897
err = json.Unmarshal(raw, &record)
···
899
899
return fmt.Errorf("invalid record: %w", err)
900
900
}
901
901
902
-
comment, err := models.IssueCommentFromRecord(did, rkey, record)
902
+
// convert 'sh.tangled.repo.issue.comment' to 'sh.tangled.comment'
903
+
comment, err := models.CommentFromRecord(syntax.DID(did), syntax.RecordKey(rkey), tangled.Comment{
904
+
Body: record.Body,
905
+
CreatedAt: record.CreatedAt,
906
+
Mentions: record.Mentions,
907
+
References: record.References,
908
+
ReplyTo: record.ReplyTo,
909
+
Subject: record.Issue,
910
+
})
903
911
if err != nil {
904
912
return fmt.Errorf("failed to parse comment from record: %w", err)
905
913
}
906
914
907
-
if err := i.Validator.ValidateIssueComment(comment); err != nil {
915
+
if err := comment.Validate(); err != nil {
908
916
return fmt.Errorf("failed to validate comment: %w", err)
909
917
}
910
918
···
914
922
}
915
923
defer tx.Rollback()
916
924
917
-
_, err = db.AddIssueComment(tx, *comment)
925
+
err = db.PutComment(tx, comment)
918
926
if err != nil {
919
-
return fmt.Errorf("failed to create issue comment: %w", err)
927
+
return fmt.Errorf("failed to create comment: %w", err)
920
928
}
921
929
922
930
return tx.Commit()
923
931
924
932
case jmodels.CommitOperationDelete:
925
-
if err := db.DeleteIssueComments(
933
+
if err := db.DeleteComments(
926
934
ddb,
927
935
orm.FilterEq("did", did),
936
+
orm.FilterEq("collection", e.Commit.Collection),
928
937
orm.FilterEq("rkey", rkey),
929
938
); err != nil {
930
939
return fmt.Errorf("failed to delete issue comment record: %w", err)
+38
-36
appview/issues/issues.go
+38
-36
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(replyToRaw)
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.mentionsResolver.Resolve(r.Context(), body)
416
421
417
-
comment := models.IssueComment{
418
-
Did: user.Active.Did,
422
+
comment := models.Comment{
423
+
Did: syntax.DID(user.Active.Did),
424
+
Collection: tangled.CommentNSID,
419
425
Rkey: tid.TID(),
420
-
IssueAt: issue.AtUri().String(),
426
+
Subject: issue.AtUri(),
421
427
ReplyTo: replyTo,
422
428
Body: body,
423
429
Created: time.Now(),
424
430
Mentions: mentions,
425
431
References: references,
426
432
}
427
-
if err = rp.validator.ValidateIssueComment(&comment); err != nil {
433
+
if err = comment.Validate(); err != nil {
428
434
l.Error("failed to validate comment", "err", err)
429
435
rp.pages.Notice(w, "issue-comment", "Failed to create comment.")
430
436
return
431
437
}
432
-
record := comment.AsRecord()
433
438
434
439
client, err := rp.oauth.AuthorizedClient(r)
435
440
if err != nil {
···
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: comment.Collection.String(),
449
+
Repo: comment.Did.String(),
445
450
Rkey: comment.Rkey,
446
451
Record: &lexutil.LexiconTypeDecoder{
447
-
Val: &record,
452
+
Val: comment.AsRecord(),
448
453
},
449
454
})
450
455
if err != nil {
···
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
orm.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
orm.FilterEq("id", commentId),
546
548
)
···
556
558
}
557
559
comment := comments[0]
558
560
559
-
if comment.Did != user.Active.Did {
561
+
if comment.Did.String() != user.Active.Did {
560
562
l.Error("unauthorized comment edit", "expectedDid", comment.Did, "gotDid", user.Active.Did)
561
563
http.Error(w, "you are not the author of this comment", http.StatusUnauthorized)
562
564
return
···
586
588
newComment.Edited = &now
587
589
newComment.Mentions, newComment.References = rp.mentionsResolver.Resolve(r.Context(), newBody)
588
590
589
-
record := newComment.AsRecord()
590
-
591
591
tx, err := rp.db.Begin()
592
592
if err != nil {
593
593
l.Error("failed to start transaction", "err", err)
···
596
596
}
597
597
defer tx.Rollback()
598
598
599
-
_, err = db.AddIssueComment(tx, newComment)
599
+
err = db.PutComment(tx, &newComment)
600
600
if err != nil {
601
601
l.Error("failed to perferom update-description query", "err", err)
602
602
rp.pages.Notice(w, "repo-notice", "Failed to update description, try again later.")
···
606
606
607
607
// rkey is optional, it was introduced later
608
608
if newComment.Rkey != "" {
609
+
// TODO: update correct comment
610
+
609
611
// update the record on pds
610
-
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoIssueCommentNSID, user.Active.Did, comment.Rkey)
612
+
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", newComment.Collection.String(), newComment.Did.String(), newComment.Rkey)
611
613
if err != nil {
612
614
l.Error("failed to get record", "err", err, "did", newComment.Did, "rkey", newComment.Rkey)
613
-
rp.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "Failed to update description, no record found on PDS.")
615
+
rp.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "Failed to update comment, no record found on PDS.")
614
616
return
615
617
}
616
618
617
619
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
618
-
Collection: tangled.RepoIssueCommentNSID,
619
-
Repo: user.Active.Did,
620
+
Collection: newComment.Collection.String(),
621
+
Repo: newComment.Did.String(),
620
622
Rkey: newComment.Rkey,
621
623
SwapRecord: ex.Cid,
622
624
Record: &lexutil.LexiconTypeDecoder{
623
-
Val: &record,
625
+
Val: newComment.AsRecord(),
624
626
},
625
627
})
626
628
if err != nil {
···
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
orm.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
orm.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
orm.FilterEq("id", commentId),
728
730
)
···
738
740
}
739
741
comment := comments[0]
740
742
741
-
if comment.Did != user.Active.Did {
743
+
if comment.Did.String() != user.Active.Did {
742
744
l.Error("unauthorized action", "expectedDid", comment.Did, "gotDid", user.Active.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, orm.FilterEq("id", comment.Id))
756
+
err = db.DeleteComments(rp.db, orm.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,
771
-
Repo: user.Active.Did,
772
+
Collection: comment.Collection.String(),
773
+
Repo: comment.Did.String(),
772
774
Rkey: comment.Rkey,
773
775
})
774
776
if err != nil {
+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
···
122
122
)
123
123
}
124
124
125
-
func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) {
126
-
issues, err := db.GetIssues(n.db, orm.FilterEq("at_uri", comment.IssueAt))
125
+
func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) {
126
+
issues, err := db.GetIssues(n.db, orm.FilterEq("at_uri", comment.Subject))
127
127
if err != nil {
128
128
log.Printf("NewIssueComment: failed to get issues: %v", err)
129
129
return
130
130
}
131
131
if len(issues) == 0 {
132
-
log.Printf("NewIssueComment: no issue found for %s", comment.IssueAt)
132
+
log.Printf("NewIssueComment: no issue found for %s", comment.Subject)
133
133
return
134
134
}
135
135
issue := issues[0]
···
147
147
148
148
// find the parent thread, and add all DIDs from here to the recipient list
149
149
for _, t := range issue.CommentList() {
150
-
if t.Self.AtUri().String() == parentAtUri {
150
+
if t.Self.AtUri() == parentAtUri {
151
151
for _, p := range t.Participants() {
152
152
recipients.Insert(p)
153
153
}
+1
-1
appview/notify/merged_notifier.go
+1
-1
appview/notify/merged_notifier.go
···
57
57
m.fanout("NewIssue", ctx, issue, mentions)
58
58
}
59
59
60
-
func (m *mergedNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) {
60
+
func (m *mergedNotifier) NewIssueComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) {
61
61
m.fanout("NewIssueComment", ctx, comment, mentions)
62
62
}
63
63
+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
···
1004
1004
LoggedInUser *oauth.MultiAccountUser
1005
1005
RepoInfo repoinfo.RepoInfo
1006
1006
Issue *models.Issue
1007
-
Comment *models.IssueComment
1007
+
Comment *models.Comment
1008
1008
}
1009
1009
1010
1010
func (p *Pages) EditIssueCommentFragment(w io.Writer, params EditIssueCommentParams) error {
···
1015
1015
LoggedInUser *oauth.MultiAccountUser
1016
1016
RepoInfo repoinfo.RepoInfo
1017
1017
Issue *models.Issue
1018
-
Comment *models.IssueComment
1018
+
Comment *models.Comment
1019
1019
}
1020
1020
1021
1021
func (p *Pages) ReplyIssueCommentPlaceholderFragment(w io.Writer, params ReplyIssueCommentPlaceholderParams) error {
···
1026
1026
LoggedInUser *oauth.MultiAccountUser
1027
1027
RepoInfo repoinfo.RepoInfo
1028
1028
Issue *models.Issue
1029
-
Comment *models.IssueComment
1029
+
Comment *models.Comment
1030
1030
}
1031
1031
1032
1032
func (p *Pages) ReplyIssueCommentFragment(w io.Writer, params ReplyIssueCommentParams) error {
···
1037
1037
LoggedInUser *oauth.MultiAccountUser
1038
1038
RepoInfo repoinfo.RepoInfo
1039
1039
Issue *models.Issue
1040
-
Comment *models.IssueComment
1040
+
Comment *models.Comment
1041
1041
}
1042
1042
1043
1043
func (p *Pages) IssueCommentBodyFragment(w io.Writer, params IssueCommentBodyParams) error {
+2
-2
appview/pages/templates/repo/issues/fragments/commentList.html
+2
-2
appview/pages/templates/repo/issues/fragments/commentList.html
···
41
41
{{ define "topLevelComment" }}
42
42
<div class="rounded px-6 py-4 bg-white dark:bg-gray-800 flex gap-2 ">
43
43
<div class="flex-shrink-0">
44
-
{{ template "user/fragments/picLink" (list .Comment.Did "size-8 mr-1") }}
44
+
{{ template "user/fragments/picLink" (list .Comment.Did.String "size-8 mr-1") }}
45
45
</div>
46
46
<div class="flex-1 min-w-0">
47
47
{{ template "repo/issues/fragments/issueCommentHeader" . }}
···
53
53
{{ define "replyComment" }}
54
54
<div class="py-4 pr-4 w-full mx-auto overflow-hidden flex gap-2 ">
55
55
<div class="flex-shrink-0">
56
-
{{ template "user/fragments/picLink" (list .Comment.Did "size-8 mr-1") }}
56
+
{{ template "user/fragments/picLink" (list .Comment.Did.String "size-8 mr-1") }}
57
57
</div>
58
58
<div class="flex-1 min-w-0">
59
59
{{ template "repo/issues/fragments/issueCommentHeader" . }}
+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
-
{{ $handle := resolve .Comment.Did }}
3
+
{{ $handle := resolve .Comment.Did.String }}
4
4
<a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="/{{ $handle }}">{{ $handle }}</a>
5
5
{{ template "hats" $ }}
6
6
<span class="before:content-['路']"></span>
7
7
{{ template "timestamp" . }}
8
-
{{ $isCommentOwner := and .LoggedInUser (eq .LoggedInUser.Did .Comment.Did) }}
8
+
{{ $isCommentOwner := and .LoggedInUser (eq .LoggedInUser.Did .Comment.Did.String) }}
9
9
{{ if and $isCommentOwner (not .Comment.Deleted) }}
10
10
{{ template "editIssueComment" . }}
11
11
{{ template "deleteIssueComment" . }}
-27
appview/validator/issue.go
-27
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
-
"tangled.org/core/orm"
10
8
)
11
9
12
-
func (v *Validator) ValidateIssueComment(comment *models.IssueComment) error {
13
-
// if comments have parents, only ingest ones that are 1 level deep
14
-
if comment.ReplyTo != nil {
15
-
parents, err := db.GetIssueComments(v.db, orm.FilterEq("at_uri", *comment.ReplyTo))
16
-
if err != nil {
17
-
return fmt.Errorf("failed to fetch parent comment: %w", err)
18
-
}
19
-
if len(parents) != 1 {
20
-
return fmt.Errorf("incorrect number of parent comments returned: %d", len(parents))
21
-
}
22
-
23
-
// depth check
24
-
parent := parents[0]
25
-
if parent.ReplyTo != nil {
26
-
return fmt.Errorf("incorrect depth, this comment is replying at depth >1")
27
-
}
28
-
}
29
-
30
-
if sb := strings.TrimSpace(v.sanitizer.SanitizeDefault(comment.Body)); sb == "" {
31
-
return fmt.Errorf("body is empty after HTML sanitization")
32
-
}
33
-
34
-
return nil
35
-
}
36
-
37
10
func (v *Validator) ValidateIssue(issue *models.Issue) error {
38
11
if issue.Title == "" {
39
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/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.