appview: replace PullComment to Comment #865

open
opened by boltless.me targeting master from sl/wnrvrwyvrlzo
Changed files
+406 -215
appview
+197
appview/db/comments.go
···
···
+
package db
+
+
import (
+
"database/sql"
+
"fmt"
+
"maps"
+
"slices"
+
"sort"
+
"strings"
+
"time"
+
+
"github.com/bluesky-social/indigo/atproto/syntax"
+
"tangled.org/core/appview/models"
+
)
+
+
func PutComment(tx *sql.Tx, c *models.Comment) error {
+
result, err := tx.Exec(
+
`insert into comments (
+
did,
+
rkey,
+
subject_at,
+
reply_to,
+
body,
+
pull_submission_id,
+
created
+
)
+
values (?, ?, ?, ?, ?, ?, ?)
+
on conflict(did, rkey) do update set
+
subject_at = excluded.subject_at,
+
reply_to = excluded.reply_to,
+
body = excluded.body,
+
edited = case
+
when
+
comments.subject_at != excluded.subject_at
+
or comments.body != excluded.body
+
or comments.reply_to != excluded.reply_to
+
then ?
+
else comments.edited
+
end`,
+
c.Did,
+
c.Rkey,
+
c.Subject,
+
c.ReplyTo,
+
c.Body,
+
c.PullSubmissionId,
+
c.Created.Format(time.RFC3339),
+
time.Now().Format(time.RFC3339),
+
)
+
if err != nil {
+
return err
+
}
+
+
c.Id, err = result.LastInsertId()
+
if err != nil {
+
return err
+
}
+
+
if err := putReferences(tx, c.AtUri(), c.References); err != nil {
+
return fmt.Errorf("put reference_links: %w", err)
+
}
+
+
return nil
+
}
+
+
func DeleteComments(e Execer, filters ...filter) error {
+
var conditions []string
+
var args []any
+
for _, filter := range filters {
+
conditions = append(conditions, filter.Condition())
+
args = append(args, filter.Arg()...)
+
}
+
+
whereClause := ""
+
if conditions != nil {
+
whereClause = " where " + strings.Join(conditions, " and ")
+
}
+
+
query := fmt.Sprintf(`update comments set body = "", deleted = strftime('%%Y-%%m-%%dT%%H:%%M:%%SZ', 'now') %s`, whereClause)
+
+
_, err := e.Exec(query, args...)
+
return err
+
}
+
+
func GetComments(e Execer, filters ...filter) ([]models.Comment, error) {
+
commentMap := make(map[string]*models.Comment)
+
+
var conditions []string
+
var args []any
+
for _, filter := range filters {
+
conditions = append(conditions, filter.Condition())
+
args = append(args, filter.Arg()...)
+
}
+
+
whereClause := ""
+
if conditions != nil {
+
whereClause = " where " + strings.Join(conditions, " and ")
+
}
+
+
query := fmt.Sprintf(`
+
select
+
id,
+
did,
+
rkey,
+
subject_at,
+
reply_to,
+
body,
+
pull_submission_id,
+
created,
+
edited,
+
deleted
+
from
+
comments
+
%s
+
`, whereClause)
+
+
rows, err := e.Query(query, args...)
+
if err != nil {
+
return nil, err
+
}
+
+
for rows.Next() {
+
var comment models.Comment
+
var created string
+
var rkey, edited, deleted, replyTo sql.Null[string]
+
err := rows.Scan(
+
&comment.Id,
+
&comment.Did,
+
&rkey,
+
&comment.Subject,
+
&replyTo,
+
&comment.Body,
+
&comment.PullSubmissionId,
+
&created,
+
&edited,
+
&deleted,
+
)
+
if err != nil {
+
return nil, err
+
}
+
+
// this is a remnant from old times, newer comments always have rkey
+
if rkey.Valid {
+
comment.Rkey = rkey.V
+
}
+
+
if t, err := time.Parse(time.RFC3339, created); err == nil {
+
comment.Created = t
+
}
+
+
if edited.Valid {
+
if t, err := time.Parse(time.RFC3339, edited.V); err == nil {
+
comment.Edited = &t
+
}
+
}
+
+
if deleted.Valid {
+
if t, err := time.Parse(time.RFC3339, deleted.V); err == nil {
+
comment.Deleted = &t
+
}
+
}
+
+
if replyTo.Valid {
+
rt := syntax.ATURI(replyTo.V)
+
comment.ReplyTo = &rt
+
}
+
+
atUri := comment.AtUri().String()
+
commentMap[atUri] = &comment
+
}
+
+
if err := rows.Err(); err != nil {
+
return nil, err
+
}
+
+
// collect references from each comments
+
commentAts := slices.Collect(maps.Keys(commentMap))
+
allReferencs, err := GetReferencesAll(e, FilterIn("from_at", commentAts))
+
if err != nil {
+
return nil, fmt.Errorf("failed to query reference_links: %w", err)
+
}
+
for commentAt, references := range allReferencs {
+
if comment, ok := commentMap[commentAt.String()]; ok {
+
comment.References = references
+
}
+
}
+
+
var comments []models.Comment
+
for _, c := range commentMap {
+
comments = append(comments, *c)
+
}
+
+
sort.Slice(comments, func(i, j int) bool {
+
return comments[i].Created.After(comments[j].Created)
+
})
+
+
return comments, nil
+
}
+32
appview/db/db.go
···
return err
})
return &DB{
db,
logger,
···
return err
})
+
// not migrating existing comments here
+
// all legacy comments will be dropped
+
runMigration(conn, logger, "add-comments-table", func(tx *sql.Tx) error {
+
_, err := tx.Exec(`
+
drop table comments;
+
+
create table comments (
+
-- identifiers
+
id integer primary key autoincrement,
+
did text not null,
+
rkey text not null,
+
at_uri text generated always as ('at://' || did || '/' || 'sh.tangled.comment' || '/' || rkey) stored,
+
+
-- at identifiers
+
subject_at text not null,
+
reply_to text, -- at_uri of parent comment
+
+
pull_submission_id integer, -- dirty fix until we atprotate the pull-rounds
+
+
-- content
+
body text not null,
+
created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
+
edited text,
+
deleted text,
+
+
-- constraints
+
unique(did, rkey)
+
);
+
`)
+
return err
+
})
+
return &DB{
db,
logger,
+6 -121
appview/db/pulls.go
···
return nil, err
}
-
// Get comments for all submissions using GetPullComments
submissionIds := slices.Collect(maps.Keys(submissionMap))
-
comments, err := GetPullComments(e, FilterIn("submission_id", submissionIds))
if err != nil {
return nil, fmt.Errorf("failed to get pull comments: %w", err)
}
for _, comment := range comments {
-
if submission, ok := submissionMap[comment.SubmissionId]; ok {
-
submission.Comments = append(submission.Comments, comment)
}
}
···
return m, nil
}
-
func GetPullComments(e Execer, filters ...filter) ([]models.PullComment, error) {
-
var conditions []string
-
var args []any
-
for _, filter := range filters {
-
conditions = append(conditions, filter.Condition())
-
args = append(args, filter.Arg()...)
-
}
-
-
whereClause := ""
-
if conditions != nil {
-
whereClause = " where " + strings.Join(conditions, " and ")
-
}
-
-
query := fmt.Sprintf(`
-
select
-
id,
-
pull_id,
-
submission_id,
-
repo_at,
-
owner_did,
-
comment_at,
-
body,
-
created
-
from
-
pull_comments
-
%s
-
order by
-
created asc
-
`, whereClause)
-
-
rows, err := e.Query(query, args...)
-
if err != nil {
-
return nil, err
-
}
-
defer rows.Close()
-
-
commentMap := make(map[string]*models.PullComment)
-
for rows.Next() {
-
var comment models.PullComment
-
var createdAt string
-
err := rows.Scan(
-
&comment.ID,
-
&comment.PullId,
-
&comment.SubmissionId,
-
&comment.RepoAt,
-
&comment.OwnerDid,
-
&comment.CommentAt,
-
&comment.Body,
-
&createdAt,
-
)
-
if err != nil {
-
return nil, err
-
}
-
-
if t, err := time.Parse(time.RFC3339, createdAt); err == nil {
-
comment.Created = t
-
}
-
-
atUri := comment.AtUri().String()
-
commentMap[atUri] = &comment
-
}
-
-
if err := rows.Err(); err != nil {
-
return nil, err
-
}
-
-
// collect references for each comments
-
commentAts := slices.Collect(maps.Keys(commentMap))
-
allReferencs, err := GetReferencesAll(e, FilterIn("from_at", commentAts))
-
if err != nil {
-
return nil, fmt.Errorf("failed to query reference_links: %w", err)
-
}
-
for commentAt, references := range allReferencs {
-
if comment, ok := commentMap[commentAt.String()]; ok {
-
comment.References = references
-
}
-
}
-
-
var comments []models.PullComment
-
for _, c := range commentMap {
-
comments = append(comments, *c)
-
}
-
-
sort.Slice(comments, func(i, j int) bool {
-
return comments[i].Created.Before(comments[j].Created)
-
})
-
-
return comments, nil
-
}
-
// timeframe here is directly passed into the sql query filter, and any
// timeframe in the past should be negative; e.g.: "-3 months"
func GetPullsByOwnerDid(e Execer, did, timeframe string) ([]models.Pull, error) {
···
return pulls, nil
}
-
func NewPullComment(tx *sql.Tx, comment *models.PullComment) (int64, error) {
-
query := `insert into pull_comments (owner_did, repo_at, submission_id, comment_at, pull_id, body) values (?, ?, ?, ?, ?, ?)`
-
res, err := tx.Exec(
-
query,
-
comment.OwnerDid,
-
comment.RepoAt,
-
comment.SubmissionId,
-
comment.CommentAt,
-
comment.PullId,
-
comment.Body,
-
)
-
if err != nil {
-
return 0, err
-
}
-
-
i, err := res.LastInsertId()
-
if err != nil {
-
return 0, err
-
}
-
-
if err := putReferences(tx, comment.AtUri(), comment.References); err != nil {
-
return 0, fmt.Errorf("put reference_links: %w", err)
-
}
-
-
return i, nil
-
}
-
func SetPullState(e Execer, repoAt syntax.ATURI, pullId int, pullState models.PullState) error {
_, err := e.Exec(
`update pulls set state = ? where repo_at = ? and pull_id = ? and (state <> ? or state <> ?)`,
···
return nil, err
}
+
// Get comments for all submissions using GetComments
submissionIds := slices.Collect(maps.Keys(submissionMap))
+
comments, err := GetComments(e, FilterIn("pull_submission_id", submissionIds))
if err != nil {
return nil, fmt.Errorf("failed to get pull comments: %w", err)
}
for _, comment := range comments {
+
if comment.PullSubmissionId != nil {
+
if submission, ok := submissionMap[*comment.PullSubmissionId]; ok {
+
submission.Comments = append(submission.Comments, comment)
+
}
}
}
···
return m, nil
}
// timeframe here is directly passed into the sql query filter, and any
// timeframe in the past should be negative; e.g.: "-3 months"
func GetPullsByOwnerDid(e Execer, did, timeframe string) ([]models.Pull, error) {
···
return pulls, nil
}
func SetPullState(e Execer, repoAt syntax.ATURI, pullId int, pullState models.PullState) error {
_, err := e.Exec(
`update pulls set state = ? where repo_at = ? and pull_id = ? and (state <> ? or state <> ?)`,
+7 -8
appview/db/reference.go
···
values %s
)
select
-
p.owner_did, p.rkey,
-
c.comment_at
from input inp
join repos r
on r.did = inp.owner_did
···
join pulls p
on p.repo_at = r.at_uri
and p.pull_id = inp.pull_id
-
left join pull_comments c
on inp.comment_id is not null
-
and c.repo_at = r.at_uri and c.pull_id = p.pull_id
and c.id = inp.comment_id
`,
strings.Join(vals, ","),
···
return nil, fmt.Errorf("get pull backlinks: %w", err)
}
backlinks = append(backlinks, ls...)
-
ls, err = getPullCommentBacklinks(e, backlinksMap[tangled.RepoPullCommentNSID])
if err != nil {
return nil, fmt.Errorf("get pull_comment backlinks: %w", err)
}
···
if len(aturis) == 0 {
return nil, nil
}
-
filter := FilterIn("c.comment_at", aturis)
rows, err := e.Query(
fmt.Sprintf(
`select r.did, r.name, p.pull_id, c.id, p.title, p.state
from repos r
join pulls p
on r.at_uri = p.repo_at
-
join pull_comments c
-
on r.at_uri = c.repo_at and p.pull_id = c.pull_id
where %s`,
filter.Condition(),
),
···
values %s
)
select
+
p.owner_did, p.rkey, c.at_uri
from input inp
join repos r
on r.did = inp.owner_did
···
join pulls p
on p.repo_at = r.at_uri
and p.pull_id = inp.pull_id
+
left join comments c
on inp.comment_id is not null
+
and c.subject_at = ('at://' || p.owner_did || '/' || 'sh.tangled.repo.pull' || '/' || p.rkey)
and c.id = inp.comment_id
`,
strings.Join(vals, ","),
···
return nil, fmt.Errorf("get pull backlinks: %w", err)
}
backlinks = append(backlinks, ls...)
+
ls, err = getPullCommentBacklinks(e, backlinksMap[tangled.CommentNSID])
if err != nil {
return nil, fmt.Errorf("get pull_comment backlinks: %w", err)
}
···
if len(aturis) == 0 {
return nil, nil
}
+
filter := FilterIn("c.at_uri", aturis)
rows, err := e.Query(
fmt.Sprintf(
`select r.did, r.name, p.pull_id, c.id, p.title, p.state
from repos r
join pulls p
on r.at_uri = p.repo_at
+
join comments c
+
on ('at://' || p.owner_did || '/' || 'sh.tangled.repo.pull' || '/' || p.rkey) = c.subject_at
where %s`,
filter.Condition(),
),
+117
appview/models/comment.go
···
···
+
package models
+
+
import (
+
"fmt"
+
"strings"
+
"time"
+
+
"github.com/bluesky-social/indigo/atproto/syntax"
+
"tangled.org/core/api/tangled"
+
)
+
+
type Comment struct {
+
Id int64
+
Did syntax.DID
+
Rkey string
+
Subject syntax.ATURI
+
ReplyTo *syntax.ATURI
+
Body string
+
Created time.Time
+
Edited *time.Time
+
Deleted *time.Time
+
Mentions []syntax.DID
+
References []syntax.ATURI
+
PullSubmissionId *int
+
}
+
+
func (c *Comment) AtUri() syntax.ATURI {
+
return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", c.Did, tangled.CommentNSID, c.Rkey))
+
}
+
+
func (c *Comment) AsRecord() tangled.Comment {
+
mentions := make([]string, len(c.Mentions))
+
for i, did := range c.Mentions {
+
mentions[i] = string(did)
+
}
+
references := make([]string, len(c.References))
+
for i, uri := range c.References {
+
references[i] = string(uri)
+
}
+
var replyTo *string
+
if c.ReplyTo != nil {
+
replyToStr := c.ReplyTo.String()
+
replyTo = &replyToStr
+
}
+
return tangled.Comment{
+
Subject: c.Subject.String(),
+
Body: c.Body,
+
CreatedAt: c.Created.Format(time.RFC3339),
+
ReplyTo: replyTo,
+
Mentions: mentions,
+
References: references,
+
}
+
}
+
+
func (c *Comment) IsTopLevel() bool {
+
return c.ReplyTo == nil
+
}
+
+
func (c *Comment) IsReply() bool {
+
return c.ReplyTo != nil
+
}
+
+
func (c *Comment) Validate() error {
+
// TODO: sanitize the body and then trim space
+
if sb := strings.TrimSpace(c.Body); sb == "" {
+
return fmt.Errorf("body is empty after HTML sanitization")
+
}
+
+
// if it's for PR, PullSubmissionId should not be nil
+
if c.Subject.Collection().String() == tangled.RepoPullNSID {
+
if c.PullSubmissionId == nil {
+
return fmt.Errorf("PullSubmissionId should not be nil")
+
}
+
}
+
return nil
+
}
+
+
func CommentFromRecord(did, rkey string, record tangled.Comment) (*Comment, error) {
+
created, err := time.Parse(time.RFC3339, record.CreatedAt)
+
if err != nil {
+
created = time.Now()
+
}
+
+
ownerDid := did
+
+
if _, err = syntax.ParseATURI(record.Subject); err != nil {
+
return nil, err
+
}
+
+
i := record
+
mentions := make([]syntax.DID, len(record.Mentions))
+
for i, did := range record.Mentions {
+
mentions[i] = syntax.DID(did)
+
}
+
references := make([]syntax.ATURI, len(record.References))
+
for i, uri := range i.References {
+
references[i] = syntax.ATURI(uri)
+
}
+
var replyTo *syntax.ATURI
+
if record.ReplyTo != nil {
+
replyToAtUri := syntax.ATURI(*record.ReplyTo)
+
replyTo = &replyToAtUri
+
}
+
+
comment := Comment{
+
Did: syntax.DID(ownerDid),
+
Rkey: rkey,
+
Body: record.Body,
+
Subject: syntax.ATURI(record.Subject),
+
ReplyTo: replyTo,
+
Created: created,
+
Mentions: mentions,
+
References: references,
+
}
+
+
return &comment, nil
+
}
+2 -46
appview/models/pull.go
···
RoundNumber int
Patch string
Combined string
-
Comments []PullComment
SourceRev string // include the rev that was used to create this submission: only for branch/fork PRs
// meta
Created time.Time
}
-
type PullComment struct {
-
// ids
-
ID int
-
PullId int
-
SubmissionId int
-
-
// at ids
-
RepoAt string
-
OwnerDid string
-
CommentAt string
-
-
// content
-
Body string
-
-
// meta
-
Mentions []syntax.DID
-
References []syntax.ATURI
-
-
// meta
-
Created time.Time
-
}
-
-
func (p *PullComment) AtUri() syntax.ATURI {
-
return syntax.ATURI(p.CommentAt)
-
}
-
-
// func (p *PullComment) AsRecord() tangled.RepoPullComment {
-
// mentions := make([]string, len(p.Mentions))
-
// for i, did := range p.Mentions {
-
// mentions[i] = string(did)
-
// }
-
// references := make([]string, len(p.References))
-
// for i, uri := range p.References {
-
// references[i] = string(uri)
-
// }
-
// return tangled.RepoPullComment{
-
// Pull: p.PullAt,
-
// Body: p.Body,
-
// Mentions: mentions,
-
// References: references,
-
// CreatedAt: p.Created.Format(time.RFC3339),
-
// }
-
// }
-
func (p *Pull) LastRoundNumber() int {
return len(p.Submissions) - 1
}
···
addParticipant(s.PullAt.Authority().String())
for _, c := range s.Comments {
-
addParticipant(c.OwnerDid)
}
return participants
···
RoundNumber int
Patch string
Combined string
+
Comments []Comment
SourceRev string // include the rev that was used to create this submission: only for branch/fork PRs
// meta
Created time.Time
}
func (p *Pull) LastRoundNumber() int {
return len(p.Submissions) - 1
}
···
addParticipant(s.PullAt.Authority().String())
for _, c := range s.Comments {
+
addParticipant(c.Did.String())
}
return participants
+11 -6
appview/notify/db/db.go
···
)
}
-
func (n *databaseNotifier) NewPullComment(ctx context.Context, comment *models.PullComment, mentions []syntax.DID) {
-
pull, err := db.GetPull(n.db,
-
syntax.ATURI(comment.RepoAt),
-
comment.PullId,
)
if err != nil {
log.Printf("NewPullComment: failed to get pulls: %v", err)
return
}
-
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", comment.RepoAt))
if err != nil {
log.Printf("NewPullComment: failed to get repos: %v", err)
return
···
recipients = append(recipients, syntax.DID(p))
}
-
actorDid := syntax.DID(comment.OwnerDid)
eventType := models.NotificationTypePullCommented
entityType := "pull"
entityId := pull.AtUri().String()
···
)
}
+
func (n *databaseNotifier) NewPullComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) {
+
pulls, err := db.GetPulls(n.db,
+
db.FilterEq("owner_did", comment.Subject.Authority()),
+
db.FilterEq("rkey", comment.Subject.RecordKey()),
)
if err != nil {
log.Printf("NewPullComment: failed to get pulls: %v", err)
return
}
+
if len(pulls) == 0 {
+
log.Printf("NewPullComment: no pull found for %s", comment.Subject)
+
return
+
}
+
pull := pulls[0]
+
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", pull.RepoAt))
if err != nil {
log.Printf("NewPullComment: failed to get repos: %v", err)
return
···
recipients = append(recipients, syntax.DID(p))
}
+
actorDid := comment.Did
eventType := models.NotificationTypePullCommented
entityType := "pull"
entityId := pull.AtUri().String()
+1 -1
appview/notify/merged_notifier.go
···
m.fanout("NewPull", ctx, pull)
}
-
func (m *mergedNotifier) NewPullComment(ctx context.Context, comment *models.PullComment, mentions []syntax.DID) {
m.fanout("NewPullComment", ctx, comment, mentions)
}
···
m.fanout("NewPull", ctx, pull)
}
+
func (m *mergedNotifier) NewPullComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) {
m.fanout("NewPullComment", ctx, comment, mentions)
}
+2 -2
appview/notify/notifier.go
···
DeleteFollow(ctx context.Context, follow *models.Follow)
NewPull(ctx context.Context, pull *models.Pull)
-
NewPullComment(ctx context.Context, comment *models.PullComment, mentions []syntax.DID)
NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull)
UpdateProfile(ctx context.Context, profile *models.Profile)
···
func (m *BaseNotifier) DeleteFollow(ctx context.Context, follow *models.Follow) {}
func (m *BaseNotifier) NewPull(ctx context.Context, pull *models.Pull) {}
-
func (m *BaseNotifier) NewPullComment(ctx context.Context, models *models.PullComment, mentions []syntax.DID) {
}
func (m *BaseNotifier) NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) {}
···
DeleteFollow(ctx context.Context, follow *models.Follow)
NewPull(ctx context.Context, pull *models.Pull)
+
NewPullComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID)
NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull)
UpdateProfile(ctx context.Context, profile *models.Profile)
···
func (m *BaseNotifier) DeleteFollow(ctx context.Context, follow *models.Follow) {}
func (m *BaseNotifier) NewPull(ctx context.Context, pull *models.Pull) {}
+
func (m *BaseNotifier) NewPullComment(ctx context.Context, models *models.Comment, mentions []syntax.DID) {
}
func (m *BaseNotifier) NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) {}
+3 -4
appview/notify/posthog/notifier.go
···
}
}
-
func (n *posthogNotifier) NewPullComment(ctx context.Context, comment *models.PullComment, mentions []syntax.DID) {
err := n.client.Enqueue(posthog.Capture{
-
DistinctId: comment.OwnerDid,
Event: "new_pull_comment",
Properties: posthog.Properties{
-
"repo_at": comment.RepoAt,
-
"pull_id": comment.PullId,
"mentions": mentions,
},
})
···
}
}
+
func (n *posthogNotifier) NewPullComment(ctx context.Context, comment *models.Comment, mentions []syntax.DID) {
err := n.client.Enqueue(posthog.Capture{
+
DistinctId: comment.Did.String(),
Event: "new_pull_comment",
Properties: posthog.Properties{
+
"pull_at": comment.Subject,
"mentions": mentions,
},
})
+3 -3
appview/pages/templates/repo/pulls/pull.html
···
<div class="md:pl-[3.5rem] flex flex-col gap-2 mt-2 relative">
{{ range $cidx, $c := .Comments }}
-
<div id="comment-{{$c.ID}}" class="bg-white dark:bg-gray-800 rounded drop-shadow-sm py-2 px-4 relative w-full">
{{ if gt $cidx 0 }}
<div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div>
{{ end }}
<div class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1">
-
{{ template "user/fragments/picHandleLink" $c.OwnerDid }}
<span class="before:content-['·']"></span>
-
<a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="#comment-{{.ID}}">{{ template "repo/fragments/time" $c.Created }}</a>
</div>
<div class="prose dark:prose-invert">
{{ $c.Body | markdown }}
···
<div class="md:pl-[3.5rem] flex flex-col gap-2 mt-2 relative">
{{ range $cidx, $c := .Comments }}
+
<div id="comment-{{$c.Id}}" class="bg-white dark:bg-gray-800 rounded drop-shadow-sm py-2 px-4 relative w-full">
{{ if gt $cidx 0 }}
<div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div>
{{ end }}
<div class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1">
+
{{ template "user/fragments/picHandleLink" $c.Did.String }}
<span class="before:content-['·']"></span>
+
<a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="#comment-{{.Id}}">{{ template "repo/fragments/time" $c.Created }}</a>
</div>
<div class="prose dark:prose-invert">
{{ $c.Body | markdown }}
+1 -1
appview/pulls/opengraph.go
···
}
// Get comment count from database
-
comments, err := db.GetPullComments(s.db, db.FilterEq("pull_id", pull.ID))
if err != nil {
log.Printf("failed to get pull comments: %v", err)
}
···
}
// Get comment count from database
+
comments, err := db.GetComments(s.db, db.FilterEq("subject_at", pull.AtUri()))
if err != nil {
log.Printf("failed to get pull comments: %v", err)
}
+24 -23
appview/pulls/pulls.go
···
}
defer tx.Rollback()
-
createdAt := time.Now().Format(time.RFC3339)
client, err := s.oauth.AuthorizedClient(r)
if err != nil {
···
s.pages.Notice(w, "pull-comment", "Failed to create comment.")
return
}
-
atResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
-
Collection: tangled.RepoPullCommentNSID,
Repo: user.Did,
-
Rkey: tid.TID(),
Record: &lexutil.LexiconTypeDecoder{
-
Val: &tangled.RepoPullComment{
-
Pull: pull.AtUri().String(),
-
Body: body,
-
CreatedAt: createdAt,
-
},
},
})
if err != nil {
···
return
}
-
comment := &models.PullComment{
-
OwnerDid: user.Did,
-
RepoAt: f.RepoAt().String(),
-
PullId: pull.PullId,
-
Body: body,
-
CommentAt: atResp.Uri,
-
SubmissionId: pull.Submissions[roundNumber].ID,
-
Mentions: mentions,
-
References: references,
-
}
-
// Create the pull comment in the database with the commentAt field
-
commentId, err := db.NewPullComment(tx, comment)
if err != nil {
log.Println("failed to create pull comment", err)
s.pages.Notice(w, "pull-comment", "Failed to create comment.")
···
return
}
-
s.notifier.NewPullComment(r.Context(), comment, mentions)
ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
-
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d#comment-%d", ownerSlashRepo, pull.PullId, commentId))
return
}
}
···
}
defer tx.Rollback()
+
comment := models.Comment{
+
Did: syntax.DID(user.Did),
+
Rkey: tid.TID(),
+
Subject: pull.AtUri(),
+
ReplyTo: nil,
+
Body: body,
+
Created: time.Now(),
+
Mentions: mentions,
+
References: references,
+
PullSubmissionId: &pull.Submissions[roundNumber].ID,
+
}
+
if err = comment.Validate(); err != nil {
+
log.Println("failed to validate comment", err)
+
s.pages.Notice(w, "pull-comment", "Failed to create comment.")
+
return
+
}
+
record := comment.AsRecord()
client, err := s.oauth.AuthorizedClient(r)
if err != nil {
···
s.pages.Notice(w, "pull-comment", "Failed to create comment.")
return
}
+
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
+
Collection: tangled.CommentNSID,
Repo: user.Did,
+
Rkey: comment.Rkey,
Record: &lexutil.LexiconTypeDecoder{
+
Val: &record,
},
})
if err != nil {
···
return
}
// Create the pull comment in the database with the commentAt field
+
err = db.PutComment(tx, &comment)
if err != nil {
log.Println("failed to create pull comment", err)
s.pages.Notice(w, "pull-comment", "Failed to create comment.")
···
return
}
+
s.notifier.NewPullComment(r.Context(), &comment, mentions)
ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
+
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d#comment-%d", ownerSlashRepo, pull.PullId, comment.Id))
return
}
}