forked from tangled.org/core
this repo has no description

add rkeys to comments

this allows to actually edit and delete them from pds etc.

Changed files
+149 -52
appview
db
pages
templates
fragments
state
+8
appview/db/db.go
···
return nil
})
+
runMigration(db, "add-rkey-to-comments", func(tx *sql.Tx) error {
+
_, err := tx.Exec(`
+
alter table comments drop column comment_at;
+
alter table comments add column rkey text;
+
`)
+
return err
+
})
+
runMigration(db, "add-deleted-and-edited-to-issue-comments", func(tx *sql.Tx) error {
// add unconstrained column
_, err := tx.Exec(`
+52 -9
appview/db/issues.go
···
type Comment struct {
OwnerDid string
RepoAt syntax.ATURI
-
CommentAt syntax.ATURI
+
Rkey string
Issue int
CommentId int
Body string
···
return &issue, comments, nil
}
-
func NewComment(e Execer, comment *Comment) error {
-
query := `insert into comments (owner_did, repo_at, comment_at, issue_id, comment_id, body) values (?, ?, ?, ?, ?, ?)`
+
func NewIssueComment(e Execer, comment *Comment) error {
+
query := `insert into comments (owner_did, repo_at, rkey, issue_id, comment_id, body) values (?, ?, ?, ?, ?, ?)`
_, err := e.Exec(
query,
comment.OwnerDid,
comment.RepoAt,
-
comment.CommentAt,
+
comment.Rkey,
comment.Issue,
comment.CommentId,
comment.Body,
···
func GetComments(e Execer, repoAt syntax.ATURI, issueId int) ([]Comment, error) {
var comments []Comment
-
rows, err := e.Query(`select owner_did, issue_id, comment_id, comment_at, body, created from comments where repo_at = ? and issue_id = ? order by created asc`, repoAt, issueId)
+
rows, err := e.Query(`
+
select
+
owner_did,
+
issue_id,
+
comment_id,
+
rkey,
+
body,
+
created,
+
edited,
+
deleted
+
from
+
comments
+
where
+
repo_at = ? and issue_id = ?
+
order by
+
created asc`,
+
repoAt,
+
issueId,
+
)
if err == sql.ErrNoRows {
return []Comment{}, nil
}
···
for rows.Next() {
var comment Comment
var createdAt string
-
err := rows.Scan(&comment.OwnerDid, &comment.Issue, &comment.CommentId, &comment.CommentAt, &comment.Body, &createdAt)
+
var deletedAt, editedAt, rkey sql.NullString
+
err := rows.Scan(&comment.OwnerDid, &comment.Issue, &comment.CommentId, &rkey, &comment.Body, &createdAt, &editedAt, &deletedAt)
if err != nil {
return nil, err
}
···
}
comment.Created = &createdAtTime
+
if deletedAt.Valid {
+
deletedTime, err := time.Parse(time.RFC3339, deletedAt.String)
+
if err != nil {
+
return nil, err
+
}
+
comment.Deleted = &deletedTime
+
}
+
+
if editedAt.Valid {
+
editedTime, err := time.Parse(time.RFC3339, editedAt.String)
+
if err != nil {
+
return nil, err
+
}
+
comment.Edited = &editedTime
+
}
+
+
if rkey.Valid {
+
comment.Rkey = rkey.String
+
}
+
comments = append(comments, comment)
}
···
func GetComment(e Execer, repoAt syntax.ATURI, issueId, commentId int) (*Comment, error) {
query := `
select
-
owner_did, body, comment_at, created, deleted, edited
+
owner_did, body, rkey, created, deleted, edited
from
comments where repo_at = ? and issue_id = ? and comment_id = ?
`
···
var comment Comment
var createdAt string
-
var deletedAt, editedAt sql.NullString
-
err := row.Scan(&comment.OwnerDid, &comment.Body, &comment.CommentAt, &createdAt, &deletedAt, &editedAt)
+
var deletedAt, editedAt, rkey sql.NullString
+
err := row.Scan(&comment.OwnerDid, &comment.Body, &rkey, &createdAt, &deletedAt, &editedAt)
if err != nil {
return nil, err
}
···
return nil, err
}
comment.Edited = &editedTime
+
}
+
+
if rkey.Valid {
+
comment.Rkey = rkey.String
}
comment.RepoAt = repoAt
+15 -8
appview/pages/templates/fragments/issueComment.html
···
{{ $isIssueAuthor := eq .OwnerDid $.Issue.OwnerDid }}
{{ if $isIssueAuthor }}
<span class="before:content-['·']"></span>
-
<span class="rounded bg-gray-100 text-black font-mono px-2 mx-1/2 inline-flex items-center">
+
<span class="rounded bg-gray-100 dark:bg-gray-700 text-black dark:text-white font-mono px-2 mx-1/2 inline-flex items-center">
author
</span>
{{ end }}
···
href="#{{ .CommentId }}"
class="text-gray-500 hover:text-gray-500 hover:underline no-underline"
id="{{ .CommentId }}">
-
{{ .Created | timeFmt }}
+
{{ if .Deleted }}
+
deleted {{ .Deleted | timeFmt }}
+
{{ else if .Edited }}
+
edited {{ .Edited | timeFmt }}
+
{{ else }}
+
{{ .Created | timeFmt }}
+
{{ end }}
</a>
{{ $isCommentOwner := eq $.LoggedInUser.Did .OwnerDid }}
···
>
{{ i "pencil" "w-4 h-4" }}
</button>
-
<button class="btn px-2 py-1 text-sm text-red-500" hx-delete="">
+
<button
+
class="btn px-2 py-1 text-sm text-red-500"
+
hx-delete="/{{ $.RepoInfo.FullName }}/issues/{{ .Issue }}/comment/{{ .CommentId }}/"
+
hx-swap="outerHTML"
+
hx-target="#comment-container-{{.CommentId}}"
+
>
{{ i "trash-2" "w-4 h-4" }}
</button>
{{ end }}
-
{{ if .Deleted }}
-
<span class="before:content-['·']">deleted {{ .Deleted | timeFmt }}</span>
-
{{ end }}
-
</div>
{{ if not .Deleted }}
-
<div class="prose">
+
<div class="prose dark:prose-invert">
{{ .Body | markdown }}
</div>
{{ end }}
+74 -35
appview/state/repo.go
···
}
commentId := rand.IntN(1000000)
+
rkey := s.TID()
-
err := db.NewComment(s.db, &db.Comment{
+
err := db.NewIssueComment(s.db, &db.Comment{
OwnerDid: user.Did,
RepoAt: f.RepoAt,
Issue: issueIdInt,
CommentId: commentId,
Body: body,
+
Rkey: rkey,
})
if err != nil {
log.Println("failed to create comment", err)
···
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
Collection: tangled.RepoIssueCommentNSID,
Repo: user.Did,
-
Rkey: s.TID(),
+
Rkey: rkey,
Record: &lexutil.LexiconTypeDecoder{
Val: &tangled.RepoIssueComment{
Repo: &atUri,
···
// extract form value
newBody := r.FormValue("body")
client, _ := s.auth.AuthorizedClient(r)
-
log.Println("comment at", comment.CommentAt)
-
rkey := comment.CommentAt.RecordKey()
+
rkey := comment.Rkey
// optimistic update
+
edited := time.Now()
err = db.EditComment(s.db, comment.RepoAt, comment.Issue, comment.CommentId, newBody)
if err != nil {
log.Println("failed to perferom update-description query", err)
···
return
-
// update the record on pds
-
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoIssueCommentNSID, user.Did, rkey.String())
-
if err != nil {
-
// failed to get record
-
log.Println(err, rkey.String())
-
s.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "Failed to update description, no record found on PDS.")
-
return
-
}
-
value, _ := ex.Value.MarshalJSON() // we just did get record; it is valid json
-
record, _ := data.UnmarshalJSON(value)
+
// rkey is optional, it was introduced later
+
if comment.Rkey != "" {
+
// update the record on pds
+
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoIssueCommentNSID, user.Did, rkey)
+
if err != nil {
+
// failed to get record
+
log.Println(err, rkey)
+
s.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "Failed to update description, no record found on PDS.")
+
return
+
}
+
value, _ := ex.Value.MarshalJSON() // we just did get record; it is valid json
+
record, _ := data.UnmarshalJSON(value)
-
repoAt := record["repo"].(string)
-
issueAt := record["issue"].(string)
-
createdAt := record["createdAt"].(string)
-
commentIdInt64 := int64(commentIdInt)
+
repoAt := record["repo"].(string)
+
issueAt := record["issue"].(string)
+
createdAt := record["createdAt"].(string)
+
commentIdInt64 := int64(commentIdInt)
-
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
-
Collection: tangled.RepoNSID,
-
Repo: user.Did,
-
Rkey: rkey.String(),
-
SwapRecord: ex.Cid,
-
Record: &lexutil.LexiconTypeDecoder{
-
Val: &tangled.RepoIssueComment{
-
Repo: &repoAt,
-
Issue: issueAt,
-
CommentId: &commentIdInt64,
-
Owner: &comment.OwnerDid,
-
Body: &newBody,
-
CreatedAt: &createdAt,
+
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
+
Collection: tangled.RepoIssueCommentNSID,
+
Repo: user.Did,
+
Rkey: rkey,
+
SwapRecord: ex.Cid,
+
Record: &lexutil.LexiconTypeDecoder{
+
Val: &tangled.RepoIssueComment{
+
Repo: &repoAt,
+
Issue: issueAt,
+
CommentId: &commentIdInt64,
+
Owner: &comment.OwnerDid,
+
Body: &newBody,
+
CreatedAt: &createdAt,
+
},
},
-
},
-
})
-
if err != nil {
-
log.Println(err)
+
})
+
if err != nil {
+
log.Println(err)
+
}
// optimistic update for htmx
···
user.Did: user.Handle,
comment.Body = newBody
+
comment.Edited = &edited
// return new comment body with htmx
s.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
···
return
+
issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt)
+
if err != nil {
+
log.Println("failed to get issue", err)
+
s.pages.Notice(w, "issues", "Failed to load issue. Try again later.")
+
return
+
}
+
commentId := chi.URLParam(r, "comment_id")
commentIdInt, err := strconv.Atoi(commentId)
if err != nil {
···
// optimistic deletion
+
deleted := time.Now()
err = db.DeleteComment(s.db, f.RepoAt, issueIdInt, commentIdInt)
if err != nil {
log.Println("failed to delete comment")
···
// delete from pds
+
if comment.Rkey != "" {
+
client, _ := s.auth.AuthorizedClient(r)
+
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
+
Collection: tangled.GraphFollowNSID,
+
Repo: user.Did,
+
Rkey: comment.Rkey,
+
})
+
if err != nil {
+
log.Println(err)
+
}
+
}
+
+
// optimistic update for htmx
+
didHandleMap := map[string]string{
+
user.Did: user.Handle,
+
}
+
comment.Body = ""
+
comment.Deleted = &deleted
// htmx fragment of comment after deletion
+
s.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
+
LoggedInUser: user,
+
RepoInfo: f.RepoInfo(s, user),
+
DidHandleMap: didHandleMap,
+
Issue: issue,
+
Comment: comment,
+
})
return