alpha
Login
or
Join now
dunkirk.sh
/
core
forked from
tangled.org/core
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
This repository has no description
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
allow editing and deleting issues
author
Akshay
date
1 year ago
(Mar 27, 2025, 10:33 PM UTC)
commit
3d95c2e4
3d95c2e4f94d79ca9a37654f16c6b80985ce3250
parent
6d84fad3
6d84fad3d97469aebcca3fdbdb2ef4852f33d41b
+476
-46
9 changed files
Expand all
Collapse all
Unified
Split
appview
db
db.go
issues.go
pages
pages.go
templates
fragments
diff.html
editIssueComment.html
issueComment.html
repo
issues
issue.html
state
repo.go
router.go
+9
appview/db/db.go
Reviewed
···
248
248
return nil
249
249
})
250
250
251
251
+
runMigration(db, "add-deleted-and-edited-to-issue-comments", func(tx *sql.Tx) error {
252
252
+
// add unconstrained column
253
253
+
_, err := tx.Exec(`
254
254
+
alter table comments add column deleted text; -- timestamp
255
255
+
alter table comments add column edited text; -- timestamp
256
256
+
`)
257
257
+
return err
258
258
+
})
259
259
+
251
260
return &DB{db}, nil
252
261
}
253
262
+71
-1
appview/db/issues.go
Reviewed
···
27
27
type Comment struct {
28
28
OwnerDid string
29
29
RepoAt syntax.ATURI
30
30
-
CommentAt string
30
30
+
CommentAt syntax.ATURI
31
31
Issue int
32
32
CommentId int
33
33
Body string
34
34
Created *time.Time
35
35
+
Deleted *time.Time
36
36
+
Edited *time.Time
35
37
}
36
38
37
39
func NewIssue(tx *sql.Tx, issue *Issue) error {
···
247
249
}
248
250
249
251
return comments, nil
252
252
+
}
253
253
+
254
254
+
func GetComment(e Execer, repoAt syntax.ATURI, issueId, commentId int) (*Comment, error) {
255
255
+
query := `
256
256
+
select
257
257
+
owner_did, body, comment_at, created, deleted, edited
258
258
+
from
259
259
+
comments where repo_at = ? and issue_id = ? and comment_id = ?
260
260
+
`
261
261
+
row := e.QueryRow(query, repoAt, issueId, commentId)
262
262
+
263
263
+
var comment Comment
264
264
+
var createdAt string
265
265
+
var deletedAt, editedAt sql.NullString
266
266
+
err := row.Scan(&comment.OwnerDid, &comment.Body, &comment.CommentAt, &createdAt, &deletedAt, &editedAt)
267
267
+
if err != nil {
268
268
+
return nil, err
269
269
+
}
270
270
+
271
271
+
createdTime, err := time.Parse(time.RFC3339, createdAt)
272
272
+
if err != nil {
273
273
+
return nil, err
274
274
+
}
275
275
+
comment.Created = &createdTime
276
276
+
277
277
+
if deletedAt.Valid {
278
278
+
deletedTime, err := time.Parse(time.RFC3339, deletedAt.String)
279
279
+
if err != nil {
280
280
+
return nil, err
281
281
+
}
282
282
+
comment.Deleted = &deletedTime
283
283
+
}
284
284
+
285
285
+
if editedAt.Valid {
286
286
+
editedTime, err := time.Parse(time.RFC3339, editedAt.String)
287
287
+
if err != nil {
288
288
+
return nil, err
289
289
+
}
290
290
+
comment.Edited = &editedTime
291
291
+
}
292
292
+
293
293
+
comment.RepoAt = repoAt
294
294
+
comment.Issue = issueId
295
295
+
comment.CommentId = commentId
296
296
+
297
297
+
return &comment, nil
298
298
+
}
299
299
+
300
300
+
func EditComment(e Execer, repoAt syntax.ATURI, issueId, commentId int, newBody string) error {
301
301
+
_, err := e.Exec(
302
302
+
`
303
303
+
update comments
304
304
+
set body = ?,
305
305
+
edited = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
306
306
+
where repo_at = ? and issue_id = ? and comment_id = ?
307
307
+
`, newBody, repoAt, issueId, commentId)
308
308
+
return err
309
309
+
}
310
310
+
311
311
+
func DeleteComment(e Execer, repoAt syntax.ATURI, issueId, commentId int) error {
312
312
+
_, err := e.Exec(
313
313
+
`
314
314
+
update comments
315
315
+
set body = "",
316
316
+
deleted = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
317
317
+
where repo_at = ? and issue_id = ? and comment_id = ?
318
318
+
`, repoAt, issueId, commentId)
319
319
+
return err
250
320
}
251
321
252
322
func CloseIssue(e Execer, repoAt syntax.ATURI, issueId int) error {
+23
appview/pages/pages.go
Reviewed
···
534
534
return p.executeRepo("repo/issues/new", w, params)
535
535
}
536
536
537
537
+
type EditIssueCommentParams struct {
538
538
+
LoggedInUser *auth.User
539
539
+
RepoInfo RepoInfo
540
540
+
Issue *db.Issue
541
541
+
Comment *db.Comment
542
542
+
}
543
543
+
544
544
+
func (p *Pages) EditIssueCommentFragment(w io.Writer, params EditIssueCommentParams) error {
545
545
+
return p.executePlain("fragments/editIssueComment", w, params)
546
546
+
}
547
547
+
548
548
+
type SingleIssueCommentParams struct {
549
549
+
LoggedInUser *auth.User
550
550
+
DidHandleMap map[string]string
551
551
+
RepoInfo RepoInfo
552
552
+
Issue *db.Issue
553
553
+
Comment *db.Comment
554
554
+
}
555
555
+
556
556
+
func (p *Pages) SingleIssueCommentFragment(w io.Writer, params SingleIssueCommentParams) error {
557
557
+
return p.executePlain("fragments/issueComment", w, params)
558
558
+
}
559
559
+
537
560
type RepoNewPullParams struct {
538
561
LoggedInUser *auth.User
539
562
RepoInfo RepoInfo
+23
-18
appview/pages/templates/fragments/diff.html
Reviewed
···
79
79
This is a binary file and will not be displayed.
80
80
</p>
81
81
{{ else }}
82
82
-
<pre class="overflow-auto">
83
83
-
{{- range .TextFragments -}}
84
84
-
<div class="bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 select-none">{{ .Header }}</div>
85
85
-
{{- range .Lines -}}
86
86
-
{{- if eq .Op.String "+" -}}
87
87
-
<div class="bg-green-100 dark:bg-green-800/30 text-green-700 dark:text-green-400 p-1 w-full min-w-fit"><span class="select-none mx-2">{{ .Op.String }}</span><span>{{ .Line }}</span></div>
88
88
-
{{- end -}}
89
89
-
90
90
-
{{- if eq .Op.String "-" -}}
91
91
-
<div class="bg-red-100 dark:bg-red-800/30 text-red-700 dark:text-red-400 p-1 w-full min-w-fit"><span class="select-none mx-2">{{ .Op.String }}</span><span>{{ .Line }}</span></div>
92
92
-
{{- end -}}
93
93
-
94
94
-
{{- if eq .Op.String " " -}}
95
95
-
<div class="bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400 px"><span class="select-none mx-2">{{ .Op.String }}</span><span>{{ .Line }}</span></div>
96
96
-
{{- end -}}
97
97
-
98
98
-
{{- end -}}
99
99
-
{{- end -}}
82
82
+
<pre class="overflow-x-auto">
83
83
+
{{- range .TextFragments -}}
84
84
+
<div class="bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 select-none">{{- .Header -}}</div><div class="overflow-x-auto"><div class="min-w-full inline-block">
85
85
+
{{- range .Lines -}}
86
86
+
{{- if eq .Op.String "+" -}}
87
87
+
<div class="bg-green-100 dark:bg-green-800/30 text-green-700 dark:text-green-400 flex min-w-full">
88
88
+
<div class="w-10 flex-shrink-0 select-none p-1 text-center">{{ .Op.String }}</div>
89
89
+
<div class="p-1 whitespace-pre">{{ .Line }}</div>
90
90
+
</div>
91
91
+
{{- end -}}
92
92
+
{{- if eq .Op.String "-" -}}
93
93
+
<div class="bg-red-100 dark:bg-red-800/30 text-red-700 dark:text-red-400 flex min-w-full">
94
94
+
<div class="w-10 flex-shrink-0 select-none p-1 text-center">{{ .Op.String }}</div>
95
95
+
<div class="p-1 whitespace-pre">{{ .Line }}</div>
96
96
+
</div>
97
97
+
{{- end -}}
98
98
+
{{- if eq .Op.String " " -}}
99
99
+
<div class="bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400 flex min-w-full">
100
100
+
<div class="w-10 flex-shrink-0 select-none p-1 text-center">{{ .Op.String }}</div>
101
101
+
<div class="p-1 whitespace-pre">{{ .Line }}</div>
102
102
+
</div>
103
103
+
{{- end -}}
104
104
+
{{- end -}}</div></div>{{- end -}}
100
105
</pre>
101
106
{{- end -}}
102
107
{{ end }}
+52
appview/pages/templates/fragments/editIssueComment.html
Reviewed
···
1
1
+
{{ define "fragments/editIssueComment" }}
2
2
+
{{ with .Comment }}
3
3
+
<div id="comment-container-{{.CommentId}}">
4
4
+
<div class="flex items-center gap-2 mb-2 text-gray-500 text-sm">
5
5
+
{{ $owner := didOrHandle $.LoggedInUser.Did $.LoggedInUser.Handle }}
6
6
+
<a href="/{{ $owner }}" class="no-underline hover:underline">{{ $owner }}</a>
7
7
+
8
8
+
<!-- show user "hats" -->
9
9
+
{{ $isIssueAuthor := eq .OwnerDid $.Issue.OwnerDid }}
10
10
+
{{ if $isIssueAuthor }}
11
11
+
<span class="before:content-['·']"></span>
12
12
+
<span class="rounded bg-gray-100 text-black font-mono px-2 mx-1/2 inline-flex items-center">
13
13
+
author
14
14
+
</span>
15
15
+
{{ end }}
16
16
+
17
17
+
<span class="before:content-['·']"></span>
18
18
+
<a
19
19
+
href="#{{ .CommentId }}"
20
20
+
class="text-gray-500 hover:text-gray-500 hover:underline no-underline"
21
21
+
id="{{ .CommentId }}">
22
22
+
{{ .Created | timeFmt }}
23
23
+
</a>
24
24
+
25
25
+
<button
26
26
+
class="btn px-2 py-1 flex items-center gap-2 text-sm"
27
27
+
hx-post="/{{ $.RepoInfo.FullName }}/issues/{{ .Issue }}/comment/{{ .CommentId }}/edit"
28
28
+
hx-include="#edit-textarea-{{ .CommentId }}"
29
29
+
hx-target="#comment-container-{{ .CommentId }}"
30
30
+
hx-swap="outerHTML">
31
31
+
{{ i "check" "w-4 h-4" }}
32
32
+
</button>
33
33
+
<button
34
34
+
class="btn px-2 py-1 flex items-center gap-2 text-sm"
35
35
+
hx-get="/{{ $.RepoInfo.FullName }}/issues/{{ .Issue }}/comment/{{ .CommentId }}/"
36
36
+
hx-target="#comment-container-{{ .CommentId }}"
37
37
+
hx-swap="outerHTML">
38
38
+
{{ i "x" "w-4 h-4" }}
39
39
+
</button>
40
40
+
<span id="comment-{{.CommentId}}-status"></span>
41
41
+
</div>
42
42
+
43
43
+
<div>
44
44
+
<textarea
45
45
+
id="edit-textarea-{{ .CommentId }}"
46
46
+
name="body"
47
47
+
class="w-full p-2 border rounded min-h-[100px]">{{ .Body }}</textarea>
48
48
+
</div>
49
49
+
</div>
50
50
+
{{ end }}
51
51
+
{{ end }}
52
52
+
+52
appview/pages/templates/fragments/issueComment.html
Reviewed
···
1
1
+
{{ define "fragments/issueComment" }}
2
2
+
{{ with .Comment }}
3
3
+
<div id="comment-container-{{.CommentId}}">
4
4
+
<div class="flex items-center gap-2 mb-2 text-gray-500 text-sm">
5
5
+
{{ $owner := index $.DidHandleMap .OwnerDid }}
6
6
+
<a href="/{{ $owner }}" class="no-underline hover:underline">{{ $owner }}</a>
7
7
+
8
8
+
<!-- show user "hats" -->
9
9
+
{{ $isIssueAuthor := eq .OwnerDid $.Issue.OwnerDid }}
10
10
+
{{ if $isIssueAuthor }}
11
11
+
<span class="before:content-['·']"></span>
12
12
+
<span class="rounded bg-gray-100 text-black font-mono px-2 mx-1/2 inline-flex items-center">
13
13
+
author
14
14
+
</span>
15
15
+
{{ end }}
16
16
+
17
17
+
<span class="before:content-['·']"></span>
18
18
+
<a
19
19
+
href="#{{ .CommentId }}"
20
20
+
class="text-gray-500 hover:text-gray-500 hover:underline no-underline"
21
21
+
id="{{ .CommentId }}">
22
22
+
{{ .Created | timeFmt }}
23
23
+
</a>
24
24
+
25
25
+
{{ $isCommentOwner := eq $.LoggedInUser.Did .OwnerDid }}
26
26
+
{{ if and $isCommentOwner (not .Deleted) }}
27
27
+
<button
28
28
+
class="btn px-2 py-1 text-sm"
29
29
+
hx-get="/{{ $.RepoInfo.FullName }}/issues/{{ .Issue }}/comment/{{ .CommentId }}/edit"
30
30
+
hx-swap="outerHTML"
31
31
+
hx-target="#comment-container-{{.CommentId}}"
32
32
+
>
33
33
+
{{ i "pencil" "w-4 h-4" }}
34
34
+
</button>
35
35
+
<button class="btn px-2 py-1 text-sm text-red-500" hx-delete="">
36
36
+
{{ i "trash-2" "w-4 h-4" }}
37
37
+
</button>
38
38
+
{{ end }}
39
39
+
40
40
+
{{ if .Deleted }}
41
41
+
<span class="before:content-['·']">deleted {{ .Deleted | timeFmt }}</span>
42
42
+
{{ end }}
43
43
+
44
44
+
</div>
45
45
+
{{ if not .Deleted }}
46
46
+
<div class="prose">
47
47
+
{{ .Body | markdown }}
48
48
+
</div>
49
49
+
{{ end }}
50
50
+
</div>
51
51
+
{{ end }}
52
52
+
{{ end }}
+3
-25
appview/pages/templates/repo/issues/issue.html
Reviewed
···
1
1
-
{{ define "title" }}{{ .Issue.Title }} · issue #{{ .Issue.IssueId }} ·{{ .RepoInfo.FullName }}{{ end }}
1
1
+
{{ define "title" }}{{ .Issue.Title }} · issue #{{ .Issue.IssueId }} · {{ .RepoInfo.FullName }}{{ end }}
2
2
3
3
{{ define "repoContent" }}
4
4
<header class="pb-4">
···
49
49
{{ range $index, $comment := .Comments }}
50
50
<div
51
51
id="comment-{{ .CommentId }}"
52
52
-
class="rounded bg-white px-6 py-4 relative dark:bg-gray-800"
53
53
-
>
52
52
+
class="rounded bg-white px-6 py-4 relative dark:bg-gray-800">
54
53
{{ if eq $index 0 }}
55
54
<div class="absolute left-8 -top-8 w-px h-8 bg-gray-300 dark:bg-gray-700" ></div>
56
55
{{ else }}
57
56
<div class="absolute left-8 -top-4 w-px h-4 bg-gray-300 dark:bg-gray-700" ></div>
58
57
{{ end }}
59
59
-
<div class="flex items-center gap-2 mb-2 text-gray-500 dark:text-gray-400">
60
60
-
{{ $owner := index $.DidHandleMap .OwnerDid }}
61
61
-
<span class="text-sm">
62
62
-
<a
63
63
-
href="/{{ $owner }}"
64
64
-
class="no-underline hover:underline"
65
65
-
>{{ $owner }}</a
66
66
-
>
67
67
-
</span>
68
58
69
69
-
<span class="before:content-['·']"></span>
70
70
-
<a
71
71
-
href="#{{ .CommentId }}"
72
72
-
class="text-gray-500 text-sm hover:text-gray-500 hover:underline no-underline dark:text-gray-400 dark:hover:text-gray-300 dark:hover:bg-gray-800"
73
73
-
id="{{ .CommentId }}"
74
74
-
title="{{ .Created | longTimeFmt }}"
75
75
-
>
76
76
-
{{ .Created | timeFmt }}
77
77
-
</a>
78
78
-
</div>
79
79
-
<div class="prose dark:prose-invert">
80
80
-
{{ .Body | markdown }}
81
81
-
</div>
59
59
+
{{ template "fragments/issueComment" (dict "RepoInfo" $.RepoInfo "LoggedInUser" $.LoggedInUser "DidHandleMap" $.DidHandleMap "Issue" $.Issue "Comment" .)}}
82
60
</div>
83
61
{{ end }}
84
62
</section>
+236
-1
appview/state/repo.go
Reviewed
···
14
14
"strings"
15
15
"time"
16
16
17
17
+
"github.com/bluesky-social/indigo/atproto/data"
17
18
"github.com/bluesky-social/indigo/atproto/identity"
18
19
"github.com/bluesky-social/indigo/atproto/syntax"
19
20
securejoin "github.com/cyphar/filepath-securejoin"
···
907
908
}
908
909
}
909
910
910
910
-
func (s *State) IssueComment(w http.ResponseWriter, r *http.Request) {
911
911
+
func (s *State) NewIssueComment(w http.ResponseWriter, r *http.Request) {
911
912
user := s.auth.GetUser(r)
912
913
f, err := fullyResolvedRepo(r)
913
914
if err != nil {
···
982
983
s.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d#comment-%d", f.OwnerSlashRepo(), issueIdInt, commentId))
983
984
return
984
985
}
986
986
+
}
987
987
+
988
988
+
func (s *State) IssueComment(w http.ResponseWriter, r *http.Request) {
989
989
+
user := s.auth.GetUser(r)
990
990
+
f, err := fullyResolvedRepo(r)
991
991
+
if err != nil {
992
992
+
log.Println("failed to get repo and knot", err)
993
993
+
return
994
994
+
}
995
995
+
996
996
+
issueId := chi.URLParam(r, "issue")
997
997
+
issueIdInt, err := strconv.Atoi(issueId)
998
998
+
if err != nil {
999
999
+
http.Error(w, "bad issue id", http.StatusBadRequest)
1000
1000
+
log.Println("failed to parse issue id", err)
1001
1001
+
return
1002
1002
+
}
1003
1003
+
1004
1004
+
commentId := chi.URLParam(r, "comment_id")
1005
1005
+
commentIdInt, err := strconv.Atoi(commentId)
1006
1006
+
if err != nil {
1007
1007
+
http.Error(w, "bad comment id", http.StatusBadRequest)
1008
1008
+
log.Println("failed to parse issue id", err)
1009
1009
+
return
1010
1010
+
}
1011
1011
+
1012
1012
+
issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt)
1013
1013
+
if err != nil {
1014
1014
+
log.Println("failed to get issue", err)
1015
1015
+
s.pages.Notice(w, "issues", "Failed to load issue. Try again later.")
1016
1016
+
return
1017
1017
+
}
1018
1018
+
1019
1019
+
comment, err := db.GetComment(s.db, f.RepoAt, issueIdInt, commentIdInt)
1020
1020
+
if err != nil {
1021
1021
+
http.Error(w, "bad comment id", http.StatusBadRequest)
1022
1022
+
return
1023
1023
+
}
1024
1024
+
1025
1025
+
identity, err := s.resolver.ResolveIdent(r.Context(), comment.OwnerDid)
1026
1026
+
if err != nil {
1027
1027
+
log.Println("failed to resolve did")
1028
1028
+
return
1029
1029
+
}
1030
1030
+
1031
1031
+
didHandleMap := make(map[string]string)
1032
1032
+
if !identity.Handle.IsInvalidHandle() {
1033
1033
+
didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
1034
1034
+
} else {
1035
1035
+
didHandleMap[identity.DID.String()] = identity.DID.String()
1036
1036
+
}
1037
1037
+
1038
1038
+
s.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
1039
1039
+
LoggedInUser: user,
1040
1040
+
RepoInfo: f.RepoInfo(s, user),
1041
1041
+
DidHandleMap: didHandleMap,
1042
1042
+
Issue: issue,
1043
1043
+
Comment: comment,
1044
1044
+
})
1045
1045
+
}
1046
1046
+
1047
1047
+
func (s *State) EditIssueComment(w http.ResponseWriter, r *http.Request) {
1048
1048
+
user := s.auth.GetUser(r)
1049
1049
+
f, err := fullyResolvedRepo(r)
1050
1050
+
if err != nil {
1051
1051
+
log.Println("failed to get repo and knot", err)
1052
1052
+
return
1053
1053
+
}
1054
1054
+
1055
1055
+
issueId := chi.URLParam(r, "issue")
1056
1056
+
issueIdInt, err := strconv.Atoi(issueId)
1057
1057
+
if err != nil {
1058
1058
+
http.Error(w, "bad issue id", http.StatusBadRequest)
1059
1059
+
log.Println("failed to parse issue id", err)
1060
1060
+
return
1061
1061
+
}
1062
1062
+
1063
1063
+
commentId := chi.URLParam(r, "comment_id")
1064
1064
+
commentIdInt, err := strconv.Atoi(commentId)
1065
1065
+
if err != nil {
1066
1066
+
http.Error(w, "bad comment id", http.StatusBadRequest)
1067
1067
+
log.Println("failed to parse issue id", err)
1068
1068
+
return
1069
1069
+
}
1070
1070
+
1071
1071
+
issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt)
1072
1072
+
if err != nil {
1073
1073
+
log.Println("failed to get issue", err)
1074
1074
+
s.pages.Notice(w, "issues", "Failed to load issue. Try again later.")
1075
1075
+
return
1076
1076
+
}
1077
1077
+
1078
1078
+
comment, err := db.GetComment(s.db, f.RepoAt, issueIdInt, commentIdInt)
1079
1079
+
if err != nil {
1080
1080
+
http.Error(w, "bad comment id", http.StatusBadRequest)
1081
1081
+
return
1082
1082
+
}
1083
1083
+
1084
1084
+
if comment.OwnerDid != user.Did {
1085
1085
+
http.Error(w, "you are not the author of this comment", http.StatusUnauthorized)
1086
1086
+
return
1087
1087
+
}
1088
1088
+
1089
1089
+
switch r.Method {
1090
1090
+
case http.MethodGet:
1091
1091
+
s.pages.EditIssueCommentFragment(w, pages.EditIssueCommentParams{
1092
1092
+
LoggedInUser: user,
1093
1093
+
RepoInfo: f.RepoInfo(s, user),
1094
1094
+
Issue: issue,
1095
1095
+
Comment: comment,
1096
1096
+
})
1097
1097
+
case http.MethodPost:
1098
1098
+
// extract form value
1099
1099
+
newBody := r.FormValue("body")
1100
1100
+
client, _ := s.auth.AuthorizedClient(r)
1101
1101
+
log.Println("comment at", comment.CommentAt)
1102
1102
+
rkey := comment.CommentAt.RecordKey()
1103
1103
+
1104
1104
+
// optimistic update
1105
1105
+
err = db.EditComment(s.db, comment.RepoAt, comment.Issue, comment.CommentId, newBody)
1106
1106
+
if err != nil {
1107
1107
+
log.Println("failed to perferom update-description query", err)
1108
1108
+
s.pages.Notice(w, "repo-notice", "Failed to update description, try again later.")
1109
1109
+
return
1110
1110
+
}
1111
1111
+
1112
1112
+
// update the record on pds
1113
1113
+
ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoIssueCommentNSID, user.Did, rkey.String())
1114
1114
+
if err != nil {
1115
1115
+
// failed to get record
1116
1116
+
log.Println(err, rkey.String())
1117
1117
+
s.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "Failed to update description, no record found on PDS.")
1118
1118
+
return
1119
1119
+
}
1120
1120
+
value, _ := ex.Value.MarshalJSON() // we just did get record; it is valid json
1121
1121
+
record, _ := data.UnmarshalJSON(value)
1122
1122
+
1123
1123
+
repoAt := record["repo"].(string)
1124
1124
+
issueAt := record["issue"].(string)
1125
1125
+
createdAt := record["createdAt"].(string)
1126
1126
+
commentIdInt64 := int64(commentIdInt)
1127
1127
+
1128
1128
+
_, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
1129
1129
+
Collection: tangled.RepoNSID,
1130
1130
+
Repo: user.Did,
1131
1131
+
Rkey: rkey.String(),
1132
1132
+
SwapRecord: ex.Cid,
1133
1133
+
Record: &lexutil.LexiconTypeDecoder{
1134
1134
+
Val: &tangled.RepoIssueComment{
1135
1135
+
Repo: &repoAt,
1136
1136
+
Issue: issueAt,
1137
1137
+
CommentId: &commentIdInt64,
1138
1138
+
Owner: &comment.OwnerDid,
1139
1139
+
Body: &newBody,
1140
1140
+
CreatedAt: &createdAt,
1141
1141
+
},
1142
1142
+
},
1143
1143
+
})
1144
1144
+
if err != nil {
1145
1145
+
log.Println(err)
1146
1146
+
}
1147
1147
+
1148
1148
+
// optimistic update for htmx
1149
1149
+
didHandleMap := map[string]string{
1150
1150
+
user.Did: user.Handle,
1151
1151
+
}
1152
1152
+
comment.Body = newBody
1153
1153
+
1154
1154
+
// return new comment body with htmx
1155
1155
+
s.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
1156
1156
+
LoggedInUser: user,
1157
1157
+
RepoInfo: f.RepoInfo(s, user),
1158
1158
+
DidHandleMap: didHandleMap,
1159
1159
+
Issue: issue,
1160
1160
+
Comment: comment,
1161
1161
+
})
1162
1162
+
return
1163
1163
+
1164
1164
+
}
1165
1165
+
1166
1166
+
}
1167
1167
+
1168
1168
+
func (s *State) DeleteIssueComment(w http.ResponseWriter, r *http.Request) {
1169
1169
+
user := s.auth.GetUser(r)
1170
1170
+
f, err := fullyResolvedRepo(r)
1171
1171
+
if err != nil {
1172
1172
+
log.Println("failed to get repo and knot", err)
1173
1173
+
return
1174
1174
+
}
1175
1175
+
1176
1176
+
issueId := chi.URLParam(r, "issue")
1177
1177
+
issueIdInt, err := strconv.Atoi(issueId)
1178
1178
+
if err != nil {
1179
1179
+
http.Error(w, "bad issue id", http.StatusBadRequest)
1180
1180
+
log.Println("failed to parse issue id", err)
1181
1181
+
return
1182
1182
+
}
1183
1183
+
1184
1184
+
commentId := chi.URLParam(r, "comment_id")
1185
1185
+
commentIdInt, err := strconv.Atoi(commentId)
1186
1186
+
if err != nil {
1187
1187
+
http.Error(w, "bad comment id", http.StatusBadRequest)
1188
1188
+
log.Println("failed to parse issue id", err)
1189
1189
+
return
1190
1190
+
}
1191
1191
+
1192
1192
+
comment, err := db.GetComment(s.db, f.RepoAt, issueIdInt, commentIdInt)
1193
1193
+
if err != nil {
1194
1194
+
http.Error(w, "bad comment id", http.StatusBadRequest)
1195
1195
+
return
1196
1196
+
}
1197
1197
+
1198
1198
+
if comment.OwnerDid != user.Did {
1199
1199
+
http.Error(w, "you are not the author of this comment", http.StatusUnauthorized)
1200
1200
+
return
1201
1201
+
}
1202
1202
+
1203
1203
+
if comment.Deleted != nil {
1204
1204
+
http.Error(w, "comment already deleted", http.StatusBadRequest)
1205
1205
+
return
1206
1206
+
}
1207
1207
+
1208
1208
+
// optimistic deletion
1209
1209
+
err = db.DeleteComment(s.db, f.RepoAt, issueIdInt, commentIdInt)
1210
1210
+
if err != nil {
1211
1211
+
log.Println("failed to delete comment")
1212
1212
+
s.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "failed to delete comment")
1213
1213
+
return
1214
1214
+
}
1215
1215
+
1216
1216
+
// delete from pds
1217
1217
+
1218
1218
+
// htmx fragment of comment after deletion
1219
1219
+
return
985
1220
}
986
1221
987
1222
func (s *State) RepoIssues(w http.ResponseWriter, r *http.Request) {
+7
-1
appview/state/router.go
Reviewed
···
73
73
r.Use(AuthMiddleware(s))
74
74
r.Get("/new", s.NewIssue)
75
75
r.Post("/new", s.NewIssue)
76
76
-
r.Post("/{issue}/comment", s.IssueComment)
76
76
+
r.Post("/{issue}/comment", s.NewIssueComment)
77
77
+
r.Route("/{issue}/comment/{comment_id}/", func(r chi.Router) {
78
78
+
r.Get("/", s.IssueComment)
79
79
+
r.Delete("/", s.DeleteIssueComment)
80
80
+
r.Get("/edit", s.EditIssueComment)
81
81
+
r.Post("/edit", s.EditIssueComment)
82
82
+
})
77
83
r.Post("/{issue}/close", s.CloseIssue)
78
84
r.Post("/{issue}/reopen", s.ReopenIssue)
79
85
})