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
···
885
885
}
886
886
887
887
switch e.Commit.Operation {
888
-
case jmodels.CommitOperationCreate, jmodels.CommitOperationUpdate:
888
+
case jmodels.CommitOperationUpdate:
889
889
raw := json.RawMessage(e.Commit.Record)
890
890
record := tangled.RepoIssueComment{}
891
891
err = json.Unmarshal(raw, &record)
···
893
893
return fmt.Errorf("invalid record: %w", err)
894
894
}
895
895
896
-
comment, err := models.IssueCommentFromRecord(did, rkey, record)
896
+
// convert 'sh.tangled.repo.issue.comment' to 'sh.tangled.comment'
897
+
comment, err := models.CommentFromRecord(syntax.DID(did), syntax.RecordKey(rkey), tangled.Comment{
898
+
Body: record.Body,
899
+
CreatedAt: record.CreatedAt,
900
+
Mentions: record.Mentions,
901
+
References: record.References,
902
+
ReplyTo: record.ReplyTo,
903
+
Subject: record.Issue,
904
+
})
897
905
if err != nil {
898
906
return fmt.Errorf("failed to parse comment from record: %w", err)
899
907
}
900
908
901
-
if err := i.Validator.ValidateIssueComment(comment); err != nil {
909
+
if err := comment.Validate(); err != nil {
902
910
return fmt.Errorf("failed to validate comment: %w", err)
903
911
}
904
912
···
908
916
}
909
917
defer tx.Rollback()
910
918
911
-
_, err = db.AddIssueComment(tx, *comment)
919
+
err = db.PutComment(tx, comment)
912
920
if err != nil {
913
-
return fmt.Errorf("failed to create issue comment: %w", err)
921
+
return fmt.Errorf("failed to create comment: %w", err)
914
922
}
915
923
916
924
return tx.Commit()
917
925
918
926
case jmodels.CommitOperationDelete:
919
-
if err := db.DeleteIssueComments(
927
+
if err := db.DeleteComments(
920
928
ddb,
921
929
orm.FilterEq("did", did),
930
+
orm.FilterEq("collection", e.Commit.Collection),
922
931
orm.FilterEq("rkey", rkey),
923
932
); err != nil {
924
933
return fmt.Errorf("failed to delete issue comment record: %w", err)
+38
-36
appview/issues/issues.go
+38
-36
appview/issues/issues.go
···
403
403
404
404
body := r.FormValue("body")
405
405
if body == "" {
406
-
rp.pages.Notice(w, "issue", "Body is required")
406
+
rp.pages.Notice(w, "issue-comment", "Body is required")
407
407
return
408
408
}
409
409
410
-
replyToUri := r.FormValue("reply-to")
411
-
var replyTo *string
412
-
if replyToUri != "" {
413
-
replyTo = &replyToUri
410
+
var replyTo *syntax.ATURI
411
+
replyToRaw := r.FormValue("reply-to")
412
+
if replyToRaw != "" {
413
+
aturi, err := syntax.ParseATURI(replyToRaw)
414
+
if err != nil {
415
+
rp.pages.Notice(w, "issue-comment", "reply-to should be valid AT-URI")
416
+
return
417
+
}
418
+
replyTo = &aturi
414
419
}
415
420
416
421
mentions, references := rp.mentionsResolver.Resolve(r.Context(), body)
417
422
418
-
comment := models.IssueComment{
419
-
Did: user.Active.Did,
423
+
comment := models.Comment{
424
+
Did: syntax.DID(user.Active.Did),
425
+
Collection: tangled.CommentNSID,
420
426
Rkey: tid.TID(),
421
-
IssueAt: issue.AtUri().String(),
427
+
Subject: issue.AtUri(),
422
428
ReplyTo: replyTo,
423
429
Body: body,
424
430
Created: time.Now(),
425
431
Mentions: mentions,
426
432
References: references,
427
433
}
428
-
if err = rp.validator.ValidateIssueComment(&comment); err != nil {
434
+
if err = comment.Validate(); err != nil {
429
435
l.Error("failed to validate comment", "err", err)
430
436
rp.pages.Notice(w, "issue-comment", "Failed to create comment.")
431
437
return
432
438
}
433
-
record := comment.AsRecord()
434
439
435
440
client, err := rp.oauth.AuthorizedClient(r)
436
441
if err != nil {
···
441
446
442
447
// create a record first
443
448
resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
444
-
Collection: tangled.RepoIssueCommentNSID,
445
-
Repo: comment.Did,
449
+
Collection: comment.Collection.String(),
450
+
Repo: comment.Did.String(),
446
451
Rkey: comment.Rkey,
447
452
Record: &lexutil.LexiconTypeDecoder{
448
-
Val: &record,
453
+
Val: comment.AsRecord(),
449
454
},
450
455
})
451
456
if err != nil {
···
468
473
}
469
474
defer tx.Rollback()
470
475
471
-
commentId, err := db.AddIssueComment(tx, comment)
476
+
err = db.PutComment(tx, &comment)
472
477
if err != nil {
473
478
l.Error("failed to create comment", "err", err)
474
479
rp.pages.Notice(w, "issue-comment", "Failed to create comment.")
···
484
489
// reset atUri to make rollback a no-op
485
490
atUri = ""
486
491
487
-
// notify about the new comment
488
-
comment.Id = commentId
489
-
490
492
rp.notifier.NewIssueComment(r.Context(), &comment, mentions)
491
493
492
494
ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
493
-
rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d#comment-%d", ownerSlashRepo, issue.IssueId, commentId))
495
+
rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d#comment-%d", ownerSlashRepo, issue.IssueId, comment.Id))
494
496
}
495
497
496
498
func (rp *Issues) IssueComment(w http.ResponseWriter, r *http.Request) {
···
505
507
}
506
508
507
509
commentId := chi.URLParam(r, "commentId")
508
-
comments, err := db.GetIssueComments(
510
+
comments, err := db.GetComments(
509
511
rp.db,
510
512
orm.FilterEq("id", commentId),
511
513
)
···
541
543
}
542
544
543
545
commentId := chi.URLParam(r, "commentId")
544
-
comments, err := db.GetIssueComments(
546
+
comments, err := db.GetComments(
545
547
rp.db,
546
548
orm.FilterEq("id", commentId),
547
549
)
···
557
559
}
558
560
comment := comments[0]
559
561
560
-
if comment.Did != user.Active.Did {
562
+
if comment.Did.String() != user.Active.Did {
561
563
l.Error("unauthorized comment edit", "expectedDid", comment.Did, "gotDid", user.Active.Did)
562
564
http.Error(w, "you are not the author of this comment", http.StatusUnauthorized)
563
565
return
···
587
589
newComment.Edited = &now
588
590
newComment.Mentions, newComment.References = rp.mentionsResolver.Resolve(r.Context(), newBody)
589
591
590
-
record := newComment.AsRecord()
591
-
592
592
tx, err := rp.db.Begin()
593
593
if err != nil {
594
594
l.Error("failed to start transaction", "err", err)
···
597
597
}
598
598
defer tx.Rollback()
599
599
600
-
_, err = db.AddIssueComment(tx, newComment)
600
+
err = db.PutComment(tx, &newComment)
601
601
if err != nil {
602
602
l.Error("failed to perferom update-description query", "err", err)
603
603
rp.pages.Notice(w, "repo-notice", "Failed to update description, try again later.")
···
607
607
608
608
// rkey is optional, it was introduced later
609
609
if newComment.Rkey != "" {
610
+
// TODO: update correct comment
611
+
610
612
// update the record on pds
611
-
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoIssueCommentNSID, user.Active.Did, comment.Rkey)
613
+
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", newComment.Collection.String(), newComment.Did.String(), newComment.Rkey)
612
614
if err != nil {
613
615
l.Error("failed to get record", "err", err, "did", newComment.Did, "rkey", newComment.Rkey)
614
-
rp.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "Failed to update description, no record found on PDS.")
616
+
rp.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "Failed to update comment, no record found on PDS.")
615
617
return
616
618
}
617
619
618
620
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
619
-
Collection: tangled.RepoIssueCommentNSID,
620
-
Repo: user.Active.Did,
621
+
Collection: newComment.Collection.String(),
622
+
Repo: newComment.Did.String(),
621
623
Rkey: newComment.Rkey,
622
624
SwapRecord: ex.Cid,
623
625
Record: &lexutil.LexiconTypeDecoder{
624
-
Val: &record,
626
+
Val: newComment.AsRecord(),
625
627
},
626
628
})
627
629
if err != nil {
···
651
653
}
652
654
653
655
commentId := chi.URLParam(r, "commentId")
654
-
comments, err := db.GetIssueComments(
656
+
comments, err := db.GetComments(
655
657
rp.db,
656
658
orm.FilterEq("id", commentId),
657
659
)
···
687
689
}
688
690
689
691
commentId := chi.URLParam(r, "commentId")
690
-
comments, err := db.GetIssueComments(
692
+
comments, err := db.GetComments(
691
693
rp.db,
692
694
orm.FilterEq("id", commentId),
693
695
)
···
723
725
}
724
726
725
727
commentId := chi.URLParam(r, "commentId")
726
-
comments, err := db.GetIssueComments(
728
+
comments, err := db.GetComments(
727
729
rp.db,
728
730
orm.FilterEq("id", commentId),
729
731
)
···
739
741
}
740
742
comment := comments[0]
741
743
742
-
if comment.Did != user.Active.Did {
744
+
if comment.Did.String() != user.Active.Did {
743
745
l.Error("unauthorized action", "expectedDid", comment.Did, "gotDid", user.Active.Did)
744
746
http.Error(w, "you are not the author of this comment", http.StatusUnauthorized)
745
747
return
···
752
754
753
755
// optimistic deletion
754
756
deleted := time.Now()
755
-
err = db.DeleteIssueComments(rp.db, orm.FilterEq("id", comment.Id))
757
+
err = db.DeleteComments(rp.db, orm.FilterEq("id", comment.Id))
756
758
if err != nil {
757
759
l.Error("failed to delete comment", "err", err)
758
760
rp.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "failed to delete comment")
···
768
770
return
769
771
}
770
772
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
771
-
Collection: tangled.RepoIssueCommentNSID,
772
-
Repo: user.Active.Did,
773
+
Collection: comment.Collection.String(),
774
+
Repo: comment.Did.String(),
773
775
Rkey: comment.Rkey,
774
776
})
775
777
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
···
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
44
<img
45
-
src="{{ tinyAvatar .Comment.Did }}"
45
+
src="{{ tinyAvatar .Comment.Did.String }}"
46
46
alt=""
47
47
class="rounded-full size-8 mr-1 border-2 border-gray-100 dark:border-gray-900"
48
48
/>
···
58
58
<div class="py-4 pr-4 w-full mx-auto overflow-hidden flex gap-2 ">
59
59
<div class="flex-shrink-0">
60
60
<img
61
-
src="{{ tinyAvatar .Comment.Did }}"
61
+
src="{{ tinyAvatar .Comment.Did.String }}"
62
62
alt=""
63
63
class="rounded-full size-8 mr-1 border-2 border-gray-100 dark:border-gray-900"
64
64
/>
+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
-
{{ resolve .Comment.Did }}
3
+
{{ resolve .Comment.Did.String }}
4
4
{{ template "hats" $ }}
5
5
<span class="before:content-['路']"></span>
6
6
{{ template "timestamp" . }}
7
-
{{ $isCommentOwner := and .LoggedInUser (eq .LoggedInUser.Did .Comment.Did) }}
7
+
{{ $isCommentOwner := and .LoggedInUser (eq .LoggedInUser.Did .Comment.Did.String) }}
8
8
{{ if and $isCommentOwner (not .Comment.Deleted) }}
9
9
{{ template "editIssueComment" . }}
10
10
{{ 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.