Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).

add rkeys to comments

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

+149 -52
+8
appview/db/db.go
··· 248 248 return nil 249 249 }) 250 250 251 + runMigration(db, "add-rkey-to-comments", func(tx *sql.Tx) error { 252 + _, err := tx.Exec(` 253 + alter table comments drop column comment_at; 254 + alter table comments add column rkey text; 255 + `) 256 + return err 257 + }) 258 + 251 259 runMigration(db, "add-deleted-and-edited-to-issue-comments", func(tx *sql.Tx) error { 252 260 // add unconstrained column 253 261 _, err := tx.Exec(`
+52 -9
appview/db/issues.go
··· 27 27 type Comment struct { 28 28 OwnerDid string 29 29 RepoAt syntax.ATURI 30 - CommentAt syntax.ATURI 30 + Rkey string 31 31 Issue int 32 32 CommentId int 33 33 Body string ··· 201 201 return &issue, comments, nil 202 202 } 203 203 204 - func NewComment(e Execer, comment *Comment) error { 205 - query := `insert into comments (owner_did, repo_at, comment_at, issue_id, comment_id, body) values (?, ?, ?, ?, ?, ?)` 204 + func NewIssueComment(e Execer, comment *Comment) error { 205 + query := `insert into comments (owner_did, repo_at, rkey, issue_id, comment_id, body) values (?, ?, ?, ?, ?, ?)` 206 206 _, err := e.Exec( 207 207 query, 208 208 comment.OwnerDid, 209 209 comment.RepoAt, 210 - comment.CommentAt, 210 + comment.Rkey, 211 211 comment.Issue, 212 212 comment.CommentId, 213 213 comment.Body, ··· 218 218 func GetComments(e Execer, repoAt syntax.ATURI, issueId int) ([]Comment, error) { 219 219 var comments []Comment 220 220 221 - 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) 221 + rows, err := e.Query(` 222 + select 223 + owner_did, 224 + issue_id, 225 + comment_id, 226 + rkey, 227 + body, 228 + created, 229 + edited, 230 + deleted 231 + from 232 + comments 233 + where 234 + repo_at = ? and issue_id = ? 235 + order by 236 + created asc`, 237 + repoAt, 238 + issueId, 239 + ) 222 240 if err == sql.ErrNoRows { 223 241 return []Comment{}, nil 224 242 } ··· 248 230 for rows.Next() { 249 231 var comment Comment 250 232 var createdAt string 251 - err := rows.Scan(&comment.OwnerDid, &comment.Issue, &comment.CommentId, &comment.CommentAt, &comment.Body, &createdAt) 233 + var deletedAt, editedAt, rkey sql.NullString 234 + err := rows.Scan(&comment.OwnerDid, &comment.Issue, &comment.CommentId, &rkey, &comment.Body, &createdAt, &editedAt, &deletedAt) 252 235 if err != nil { 253 236 return nil, err 254 237 } ··· 259 240 return nil, err 260 241 } 261 242 comment.Created = &createdAtTime 243 + 244 + if deletedAt.Valid { 245 + deletedTime, err := time.Parse(time.RFC3339, deletedAt.String) 246 + if err != nil { 247 + return nil, err 248 + } 249 + comment.Deleted = &deletedTime 250 + } 251 + 252 + if editedAt.Valid { 253 + editedTime, err := time.Parse(time.RFC3339, editedAt.String) 254 + if err != nil { 255 + return nil, err 256 + } 257 + comment.Edited = &editedTime 258 + } 259 + 260 + if rkey.Valid { 261 + comment.Rkey = rkey.String 262 + } 262 263 263 264 comments = append(comments, comment) 264 265 } ··· 293 254 func GetComment(e Execer, repoAt syntax.ATURI, issueId, commentId int) (*Comment, error) { 294 255 query := ` 295 256 select 296 - owner_did, body, comment_at, created, deleted, edited 257 + owner_did, body, rkey, created, deleted, edited 297 258 from 298 259 comments where repo_at = ? and issue_id = ? and comment_id = ? 299 260 ` ··· 301 262 302 263 var comment Comment 303 264 var createdAt string 304 - var deletedAt, editedAt sql.NullString 305 - err := row.Scan(&comment.OwnerDid, &comment.Body, &comment.CommentAt, &createdAt, &deletedAt, &editedAt) 265 + var deletedAt, editedAt, rkey sql.NullString 266 + err := row.Scan(&comment.OwnerDid, &comment.Body, &rkey, &createdAt, &deletedAt, &editedAt) 306 267 if err != nil { 307 268 return nil, err 308 269 } ··· 327 288 return nil, err 328 289 } 329 290 comment.Edited = &editedTime 291 + } 292 + 293 + if rkey.Valid { 294 + comment.Rkey = rkey.String 330 295 } 331 296 332 297 comment.RepoAt = repoAt
+15 -8
appview/pages/templates/fragments/issueComment.html
··· 9 9 {{ $isIssueAuthor := eq .OwnerDid $.Issue.OwnerDid }} 10 10 {{ if $isIssueAuthor }} 11 11 <span class="before:content-['·']"></span> 12 - <span class="rounded bg-gray-100 text-black font-mono px-2 mx-1/2 inline-flex items-center"> 12 + <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"> 13 13 author 14 14 </span> 15 15 {{ end }} ··· 19 19 href="#{{ .CommentId }}" 20 20 class="text-gray-500 hover:text-gray-500 hover:underline no-underline" 21 21 id="{{ .CommentId }}"> 22 - {{ .Created | timeFmt }} 22 + {{ if .Deleted }} 23 + deleted {{ .Deleted | timeFmt }} 24 + {{ else if .Edited }} 25 + edited {{ .Edited | timeFmt }} 26 + {{ else }} 27 + {{ .Created | timeFmt }} 28 + {{ end }} 23 29 </a> 24 30 25 31 {{ $isCommentOwner := eq $.LoggedInUser.Did .OwnerDid }} ··· 38 32 > 39 33 {{ i "pencil" "w-4 h-4" }} 40 34 </button> 41 - <button class="btn px-2 py-1 text-sm text-red-500" hx-delete=""> 35 + <button 36 + class="btn px-2 py-1 text-sm text-red-500" 37 + hx-delete="/{{ $.RepoInfo.FullName }}/issues/{{ .Issue }}/comment/{{ .CommentId }}/" 38 + hx-swap="outerHTML" 39 + hx-target="#comment-container-{{.CommentId}}" 40 + > 42 41 {{ i "trash-2" "w-4 h-4" }} 43 42 </button> 44 43 {{ end }} 45 44 46 - {{ if .Deleted }} 47 - <span class="before:content-['·']">deleted {{ .Deleted | timeFmt }}</span> 48 - {{ end }} 49 - 50 45 </div> 51 46 {{ if not .Deleted }} 52 - <div class="prose"> 47 + <div class="prose dark:prose-invert"> 53 48 {{ .Body | markdown }} 54 49 </div> 55 50 {{ end }}
+74 -35
appview/state/repo.go
··· 933 933 } 934 934 935 935 commentId := rand.IntN(1000000) 936 + rkey := s.TID() 936 937 937 - err := db.NewComment(s.db, &db.Comment{ 938 + err := db.NewIssueComment(s.db, &db.Comment{ 938 939 OwnerDid: user.Did, 939 940 RepoAt: f.RepoAt, 940 941 Issue: issueIdInt, 941 942 CommentId: commentId, 942 943 Body: body, 944 + Rkey: rkey, 943 945 }) 944 946 if err != nil { 945 947 log.Println("failed to create comment", err) ··· 964 962 _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 965 963 Collection: tangled.RepoIssueCommentNSID, 966 964 Repo: user.Did, 967 - Rkey: s.TID(), 965 + Rkey: rkey, 968 966 Record: &lexutil.LexiconTypeDecoder{ 969 967 Val: &tangled.RepoIssueComment{ 970 968 Repo: &atUri, ··· 1100 1098 // extract form value 1101 1099 newBody := r.FormValue("body") 1102 1100 client, _ := s.auth.AuthorizedClient(r) 1103 - log.Println("comment at", comment.CommentAt) 1104 - rkey := comment.CommentAt.RecordKey() 1101 + rkey := comment.Rkey 1105 1102 1106 1103 // optimistic update 1104 + edited := time.Now() 1107 1105 err = db.EditComment(s.db, comment.RepoAt, comment.Issue, comment.CommentId, newBody) 1108 1106 if err != nil { 1109 1107 log.Println("failed to perferom update-description query", err) ··· 1111 1109 return 1112 1110 } 1113 1111 1114 - // update the record on pds 1115 - ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoIssueCommentNSID, user.Did, rkey.String()) 1116 - if err != nil { 1117 - // failed to get record 1118 - log.Println(err, rkey.String()) 1119 - s.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "Failed to update description, no record found on PDS.") 1120 - return 1121 - } 1122 - value, _ := ex.Value.MarshalJSON() // we just did get record; it is valid json 1123 - record, _ := data.UnmarshalJSON(value) 1112 + // rkey is optional, it was introduced later 1113 + if comment.Rkey != "" { 1114 + // update the record on pds 1115 + ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoIssueCommentNSID, user.Did, rkey) 1116 + if err != nil { 1117 + // failed to get record 1118 + log.Println(err, rkey) 1119 + s.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "Failed to update description, no record found on PDS.") 1120 + return 1121 + } 1122 + value, _ := ex.Value.MarshalJSON() // we just did get record; it is valid json 1123 + record, _ := data.UnmarshalJSON(value) 1124 1124 1125 - repoAt := record["repo"].(string) 1126 - issueAt := record["issue"].(string) 1127 - createdAt := record["createdAt"].(string) 1128 - commentIdInt64 := int64(commentIdInt) 1125 + repoAt := record["repo"].(string) 1126 + issueAt := record["issue"].(string) 1127 + createdAt := record["createdAt"].(string) 1128 + commentIdInt64 := int64(commentIdInt) 1129 1129 1130 - _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1131 - Collection: tangled.RepoNSID, 1132 - Repo: user.Did, 1133 - Rkey: rkey.String(), 1134 - SwapRecord: ex.Cid, 1135 - Record: &lexutil.LexiconTypeDecoder{ 1136 - Val: &tangled.RepoIssueComment{ 1137 - Repo: &repoAt, 1138 - Issue: issueAt, 1139 - CommentId: &commentIdInt64, 1140 - Owner: &comment.OwnerDid, 1141 - Body: &newBody, 1142 - CreatedAt: &createdAt, 1130 + _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1131 + Collection: tangled.RepoIssueCommentNSID, 1132 + Repo: user.Did, 1133 + Rkey: rkey, 1134 + SwapRecord: ex.Cid, 1135 + Record: &lexutil.LexiconTypeDecoder{ 1136 + Val: &tangled.RepoIssueComment{ 1137 + Repo: &repoAt, 1138 + Issue: issueAt, 1139 + CommentId: &commentIdInt64, 1140 + Owner: &comment.OwnerDid, 1141 + Body: &newBody, 1142 + CreatedAt: &createdAt, 1143 + }, 1143 1144 }, 1144 - }, 1145 - }) 1146 - if err != nil { 1147 - log.Println(err) 1145 + }) 1146 + if err != nil { 1147 + log.Println(err) 1148 + } 1148 1149 } 1149 1150 1150 1151 // optimistic update for htmx ··· 1155 1150 user.Did: user.Handle, 1156 1151 } 1157 1152 comment.Body = newBody 1153 + comment.Edited = &edited 1158 1154 1159 1155 // return new comment body with htmx 1160 1156 s.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{ ··· 1187 1181 return 1188 1182 } 1189 1183 1184 + issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt) 1185 + if err != nil { 1186 + log.Println("failed to get issue", err) 1187 + s.pages.Notice(w, "issues", "Failed to load issue. Try again later.") 1188 + return 1189 + } 1190 + 1190 1191 commentId := chi.URLParam(r, "comment_id") 1191 1192 commentIdInt, err := strconv.Atoi(commentId) 1192 1193 if err != nil { ··· 1219 1206 } 1220 1207 1221 1208 // optimistic deletion 1209 + deleted := time.Now() 1222 1210 err = db.DeleteComment(s.db, f.RepoAt, issueIdInt, commentIdInt) 1223 1211 if err != nil { 1224 1212 log.Println("failed to delete comment") ··· 1228 1214 } 1229 1215 1230 1216 // delete from pds 1217 + if comment.Rkey != "" { 1218 + client, _ := s.auth.AuthorizedClient(r) 1219 + _, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{ 1220 + Collection: tangled.GraphFollowNSID, 1221 + Repo: user.Did, 1222 + Rkey: comment.Rkey, 1223 + }) 1224 + if err != nil { 1225 + log.Println(err) 1226 + } 1227 + } 1228 + 1229 + // optimistic update for htmx 1230 + didHandleMap := map[string]string{ 1231 + user.Did: user.Handle, 1232 + } 1233 + comment.Body = "" 1234 + comment.Deleted = &deleted 1231 1235 1232 1236 // htmx fragment of comment after deletion 1237 + s.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{ 1238 + LoggedInUser: user, 1239 + RepoInfo: f.RepoInfo(s, user), 1240 + DidHandleMap: didHandleMap, 1241 + Issue: issue, 1242 + Comment: comment, 1243 + }) 1233 1244 return 1234 1245 } 1235 1246