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