+16
-2
appview/pulls/pulls.go
+16
-2
appview/pulls/pulls.go
···
43
posthog posthog.Client
44
}
45
46
-
func New(oauth *oauth.OAuth, repoResolver *reporesolver.RepoResolver, pages *pages.Pages, resolver *idresolver.Resolver, db *db.DB, config *appview.Config) *Pulls {
47
-
return &Pulls{oauth: oauth, repoResolver: repoResolver, pages: pages, idResolver: resolver, db: db, config: config}
48
}
49
50
// htmx fragment
···
43
posthog posthog.Client
44
}
45
46
+
func New(
47
+
oauth *oauth.OAuth,
48
+
repoResolver *reporesolver.RepoResolver,
49
+
pages *pages.Pages,
50
+
resolver *idresolver.Resolver,
51
+
db *db.DB,
52
+
config *appview.Config,
53
+
) *Pulls {
54
+
return &Pulls{
55
+
oauth: oauth,
56
+
repoResolver: repoResolver,
57
+
pages: pages,
58
+
idResolver: resolver,
59
+
db: db,
60
+
config: config,
61
+
}
62
}
63
64
// htmx fragment
+100
appview/repo/router.go
+100
appview/repo/router.go
···
···
1
+
package repo
2
+
3
+
import (
4
+
"net/http"
5
+
6
+
"github.com/go-chi/chi/v5"
7
+
"tangled.sh/tangled.sh/core/appview/middleware"
8
+
)
9
+
10
+
func (rp *Repo) Router(mw *middleware.Middleware) http.Handler {
11
+
r := chi.NewRouter()
12
+
r.Get("/", rp.RepoIndex)
13
+
r.Get("/commits/{ref}", rp.RepoLog)
14
+
r.Route("/tree/{ref}", func(r chi.Router) {
15
+
r.Get("/", rp.RepoIndex)
16
+
r.Get("/*", rp.RepoTree)
17
+
})
18
+
r.Get("/commit/{ref}", rp.RepoCommit)
19
+
r.Get("/branches", rp.RepoBranches)
20
+
r.Route("/tags", func(r chi.Router) {
21
+
r.Get("/", rp.RepoTags)
22
+
r.Route("/{tag}", func(r chi.Router) {
23
+
r.Use(middleware.AuthMiddleware(rp.oauth))
24
+
// require auth to download for now
25
+
r.Get("/download/{file}", rp.DownloadArtifact)
26
+
27
+
// require repo:push to upload or delete artifacts
28
+
//
29
+
// additionally: only the uploader can truly delete an artifact
30
+
// (record+blob will live on their pds)
31
+
r.Group(func(r chi.Router) {
32
+
r.With(mw.RepoPermissionMiddleware("repo:push"))
33
+
r.Post("/upload", rp.AttachArtifact)
34
+
r.Delete("/{file}", rp.DeleteArtifact)
35
+
})
36
+
})
37
+
})
38
+
r.Get("/blob/{ref}/*", rp.RepoBlob)
39
+
r.Get("/raw/{ref}/*", rp.RepoBlobRaw)
40
+
41
+
r.Route("/issues", func(r chi.Router) {
42
+
r.With(middleware.Paginate).Get("/", rp.RepoIssues)
43
+
r.Get("/{issue}", rp.RepoSingleIssue)
44
+
45
+
r.Group(func(r chi.Router) {
46
+
r.Use(middleware.AuthMiddleware(rp.oauth))
47
+
r.Get("/new", rp.NewIssue)
48
+
r.Post("/new", rp.NewIssue)
49
+
r.Post("/{issue}/comment", rp.NewIssueComment)
50
+
r.Route("/{issue}/comment/{comment_id}/", func(r chi.Router) {
51
+
r.Get("/", rp.IssueComment)
52
+
r.Delete("/", rp.DeleteIssueComment)
53
+
r.Get("/edit", rp.EditIssueComment)
54
+
r.Post("/edit", rp.EditIssueComment)
55
+
})
56
+
r.Post("/{issue}/close", rp.CloseIssue)
57
+
r.Post("/{issue}/reopen", rp.ReopenIssue)
58
+
})
59
+
})
60
+
61
+
r.Route("/fork", func(r chi.Router) {
62
+
r.Use(middleware.AuthMiddleware(rp.oauth))
63
+
r.Get("/", rp.ForkRepo)
64
+
r.Post("/", rp.ForkRepo)
65
+
r.With(mw.RepoPermissionMiddleware("repo:owner")).Route("/sync", func(r chi.Router) {
66
+
r.Post("/", rp.SyncRepoFork)
67
+
})
68
+
})
69
+
70
+
r.Route("/compare", func(r chi.Router) {
71
+
r.Get("/", rp.RepoCompareNew) // start an new comparison
72
+
73
+
// we have to wildcard here since we want to support GitHub's compare syntax
74
+
// /compare/{ref1}...{ref2}
75
+
// for example:
76
+
// /compare/master...some/feature
77
+
// /compare/master...example.com:another/feature <- this is a fork
78
+
r.Get("/{base}/{head}", rp.RepoCompare)
79
+
r.Get("/*", rp.RepoCompare)
80
+
})
81
+
82
+
// settings routes, needs auth
83
+
r.Group(func(r chi.Router) {
84
+
r.Use(middleware.AuthMiddleware(rp.oauth))
85
+
// repo description can only be edited by owner
86
+
r.With(mw.RepoPermissionMiddleware("repo:owner")).Route("/description", func(r chi.Router) {
87
+
r.Put("/", rp.RepoDescription)
88
+
r.Get("/", rp.RepoDescription)
89
+
r.Get("/edit", rp.RepoDescriptionEdit)
90
+
})
91
+
r.With(mw.RepoPermissionMiddleware("repo:settings")).Route("/settings", func(r chi.Router) {
92
+
r.Get("/", rp.RepoSettings)
93
+
r.With(mw.RepoPermissionMiddleware("repo:invite")).Put("/collaborator", rp.AddCollaborator)
94
+
r.With(mw.RepoPermissionMiddleware("repo:delete")).Delete("/delete", rp.DeleteRepo)
95
+
r.Put("/branches/default", rp.SetDefaultBranch)
96
+
})
97
+
})
98
+
99
+
return r
100
+
}
+38
-38
appview/state/artifact.go
appview/repo/artifact.go
+38
-38
appview/state/artifact.go
appview/repo/artifact.go
···
1
-
package state
2
3
import (
4
"fmt"
···
23
)
24
25
// TODO: proper statuses here on early exit
26
-
func (s *State) AttachArtifact(w http.ResponseWriter, r *http.Request) {
27
-
user := s.oauth.GetUser(r)
28
tagParam := chi.URLParam(r, "tag")
29
-
f, err := s.repoResolver.Resolve(r)
30
if err != nil {
31
log.Println("failed to get repo and knot", err)
32
-
s.pages.Notice(w, "upload", "failed to upload artifact, error in repo resolution")
33
return
34
}
35
36
-
tag, err := s.resolveTag(f, tagParam)
37
if err != nil {
38
log.Println("failed to resolve tag", err)
39
-
s.pages.Notice(w, "upload", "failed to upload artifact, error in tag resolution")
40
return
41
}
42
43
file, handler, err := r.FormFile("artifact")
44
if err != nil {
45
log.Println("failed to upload artifact", err)
46
-
s.pages.Notice(w, "upload", "failed to upload artifact")
47
return
48
}
49
defer file.Close()
50
51
-
client, err := s.oauth.AuthorizedClient(r)
52
if err != nil {
53
log.Println("failed to get authorized client", err)
54
-
s.pages.Notice(w, "upload", "failed to get authorized client")
55
return
56
}
57
58
uploadBlobResp, err := client.RepoUploadBlob(r.Context(), file)
59
if err != nil {
60
log.Println("failed to upload blob", err)
61
-
s.pages.Notice(w, "upload", "Failed to upload blob to your PDS. Try again later.")
62
return
63
}
64
···
83
})
84
if err != nil {
85
log.Println("failed to create record", err)
86
-
s.pages.Notice(w, "upload", "Failed to create artifact record. Try again later.")
87
return
88
}
89
90
log.Println(putRecordResp.Uri)
91
92
-
tx, err := s.db.BeginTx(r.Context(), nil)
93
if err != nil {
94
log.Println("failed to start tx")
95
-
s.pages.Notice(w, "upload", "Failed to create artifact. Try again later.")
96
return
97
}
98
defer tx.Rollback()
···
112
err = db.AddArtifact(tx, artifact)
113
if err != nil {
114
log.Println("failed to add artifact record to db", err)
115
-
s.pages.Notice(w, "upload", "Failed to create artifact. Try again later.")
116
return
117
}
118
119
err = tx.Commit()
120
if err != nil {
121
log.Println("failed to add artifact record to db")
122
-
s.pages.Notice(w, "upload", "Failed to create artifact. Try again later.")
123
return
124
}
125
126
-
s.pages.RepoArtifactFragment(w, pages.RepoArtifactParams{
127
LoggedInUser: user,
128
RepoInfo: f.RepoInfo(user),
129
Artifact: artifact,
···
131
}
132
133
// TODO: proper statuses here on early exit
134
-
func (s *State) DownloadArtifact(w http.ResponseWriter, r *http.Request) {
135
tagParam := chi.URLParam(r, "tag")
136
filename := chi.URLParam(r, "file")
137
-
f, err := s.repoResolver.Resolve(r)
138
if err != nil {
139
log.Println("failed to get repo and knot", err)
140
return
141
}
142
143
-
tag, err := s.resolveTag(f, tagParam)
144
if err != nil {
145
log.Println("failed to resolve tag", err)
146
-
s.pages.Notice(w, "upload", "failed to upload artifact, error in tag resolution")
147
return
148
}
149
150
-
client, err := s.oauth.AuthorizedClient(r)
151
if err != nil {
152
log.Println("failed to get authorized client", err)
153
return
154
}
155
156
artifacts, err := db.GetArtifact(
157
-
s.db,
158
db.FilterEq("repo_at", f.RepoAt),
159
db.FilterEq("tag", tag.Tag.Hash[:]),
160
db.FilterEq("name", filename),
···
181
}
182
183
// TODO: proper statuses here on early exit
184
-
func (s *State) DeleteArtifact(w http.ResponseWriter, r *http.Request) {
185
-
user := s.oauth.GetUser(r)
186
tagParam := chi.URLParam(r, "tag")
187
filename := chi.URLParam(r, "file")
188
-
f, err := s.repoResolver.Resolve(r)
189
if err != nil {
190
log.Println("failed to get repo and knot", err)
191
return
192
}
193
194
-
client, _ := s.oauth.AuthorizedClient(r)
195
196
tag := plumbing.NewHash(tagParam)
197
198
artifacts, err := db.GetArtifact(
199
-
s.db,
200
db.FilterEq("repo_at", f.RepoAt),
201
db.FilterEq("tag", tag[:]),
202
db.FilterEq("name", filename),
203
)
204
if err != nil {
205
log.Println("failed to get artifacts", err)
206
-
s.pages.Notice(w, "remove", "Failed to delete artifact. Try again later.")
207
return
208
}
209
if len(artifacts) != 1 {
210
-
s.pages.Notice(w, "remove", "Unable to find artifact.")
211
return
212
}
213
···
215
216
if user.Did != artifact.Did {
217
log.Println("user not authorized to delete artifact", err)
218
-
s.pages.Notice(w, "remove", "Unauthorized deletion of artifact.")
219
return
220
}
221
···
226
})
227
if err != nil {
228
log.Println("failed to get blob from pds", err)
229
-
s.pages.Notice(w, "remove", "Failed to remove blob from PDS.")
230
return
231
}
232
233
-
tx, err := s.db.BeginTx(r.Context(), nil)
234
if err != nil {
235
log.Println("failed to start tx")
236
-
s.pages.Notice(w, "remove", "Failed to delete artifact. Try again later.")
237
return
238
}
239
defer tx.Rollback()
···
245
)
246
if err != nil {
247
log.Println("failed to remove artifact record from db", err)
248
-
s.pages.Notice(w, "remove", "Failed to delete artifact. Try again later.")
249
return
250
}
251
252
err = tx.Commit()
253
if err != nil {
254
log.Println("failed to remove artifact record from db")
255
-
s.pages.Notice(w, "remove", "Failed to delete artifact. Try again later.")
256
return
257
}
258
259
w.Write([]byte{})
260
}
261
262
-
func (s *State) resolveTag(f *reporesolver.ResolvedRepo, tagParam string) (*types.TagReference, error) {
263
tagParam, err := url.QueryUnescape(tagParam)
264
if err != nil {
265
return nil, err
266
}
267
268
-
us, err := knotclient.NewUnsignedClient(f.Knot, s.config.Core.Dev)
269
if err != nil {
270
return nil, err
271
}
···
1
+
package repo
2
3
import (
4
"fmt"
···
23
)
24
25
// TODO: proper statuses here on early exit
26
+
func (rp *Repo) AttachArtifact(w http.ResponseWriter, r *http.Request) {
27
+
user := rp.oauth.GetUser(r)
28
tagParam := chi.URLParam(r, "tag")
29
+
f, err := rp.repoResolver.Resolve(r)
30
if err != nil {
31
log.Println("failed to get repo and knot", err)
32
+
rp.pages.Notice(w, "upload", "failed to upload artifact, error in repo resolution")
33
return
34
}
35
36
+
tag, err := rp.resolveTag(f, tagParam)
37
if err != nil {
38
log.Println("failed to resolve tag", err)
39
+
rp.pages.Notice(w, "upload", "failed to upload artifact, error in tag resolution")
40
return
41
}
42
43
file, handler, err := r.FormFile("artifact")
44
if err != nil {
45
log.Println("failed to upload artifact", err)
46
+
rp.pages.Notice(w, "upload", "failed to upload artifact")
47
return
48
}
49
defer file.Close()
50
51
+
client, err := rp.oauth.AuthorizedClient(r)
52
if err != nil {
53
log.Println("failed to get authorized client", err)
54
+
rp.pages.Notice(w, "upload", "failed to get authorized client")
55
return
56
}
57
58
uploadBlobResp, err := client.RepoUploadBlob(r.Context(), file)
59
if err != nil {
60
log.Println("failed to upload blob", err)
61
+
rp.pages.Notice(w, "upload", "Failed to upload blob to your PDS. Try again later.")
62
return
63
}
64
···
83
})
84
if err != nil {
85
log.Println("failed to create record", err)
86
+
rp.pages.Notice(w, "upload", "Failed to create artifact record. Try again later.")
87
return
88
}
89
90
log.Println(putRecordResp.Uri)
91
92
+
tx, err := rp.db.BeginTx(r.Context(), nil)
93
if err != nil {
94
log.Println("failed to start tx")
95
+
rp.pages.Notice(w, "upload", "Failed to create artifact. Try again later.")
96
return
97
}
98
defer tx.Rollback()
···
112
err = db.AddArtifact(tx, artifact)
113
if err != nil {
114
log.Println("failed to add artifact record to db", err)
115
+
rp.pages.Notice(w, "upload", "Failed to create artifact. Try again later.")
116
return
117
}
118
119
err = tx.Commit()
120
if err != nil {
121
log.Println("failed to add artifact record to db")
122
+
rp.pages.Notice(w, "upload", "Failed to create artifact. Try again later.")
123
return
124
}
125
126
+
rp.pages.RepoArtifactFragment(w, pages.RepoArtifactParams{
127
LoggedInUser: user,
128
RepoInfo: f.RepoInfo(user),
129
Artifact: artifact,
···
131
}
132
133
// TODO: proper statuses here on early exit
134
+
func (rp *Repo) DownloadArtifact(w http.ResponseWriter, r *http.Request) {
135
tagParam := chi.URLParam(r, "tag")
136
filename := chi.URLParam(r, "file")
137
+
f, err := rp.repoResolver.Resolve(r)
138
if err != nil {
139
log.Println("failed to get repo and knot", err)
140
return
141
}
142
143
+
tag, err := rp.resolveTag(f, tagParam)
144
if err != nil {
145
log.Println("failed to resolve tag", err)
146
+
rp.pages.Notice(w, "upload", "failed to upload artifact, error in tag resolution")
147
return
148
}
149
150
+
client, err := rp.oauth.AuthorizedClient(r)
151
if err != nil {
152
log.Println("failed to get authorized client", err)
153
return
154
}
155
156
artifacts, err := db.GetArtifact(
157
+
rp.db,
158
db.FilterEq("repo_at", f.RepoAt),
159
db.FilterEq("tag", tag.Tag.Hash[:]),
160
db.FilterEq("name", filename),
···
181
}
182
183
// TODO: proper statuses here on early exit
184
+
func (rp *Repo) DeleteArtifact(w http.ResponseWriter, r *http.Request) {
185
+
user := rp.oauth.GetUser(r)
186
tagParam := chi.URLParam(r, "tag")
187
filename := chi.URLParam(r, "file")
188
+
f, err := rp.repoResolver.Resolve(r)
189
if err != nil {
190
log.Println("failed to get repo and knot", err)
191
return
192
}
193
194
+
client, _ := rp.oauth.AuthorizedClient(r)
195
196
tag := plumbing.NewHash(tagParam)
197
198
artifacts, err := db.GetArtifact(
199
+
rp.db,
200
db.FilterEq("repo_at", f.RepoAt),
201
db.FilterEq("tag", tag[:]),
202
db.FilterEq("name", filename),
203
)
204
if err != nil {
205
log.Println("failed to get artifacts", err)
206
+
rp.pages.Notice(w, "remove", "Failed to delete artifact. Try again later.")
207
return
208
}
209
if len(artifacts) != 1 {
210
+
rp.pages.Notice(w, "remove", "Unable to find artifact.")
211
return
212
}
213
···
215
216
if user.Did != artifact.Did {
217
log.Println("user not authorized to delete artifact", err)
218
+
rp.pages.Notice(w, "remove", "Unauthorized deletion of artifact.")
219
return
220
}
221
···
226
})
227
if err != nil {
228
log.Println("failed to get blob from pds", err)
229
+
rp.pages.Notice(w, "remove", "Failed to remove blob from PDS.")
230
return
231
}
232
233
+
tx, err := rp.db.BeginTx(r.Context(), nil)
234
if err != nil {
235
log.Println("failed to start tx")
236
+
rp.pages.Notice(w, "remove", "Failed to delete artifact. Try again later.")
237
return
238
}
239
defer tx.Rollback()
···
245
)
246
if err != nil {
247
log.Println("failed to remove artifact record from db", err)
248
+
rp.pages.Notice(w, "remove", "Failed to delete artifact. Try again later.")
249
return
250
}
251
252
err = tx.Commit()
253
if err != nil {
254
log.Println("failed to remove artifact record from db")
255
+
rp.pages.Notice(w, "remove", "Failed to delete artifact. Try again later.")
256
return
257
}
258
259
w.Write([]byte{})
260
}
261
262
+
func (rp *Repo) resolveTag(f *reporesolver.ResolvedRepo, tagParam string) (*types.TagReference, error) {
263
tagParam, err := url.QueryUnescape(tagParam)
264
if err != nil {
265
return nil, err
266
}
267
268
+
us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev)
269
if err != nil {
270
return nil, err
271
}
+295
-261
appview/state/repo.go
appview/repo/repo.go
+295
-261
appview/state/repo.go
appview/repo/repo.go
···
1
-
package state
2
3
import (
4
"database/sql"
···
19
"tangled.sh/tangled.sh/core/api/tangled"
20
"tangled.sh/tangled.sh/core/appview"
21
"tangled.sh/tangled.sh/core/appview/db"
22
"tangled.sh/tangled.sh/core/appview/oauth"
23
"tangled.sh/tangled.sh/core/appview/pages"
24
"tangled.sh/tangled.sh/core/appview/pages/markup"
···
27
"tangled.sh/tangled.sh/core/appview/reporesolver"
28
"tangled.sh/tangled.sh/core/knotclient"
29
"tangled.sh/tangled.sh/core/patchutil"
30
"tangled.sh/tangled.sh/core/types"
31
32
"github.com/bluesky-social/indigo/atproto/data"
···
39
lexutil "github.com/bluesky-social/indigo/lex/util"
40
)
41
42
-
func (s *State) RepoIndex(w http.ResponseWriter, r *http.Request) {
43
ref := chi.URLParam(r, "ref")
44
-
f, err := s.repoResolver.Resolve(r)
45
if err != nil {
46
log.Println("failed to fully resolve repo", err)
47
return
48
}
49
50
-
us, err := knotclient.NewUnsignedClient(f.Knot, s.config.Core.Dev)
51
if err != nil {
52
log.Printf("failed to create unsigned client for %s", f.Knot)
53
-
s.pages.Error503(w)
54
return
55
}
56
57
result, err := us.Index(f.OwnerDid(), f.RepoName, ref)
58
if err != nil {
59
-
s.pages.Error503(w)
60
log.Println("failed to reach knotserver", err)
61
return
62
}
···
107
108
emails := uniqueEmails(commitsTrunc)
109
110
-
user := s.oauth.GetUser(r)
111
repoInfo := f.RepoInfo(user)
112
113
-
secret, err := db.GetRegistrationKey(s.db, f.Knot)
114
if err != nil {
115
log.Printf("failed to get registration key for %s: %s", f.Knot, err)
116
-
s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
117
}
118
119
-
signedClient, err := knotclient.NewSignedClient(f.Knot, secret, s.config.Core.Dev)
120
if err != nil {
121
log.Printf("failed to create signed client for %s: %s", f.Knot, err)
122
return
···
124
125
var forkInfo *types.ForkInfo
126
if user != nil && (repoInfo.Roles.IsOwner() || repoInfo.Roles.IsCollaborator()) {
127
-
forkInfo, err = getForkInfo(repoInfo, s, f, user, signedClient)
128
if err != nil {
129
log.Printf("Failed to fetch fork information: %v", err)
130
return
···
137
// non-fatal
138
}
139
140
-
s.pages.RepoIndexPage(w, pages.RepoIndexParams{
141
LoggedInUser: user,
142
RepoInfo: repoInfo,
143
TagMap: tagMap,
···
146
TagsTrunc: tagsTrunc,
147
ForkInfo: forkInfo,
148
BranchesTrunc: branchesTrunc,
149
-
EmailToDidOrHandle: EmailToDidOrHandle(s, emails),
150
Languages: repoLanguages,
151
})
152
return
···
154
155
func getForkInfo(
156
repoInfo repoinfo.RepoInfo,
157
-
s *State,
158
f *reporesolver.ResolvedRepo,
159
user *oauth.User,
160
signedClient *knotclient.SignedClient,
···
173
return &forkInfo, nil
174
}
175
176
-
us, err := knotclient.NewUnsignedClient(repoInfo.Source.Knot, s.config.Core.Dev)
177
if err != nil {
178
log.Printf("failed to create unsigned client for %s", repoInfo.Source.Knot)
179
return nil, err
···
216
return &forkInfo, nil
217
}
218
219
-
func (s *State) RepoLog(w http.ResponseWriter, r *http.Request) {
220
-
f, err := s.repoResolver.Resolve(r)
221
if err != nil {
222
log.Println("failed to fully resolve repo", err)
223
return
···
233
234
ref := chi.URLParam(r, "ref")
235
236
-
us, err := knotclient.NewUnsignedClient(f.Knot, s.config.Core.Dev)
237
if err != nil {
238
log.Println("failed to create unsigned client", err)
239
return
···
260
tagMap[hash] = append(tagMap[hash], tag.Name)
261
}
262
263
-
user := s.oauth.GetUser(r)
264
-
s.pages.RepoLog(w, pages.RepoLogParams{
265
LoggedInUser: user,
266
TagMap: tagMap,
267
RepoInfo: f.RepoInfo(user),
268
RepoLogResponse: *repolog,
269
-
EmailToDidOrHandle: EmailToDidOrHandle(s, uniqueEmails(repolog.Commits)),
270
})
271
return
272
}
273
274
-
func (s *State) RepoDescriptionEdit(w http.ResponseWriter, r *http.Request) {
275
-
f, err := s.repoResolver.Resolve(r)
276
if err != nil {
277
log.Println("failed to get repo and knot", err)
278
w.WriteHeader(http.StatusBadRequest)
279
return
280
}
281
282
-
user := s.oauth.GetUser(r)
283
-
s.pages.EditRepoDescriptionFragment(w, pages.RepoDescriptionParams{
284
RepoInfo: f.RepoInfo(user),
285
})
286
return
287
}
288
289
-
func (s *State) RepoDescription(w http.ResponseWriter, r *http.Request) {
290
-
f, err := s.repoResolver.Resolve(r)
291
if err != nil {
292
log.Println("failed to get repo and knot", err)
293
w.WriteHeader(http.StatusBadRequest)
···
302
return
303
}
304
305
-
user := s.oauth.GetUser(r)
306
307
switch r.Method {
308
case http.MethodGet:
309
-
s.pages.RepoDescriptionFragment(w, pages.RepoDescriptionParams{
310
RepoInfo: f.RepoInfo(user),
311
})
312
return
313
case http.MethodPut:
314
-
user := s.oauth.GetUser(r)
315
newDescription := r.FormValue("description")
316
-
client, err := s.oauth.AuthorizedClient(r)
317
if err != nil {
318
log.Println("failed to get client")
319
-
s.pages.Notice(w, "repo-notice", "Failed to update description, try again later.")
320
return
321
}
322
323
// optimistic update
324
-
err = db.UpdateDescription(s.db, string(repoAt), newDescription)
325
if err != nil {
326
log.Println("failed to perferom update-description query", err)
327
-
s.pages.Notice(w, "repo-notice", "Failed to update description, try again later.")
328
return
329
}
330
···
334
ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoNSID, user.Did, rkey)
335
if err != nil {
336
// failed to get record
337
-
s.pages.Notice(w, "repo-notice", "Failed to update description, no record found on PDS.")
338
return
339
}
340
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
···
356
if err != nil {
357
log.Println("failed to perferom update-description query", err)
358
// failed to get record
359
-
s.pages.Notice(w, "repo-notice", "Failed to update description, unable to save to PDS.")
360
return
361
}
362
363
newRepoInfo := f.RepoInfo(user)
364
newRepoInfo.Description = newDescription
365
366
-
s.pages.RepoDescriptionFragment(w, pages.RepoDescriptionParams{
367
RepoInfo: newRepoInfo,
368
})
369
return
370
}
371
}
372
373
-
func (s *State) RepoCommit(w http.ResponseWriter, r *http.Request) {
374
-
f, err := s.repoResolver.Resolve(r)
375
if err != nil {
376
log.Println("failed to fully resolve repo", err)
377
return
378
}
379
ref := chi.URLParam(r, "ref")
380
protocol := "http"
381
-
if !s.config.Core.Dev {
382
protocol = "https"
383
}
384
385
if !plumbing.IsHash(ref) {
386
-
s.pages.Error404(w)
387
return
388
}
389
···
406
return
407
}
408
409
-
user := s.oauth.GetUser(r)
410
-
s.pages.RepoCommit(w, pages.RepoCommitParams{
411
LoggedInUser: user,
412
RepoInfo: f.RepoInfo(user),
413
RepoCommitResponse: result,
414
-
EmailToDidOrHandle: EmailToDidOrHandle(s, []string{result.Diff.Commit.Author.Email}),
415
})
416
return
417
}
418
419
-
func (s *State) RepoTree(w http.ResponseWriter, r *http.Request) {
420
-
f, err := s.repoResolver.Resolve(r)
421
if err != nil {
422
log.Println("failed to fully resolve repo", err)
423
return
···
426
ref := chi.URLParam(r, "ref")
427
treePath := chi.URLParam(r, "*")
428
protocol := "http"
429
-
if !s.config.Core.Dev {
430
protocol = "https"
431
}
432
resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/tree/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, treePath))
···
455
return
456
}
457
458
-
user := s.oauth.GetUser(r)
459
460
var breadcrumbs [][]string
461
breadcrumbs = append(breadcrumbs, []string{f.RepoName, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), ref)})
···
468
baseTreeLink := path.Join(f.OwnerSlashRepo(), "tree", ref, treePath)
469
baseBlobLink := path.Join(f.OwnerSlashRepo(), "blob", ref, treePath)
470
471
-
s.pages.RepoTree(w, pages.RepoTreeParams{
472
LoggedInUser: user,
473
BreadCrumbs: breadcrumbs,
474
BaseTreeLink: baseTreeLink,
···
479
return
480
}
481
482
-
func (s *State) RepoTags(w http.ResponseWriter, r *http.Request) {
483
-
f, err := s.repoResolver.Resolve(r)
484
if err != nil {
485
log.Println("failed to get repo and knot", err)
486
return
487
}
488
489
-
us, err := knotclient.NewUnsignedClient(f.Knot, s.config.Core.Dev)
490
if err != nil {
491
log.Println("failed to create unsigned client", err)
492
return
···
498
return
499
}
500
501
-
artifacts, err := db.GetArtifact(s.db, db.FilterEq("repo_at", f.RepoAt))
502
if err != nil {
503
log.Println("failed grab artifacts", err)
504
return
···
526
}
527
}
528
529
-
user := s.oauth.GetUser(r)
530
-
s.pages.RepoTags(w, pages.RepoTagsParams{
531
LoggedInUser: user,
532
RepoInfo: f.RepoInfo(user),
533
RepoTagsResponse: *result,
···
537
return
538
}
539
540
-
func (s *State) RepoBranches(w http.ResponseWriter, r *http.Request) {
541
-
f, err := s.repoResolver.Resolve(r)
542
if err != nil {
543
log.Println("failed to get repo and knot", err)
544
return
545
}
546
547
-
us, err := knotclient.NewUnsignedClient(f.Knot, s.config.Core.Dev)
548
if err != nil {
549
log.Println("failed to create unsigned client", err)
550
return
···
573
return strings.Compare(a.Name, b.Name) * -1
574
})
575
576
-
user := s.oauth.GetUser(r)
577
-
s.pages.RepoBranches(w, pages.RepoBranchesParams{
578
LoggedInUser: user,
579
RepoInfo: f.RepoInfo(user),
580
RepoBranchesResponse: *result,
···
582
return
583
}
584
585
-
func (s *State) RepoBlob(w http.ResponseWriter, r *http.Request) {
586
-
f, err := s.repoResolver.Resolve(r)
587
if err != nil {
588
log.Println("failed to get repo and knot", err)
589
return
···
592
ref := chi.URLParam(r, "ref")
593
filePath := chi.URLParam(r, "*")
594
protocol := "http"
595
-
if !s.config.Core.Dev {
596
protocol = "https"
597
}
598
resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath))
···
630
showRendered = r.URL.Query().Get("code") != "true"
631
}
632
633
-
user := s.oauth.GetUser(r)
634
-
s.pages.RepoBlob(w, pages.RepoBlobParams{
635
LoggedInUser: user,
636
RepoInfo: f.RepoInfo(user),
637
RepoBlobResponse: result,
···
642
return
643
}
644
645
-
func (s *State) RepoBlobRaw(w http.ResponseWriter, r *http.Request) {
646
-
f, err := s.repoResolver.Resolve(r)
647
if err != nil {
648
log.Println("failed to get repo and knot", err)
649
return
···
653
filePath := chi.URLParam(r, "*")
654
655
protocol := "http"
656
-
if !s.config.Core.Dev {
657
protocol = "https"
658
}
659
resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath))
···
686
return
687
}
688
689
-
func (s *State) AddCollaborator(w http.ResponseWriter, r *http.Request) {
690
-
f, err := s.repoResolver.Resolve(r)
691
if err != nil {
692
log.Println("failed to get repo and knot", err)
693
return
···
699
return
700
}
701
702
-
collaboratorIdent, err := s.idResolver.ResolveIdent(r.Context(), collaborator)
703
if err != nil {
704
w.Write([]byte("failed to resolve collaborator did to a handle"))
705
return
···
708
709
// TODO: create an atproto record for this
710
711
-
secret, err := db.GetRegistrationKey(s.db, f.Knot)
712
if err != nil {
713
log.Printf("no key found for domain %s: %s\n", f.Knot, err)
714
return
715
}
716
717
-
ksClient, err := knotclient.NewSignedClient(f.Knot, secret, s.config.Core.Dev)
718
if err != nil {
719
log.Println("failed to create client to ", f.Knot)
720
return
···
731
return
732
}
733
734
-
tx, err := s.db.BeginTx(r.Context(), nil)
735
if err != nil {
736
log.Println("failed to start tx")
737
w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err)))
···
739
}
740
defer func() {
741
tx.Rollback()
742
-
err = s.enforcer.E.LoadPolicy()
743
if err != nil {
744
log.Println("failed to rollback policies")
745
}
746
}()
747
748
-
err = s.enforcer.AddCollaborator(collaboratorIdent.DID.String(), f.Knot, f.DidSlashRepo())
749
if err != nil {
750
w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err)))
751
return
752
}
753
754
-
err = db.AddCollaborator(s.db, collaboratorIdent.DID.String(), f.OwnerDid(), f.RepoName, f.Knot)
755
if err != nil {
756
w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err)))
757
return
···
764
return
765
}
766
767
-
err = s.enforcer.E.SavePolicy()
768
if err != nil {
769
log.Println("failed to update ACLs", err)
770
http.Error(w, err.Error(), http.StatusInternalServerError)
···
775
776
}
777
778
-
func (s *State) DeleteRepo(w http.ResponseWriter, r *http.Request) {
779
-
user := s.oauth.GetUser(r)
780
781
-
f, err := s.repoResolver.Resolve(r)
782
if err != nil {
783
log.Println("failed to get repo and knot", err)
784
return
785
}
786
787
// remove record from pds
788
-
xrpcClient, err := s.oauth.AuthorizedClient(r)
789
if err != nil {
790
log.Println("failed to get authorized client", err)
791
return
···
798
})
799
if err != nil {
800
log.Printf("failed to delete record: %s", err)
801
-
s.pages.Notice(w, "settings-delete", "Failed to delete repository from PDS.")
802
return
803
}
804
log.Println("removed repo record ", f.RepoAt.String())
805
806
-
secret, err := db.GetRegistrationKey(s.db, f.Knot)
807
if err != nil {
808
log.Printf("no key found for domain %s: %s\n", f.Knot, err)
809
return
810
}
811
812
-
ksClient, err := knotclient.NewSignedClient(f.Knot, secret, s.config.Core.Dev)
813
if err != nil {
814
log.Println("failed to create client to ", f.Knot)
815
return
···
827
log.Println("removed repo from knot ", f.Knot)
828
}
829
830
-
tx, err := s.db.BeginTx(r.Context(), nil)
831
if err != nil {
832
log.Println("failed to start tx")
833
w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err)))
···
835
}
836
defer func() {
837
tx.Rollback()
838
-
err = s.enforcer.E.LoadPolicy()
839
if err != nil {
840
log.Println("failed to rollback policies")
841
}
842
}()
843
844
// remove collaborator RBAC
845
-
repoCollaborators, err := s.enforcer.E.GetImplicitUsersForResourceByDomain(f.DidSlashRepo(), f.Knot)
846
if err != nil {
847
-
s.pages.Notice(w, "settings-delete", "Failed to remove collaborators")
848
return
849
}
850
for _, c := range repoCollaborators {
851
did := c[0]
852
-
s.enforcer.RemoveCollaborator(did, f.Knot, f.DidSlashRepo())
853
}
854
log.Println("removed collaborators")
855
856
// remove repo RBAC
857
-
err = s.enforcer.RemoveRepo(f.OwnerDid(), f.Knot, f.DidSlashRepo())
858
if err != nil {
859
-
s.pages.Notice(w, "settings-delete", "Failed to update RBAC rules")
860
return
861
}
862
863
// remove repo from db
864
err = db.RemoveRepo(tx, f.OwnerDid(), f.RepoName)
865
if err != nil {
866
-
s.pages.Notice(w, "settings-delete", "Failed to update appview")
867
return
868
}
869
log.Println("removed repo from db")
···
875
return
876
}
877
878
-
err = s.enforcer.E.SavePolicy()
879
if err != nil {
880
log.Println("failed to update ACLs", err)
881
http.Error(w, err.Error(), http.StatusInternalServerError)
882
return
883
}
884
885
-
s.pages.HxRedirect(w, fmt.Sprintf("/%s", f.OwnerDid()))
886
}
887
888
-
func (s *State) SetDefaultBranch(w http.ResponseWriter, r *http.Request) {
889
-
f, err := s.repoResolver.Resolve(r)
890
if err != nil {
891
log.Println("failed to get repo and knot", err)
892
return
···
898
return
899
}
900
901
-
secret, err := db.GetRegistrationKey(s.db, f.Knot)
902
if err != nil {
903
log.Printf("no key found for domain %s: %s\n", f.Knot, err)
904
return
905
}
906
907
-
ksClient, err := knotclient.NewSignedClient(f.Knot, secret, s.config.Core.Dev)
908
if err != nil {
909
log.Println("failed to create client to ", f.Knot)
910
return
···
917
}
918
919
if ksResp.StatusCode != http.StatusNoContent {
920
-
s.pages.Notice(w, "repo-settings", "Failed to set default branch. Try again later.")
921
return
922
}
923
924
w.Write([]byte(fmt.Sprint("default branch set to: ", branch)))
925
}
926
927
-
func (s *State) RepoSettings(w http.ResponseWriter, r *http.Request) {
928
-
f, err := s.repoResolver.Resolve(r)
929
if err != nil {
930
log.Println("failed to get repo and knot", err)
931
return
···
934
switch r.Method {
935
case http.MethodGet:
936
// for now, this is just pubkeys
937
-
user := s.oauth.GetUser(r)
938
repoCollaborators, err := f.Collaborators(r.Context())
939
if err != nil {
940
log.Println("failed to get collaborators", err)
···
942
943
isCollaboratorInviteAllowed := false
944
if user != nil {
945
-
ok, err := s.enforcer.IsCollaboratorInviteAllowed(user.Did, f.Knot, f.DidSlashRepo())
946
if err == nil && ok {
947
isCollaboratorInviteAllowed = true
948
}
949
}
950
951
-
us, err := knotclient.NewUnsignedClient(f.Knot, s.config.Core.Dev)
952
if err != nil {
953
log.Println("failed to create unsigned client", err)
954
return
···
960
return
961
}
962
963
-
s.pages.RepoSettings(w, pages.RepoSettingsParams{
964
LoggedInUser: user,
965
RepoInfo: f.RepoInfo(user),
966
Collaborators: repoCollaborators,
···
970
}
971
}
972
973
-
func (s *State) RepoSingleIssue(w http.ResponseWriter, r *http.Request) {
974
-
user := s.oauth.GetUser(r)
975
-
f, err := s.repoResolver.Resolve(r)
976
if err != nil {
977
log.Println("failed to get repo and knot", err)
978
return
···
986
return
987
}
988
989
-
issue, comments, err := db.GetIssueWithComments(s.db, f.RepoAt, issueIdInt)
990
if err != nil {
991
log.Println("failed to get issue and comments", err)
992
-
s.pages.Notice(w, "issues", "Failed to load issue. Try again later.")
993
return
994
}
995
996
-
issueOwnerIdent, err := s.idResolver.ResolveIdent(r.Context(), issue.OwnerDid)
997
if err != nil {
998
log.Println("failed to resolve issue owner", err)
999
}
···
1002
for i, comment := range comments {
1003
identsToResolve[i] = comment.OwnerDid
1004
}
1005
-
resolvedIds := s.idResolver.ResolveIdents(r.Context(), identsToResolve)
1006
didHandleMap := make(map[string]string)
1007
for _, identity := range resolvedIds {
1008
if !identity.Handle.IsInvalidHandle() {
···
1012
}
1013
}
1014
1015
-
s.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{
1016
LoggedInUser: user,
1017
RepoInfo: f.RepoInfo(user),
1018
Issue: *issue,
···
1024
1025
}
1026
1027
-
func (s *State) CloseIssue(w http.ResponseWriter, r *http.Request) {
1028
-
user := s.oauth.GetUser(r)
1029
-
f, err := s.repoResolver.Resolve(r)
1030
if err != nil {
1031
log.Println("failed to get repo and knot", err)
1032
return
···
1040
return
1041
}
1042
1043
-
issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt)
1044
if err != nil {
1045
log.Println("failed to get issue", err)
1046
-
s.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.")
1047
return
1048
}
1049
···
1061
1062
closed := tangled.RepoIssueStateClosed
1063
1064
-
client, err := s.oauth.AuthorizedClient(r)
1065
if err != nil {
1066
log.Println("failed to get authorized client", err)
1067
return
···
1080
1081
if err != nil {
1082
log.Println("failed to update issue state", err)
1083
-
s.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.")
1084
return
1085
}
1086
1087
-
err = db.CloseIssue(s.db, f.RepoAt, issueIdInt)
1088
if err != nil {
1089
log.Println("failed to close issue", err)
1090
-
s.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.")
1091
return
1092
}
1093
1094
-
s.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issueIdInt))
1095
return
1096
} else {
1097
log.Println("user is not permitted to close issue")
···
1100
}
1101
}
1102
1103
-
func (s *State) ReopenIssue(w http.ResponseWriter, r *http.Request) {
1104
-
user := s.oauth.GetUser(r)
1105
-
f, err := s.repoResolver.Resolve(r)
1106
if err != nil {
1107
log.Println("failed to get repo and knot", err)
1108
return
···
1116
return
1117
}
1118
1119
-
issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt)
1120
if err != nil {
1121
log.Println("failed to get issue", err)
1122
-
s.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.")
1123
return
1124
}
1125
···
1133
isIssueOwner := user.Did == issue.OwnerDid
1134
1135
if isCollaborator || isIssueOwner {
1136
-
err := db.ReopenIssue(s.db, f.RepoAt, issueIdInt)
1137
if err != nil {
1138
log.Println("failed to reopen issue", err)
1139
-
s.pages.Notice(w, "issue-action", "Failed to reopen issue. Try again later.")
1140
return
1141
}
1142
-
s.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issueIdInt))
1143
return
1144
} else {
1145
log.Println("user is not the owner of the repo")
···
1148
}
1149
}
1150
1151
-
func (s *State) NewIssueComment(w http.ResponseWriter, r *http.Request) {
1152
-
user := s.oauth.GetUser(r)
1153
-
f, err := s.repoResolver.Resolve(r)
1154
if err != nil {
1155
log.Println("failed to get repo and knot", err)
1156
return
···
1168
case http.MethodPost:
1169
body := r.FormValue("body")
1170
if body == "" {
1171
-
s.pages.Notice(w, "issue", "Body is required")
1172
return
1173
}
1174
1175
commentId := mathrand.IntN(1000000)
1176
rkey := appview.TID()
1177
1178
-
err := db.NewIssueComment(s.db, &db.Comment{
1179
OwnerDid: user.Did,
1180
RepoAt: f.RepoAt,
1181
Issue: issueIdInt,
···
1185
})
1186
if err != nil {
1187
log.Println("failed to create comment", err)
1188
-
s.pages.Notice(w, "issue-comment", "Failed to create comment.")
1189
return
1190
}
1191
1192
createdAt := time.Now().Format(time.RFC3339)
1193
commentIdInt64 := int64(commentId)
1194
ownerDid := user.Did
1195
-
issueAt, err := db.GetIssueAt(s.db, f.RepoAt, issueIdInt)
1196
if err != nil {
1197
log.Println("failed to get issue at", err)
1198
-
s.pages.Notice(w, "issue-comment", "Failed to create comment.")
1199
return
1200
}
1201
1202
atUri := f.RepoAt.String()
1203
-
client, err := s.oauth.AuthorizedClient(r)
1204
if err != nil {
1205
log.Println("failed to get authorized client", err)
1206
-
s.pages.Notice(w, "issue-comment", "Failed to create comment.")
1207
return
1208
}
1209
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
···
1223
})
1224
if err != nil {
1225
log.Println("failed to create comment", err)
1226
-
s.pages.Notice(w, "issue-comment", "Failed to create comment.")
1227
return
1228
}
1229
1230
-
s.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d#comment-%d", f.OwnerSlashRepo(), issueIdInt, commentId))
1231
return
1232
}
1233
}
1234
1235
-
func (s *State) IssueComment(w http.ResponseWriter, r *http.Request) {
1236
-
user := s.oauth.GetUser(r)
1237
-
f, err := s.repoResolver.Resolve(r)
1238
if err != nil {
1239
log.Println("failed to get repo and knot", err)
1240
return
···
1256
return
1257
}
1258
1259
-
issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt)
1260
if err != nil {
1261
log.Println("failed to get issue", err)
1262
-
s.pages.Notice(w, "issues", "Failed to load issue. Try again later.")
1263
return
1264
}
1265
1266
-
comment, err := db.GetComment(s.db, f.RepoAt, issueIdInt, commentIdInt)
1267
if err != nil {
1268
http.Error(w, "bad comment id", http.StatusBadRequest)
1269
return
1270
}
1271
1272
-
identity, err := s.idResolver.ResolveIdent(r.Context(), comment.OwnerDid)
1273
if err != nil {
1274
log.Println("failed to resolve did")
1275
return
···
1282
didHandleMap[identity.DID.String()] = identity.DID.String()
1283
}
1284
1285
-
s.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
1286
LoggedInUser: user,
1287
RepoInfo: f.RepoInfo(user),
1288
DidHandleMap: didHandleMap,
···
1291
})
1292
}
1293
1294
-
func (s *State) EditIssueComment(w http.ResponseWriter, r *http.Request) {
1295
-
user := s.oauth.GetUser(r)
1296
-
f, err := s.repoResolver.Resolve(r)
1297
if err != nil {
1298
log.Println("failed to get repo and knot", err)
1299
return
···
1315
return
1316
}
1317
1318
-
issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt)
1319
if err != nil {
1320
log.Println("failed to get issue", err)
1321
-
s.pages.Notice(w, "issues", "Failed to load issue. Try again later.")
1322
return
1323
}
1324
1325
-
comment, err := db.GetComment(s.db, f.RepoAt, issueIdInt, commentIdInt)
1326
if err != nil {
1327
http.Error(w, "bad comment id", http.StatusBadRequest)
1328
return
···
1335
1336
switch r.Method {
1337
case http.MethodGet:
1338
-
s.pages.EditIssueCommentFragment(w, pages.EditIssueCommentParams{
1339
LoggedInUser: user,
1340
RepoInfo: f.RepoInfo(user),
1341
Issue: issue,
···
1344
case http.MethodPost:
1345
// extract form value
1346
newBody := r.FormValue("body")
1347
-
client, err := s.oauth.AuthorizedClient(r)
1348
if err != nil {
1349
log.Println("failed to get authorized client", err)
1350
-
s.pages.Notice(w, "issue-comment", "Failed to create comment.")
1351
return
1352
}
1353
rkey := comment.Rkey
1354
1355
// optimistic update
1356
edited := time.Now()
1357
-
err = db.EditComment(s.db, comment.RepoAt, comment.Issue, comment.CommentId, newBody)
1358
if err != nil {
1359
log.Println("failed to perferom update-description query", err)
1360
-
s.pages.Notice(w, "repo-notice", "Failed to update description, try again later.")
1361
return
1362
}
1363
···
1368
if err != nil {
1369
// failed to get record
1370
log.Println(err, rkey)
1371
-
s.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "Failed to update description, no record found on PDS.")
1372
return
1373
}
1374
value, _ := ex.Value.MarshalJSON() // we just did get record; it is valid json
···
1408
comment.Edited = &edited
1409
1410
// return new comment body with htmx
1411
-
s.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
1412
LoggedInUser: user,
1413
RepoInfo: f.RepoInfo(user),
1414
DidHandleMap: didHandleMap,
···
1421
1422
}
1423
1424
-
func (s *State) DeleteIssueComment(w http.ResponseWriter, r *http.Request) {
1425
-
user := s.oauth.GetUser(r)
1426
-
f, err := s.repoResolver.Resolve(r)
1427
if err != nil {
1428
log.Println("failed to get repo and knot", err)
1429
return
···
1437
return
1438
}
1439
1440
-
issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt)
1441
if err != nil {
1442
log.Println("failed to get issue", err)
1443
-
s.pages.Notice(w, "issues", "Failed to load issue. Try again later.")
1444
return
1445
}
1446
···
1452
return
1453
}
1454
1455
-
comment, err := db.GetComment(s.db, f.RepoAt, issueIdInt, commentIdInt)
1456
if err != nil {
1457
http.Error(w, "bad comment id", http.StatusBadRequest)
1458
return
···
1470
1471
// optimistic deletion
1472
deleted := time.Now()
1473
-
err = db.DeleteComment(s.db, f.RepoAt, issueIdInt, commentIdInt)
1474
if err != nil {
1475
log.Println("failed to delete comment")
1476
-
s.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "failed to delete comment")
1477
return
1478
}
1479
1480
// delete from pds
1481
if comment.Rkey != "" {
1482
-
client, err := s.oauth.AuthorizedClient(r)
1483
if err != nil {
1484
log.Println("failed to get authorized client", err)
1485
-
s.pages.Notice(w, "issue-comment", "Failed to delete comment.")
1486
return
1487
}
1488
_, err = client.RepoDeleteRecord(r.Context(), &comatproto.RepoDeleteRecord_Input{
···
1503
comment.Deleted = &deleted
1504
1505
// htmx fragment of comment after deletion
1506
-
s.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
1507
LoggedInUser: user,
1508
RepoInfo: f.RepoInfo(user),
1509
DidHandleMap: didHandleMap,
···
1513
return
1514
}
1515
1516
-
func (s *State) RepoIssues(w http.ResponseWriter, r *http.Request) {
1517
params := r.URL.Query()
1518
state := params.Get("state")
1519
isOpen := true
···
1532
page = pagination.FirstPage()
1533
}
1534
1535
-
user := s.oauth.GetUser(r)
1536
-
f, err := s.repoResolver.Resolve(r)
1537
if err != nil {
1538
log.Println("failed to get repo and knot", err)
1539
return
1540
}
1541
1542
-
issues, err := db.GetIssues(s.db, f.RepoAt, isOpen, page)
1543
if err != nil {
1544
log.Println("failed to get issues", err)
1545
-
s.pages.Notice(w, "issues", "Failed to load issues. Try again later.")
1546
return
1547
}
1548
···
1550
for i, issue := range issues {
1551
identsToResolve[i] = issue.OwnerDid
1552
}
1553
-
resolvedIds := s.idResolver.ResolveIdents(r.Context(), identsToResolve)
1554
didHandleMap := make(map[string]string)
1555
for _, identity := range resolvedIds {
1556
if !identity.Handle.IsInvalidHandle() {
···
1560
}
1561
}
1562
1563
-
s.pages.RepoIssues(w, pages.RepoIssuesParams{
1564
-
LoggedInUser: s.oauth.GetUser(r),
1565
RepoInfo: f.RepoInfo(user),
1566
Issues: issues,
1567
DidHandleMap: didHandleMap,
···
1571
return
1572
}
1573
1574
-
func (s *State) NewIssue(w http.ResponseWriter, r *http.Request) {
1575
-
user := s.oauth.GetUser(r)
1576
1577
-
f, err := s.repoResolver.Resolve(r)
1578
if err != nil {
1579
log.Println("failed to get repo and knot", err)
1580
return
···
1582
1583
switch r.Method {
1584
case http.MethodGet:
1585
-
s.pages.RepoNewIssue(w, pages.RepoNewIssueParams{
1586
LoggedInUser: user,
1587
RepoInfo: f.RepoInfo(user),
1588
})
···
1591
body := r.FormValue("body")
1592
1593
if title == "" || body == "" {
1594
-
s.pages.Notice(w, "issues", "Title and body are required")
1595
return
1596
}
1597
1598
-
tx, err := s.db.BeginTx(r.Context(), nil)
1599
if err != nil {
1600
-
s.pages.Notice(w, "issues", "Failed to create issue, try again later")
1601
return
1602
}
1603
···
1609
})
1610
if err != nil {
1611
log.Println("failed to create issue", err)
1612
-
s.pages.Notice(w, "issues", "Failed to create issue.")
1613
return
1614
}
1615
1616
-
issueId, err := db.GetIssueId(s.db, f.RepoAt)
1617
if err != nil {
1618
log.Println("failed to get issue id", err)
1619
-
s.pages.Notice(w, "issues", "Failed to create issue.")
1620
return
1621
}
1622
1623
-
client, err := s.oauth.AuthorizedClient(r)
1624
if err != nil {
1625
log.Println("failed to get authorized client", err)
1626
-
s.pages.Notice(w, "issues", "Failed to create issue.")
1627
return
1628
}
1629
atUri := f.RepoAt.String()
···
1643
})
1644
if err != nil {
1645
log.Println("failed to create issue", err)
1646
-
s.pages.Notice(w, "issues", "Failed to create issue.")
1647
return
1648
}
1649
1650
-
err = db.SetIssueAt(s.db, f.RepoAt, issueId, resp.Uri)
1651
if err != nil {
1652
log.Println("failed to set issue at", err)
1653
-
s.pages.Notice(w, "issues", "Failed to create issue.")
1654
return
1655
}
1656
1657
-
if !s.config.Core.Dev {
1658
-
err = s.posthog.Enqueue(posthog.Capture{
1659
DistinctId: user.Did,
1660
Event: "new_issue",
1661
Properties: posthog.Properties{"repo_at": f.RepoAt.String(), "issue_id": issueId},
···
1665
}
1666
}
1667
1668
-
s.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issueId))
1669
return
1670
}
1671
}
1672
1673
-
func (s *State) SyncRepoFork(w http.ResponseWriter, r *http.Request) {
1674
-
user := s.oauth.GetUser(r)
1675
-
f, err := s.repoResolver.Resolve(r)
1676
if err != nil {
1677
log.Printf("failed to resolve source repo: %v", err)
1678
return
···
1680
1681
switch r.Method {
1682
case http.MethodPost:
1683
-
secret, err := db.GetRegistrationKey(s.db, f.Knot)
1684
if err != nil {
1685
-
s.pages.Notice(w, "repo", fmt.Sprintf("No registration key found for knot %s.", f.Knot))
1686
return
1687
}
1688
1689
-
client, err := knotclient.NewSignedClient(f.Knot, secret, s.config.Core.Dev)
1690
if err != nil {
1691
-
s.pages.Notice(w, "repo", "Failed to reach knot server.")
1692
return
1693
}
1694
1695
var uri string
1696
-
if s.config.Core.Dev {
1697
uri = "http"
1698
} else {
1699
uri = "https"
···
1703
1704
_, err = client.SyncRepoFork(user.Did, forkSourceUrl, forkName, f.Ref)
1705
if err != nil {
1706
-
s.pages.Notice(w, "repo", "Failed to sync repository fork.")
1707
return
1708
}
1709
1710
-
s.pages.HxRefresh(w)
1711
return
1712
}
1713
}
1714
1715
-
func (s *State) ForkRepo(w http.ResponseWriter, r *http.Request) {
1716
-
user := s.oauth.GetUser(r)
1717
-
f, err := s.repoResolver.Resolve(r)
1718
if err != nil {
1719
log.Printf("failed to resolve source repo: %v", err)
1720
return
···
1722
1723
switch r.Method {
1724
case http.MethodGet:
1725
-
user := s.oauth.GetUser(r)
1726
-
knots, err := s.enforcer.GetDomainsForUser(user.Did)
1727
if err != nil {
1728
-
s.pages.Notice(w, "repo", "Invalid user account.")
1729
return
1730
}
1731
1732
-
s.pages.ForkRepo(w, pages.ForkRepoParams{
1733
LoggedInUser: user,
1734
Knots: knots,
1735
RepoInfo: f.RepoInfo(user),
···
1739
1740
knot := r.FormValue("knot")
1741
if knot == "" {
1742
-
s.pages.Notice(w, "repo", "Invalid form submission—missing knot domain.")
1743
return
1744
}
1745
1746
-
ok, err := s.enforcer.E.Enforce(user.Did, knot, knot, "repo:create")
1747
if err != nil || !ok {
1748
-
s.pages.Notice(w, "repo", "You do not have permission to create a repo in this knot.")
1749
return
1750
}
1751
···
1753
1754
// this check is *only* to see if the forked repo name already exists
1755
// in the user's account.
1756
-
existingRepo, err := db.GetRepo(s.db, user.Did, f.RepoName)
1757
if err != nil {
1758
if errors.Is(err, sql.ErrNoRows) {
1759
// no existing repo with this name found, we can use the name as is
1760
} else {
1761
log.Println("error fetching existing repo from db", err)
1762
-
s.pages.Notice(w, "repo", "Failed to fork this repository. Try again later.")
1763
return
1764
}
1765
} else if existingRepo != nil {
1766
// repo with this name already exists, append random string
1767
forkName = fmt.Sprintf("%s-%s", forkName, randomString(3))
1768
}
1769
-
secret, err := db.GetRegistrationKey(s.db, knot)
1770
if err != nil {
1771
-
s.pages.Notice(w, "repo", fmt.Sprintf("No registration key found for knot %s.", knot))
1772
return
1773
}
1774
1775
-
client, err := knotclient.NewSignedClient(knot, secret, s.config.Core.Dev)
1776
if err != nil {
1777
-
s.pages.Notice(w, "repo", "Failed to reach knot server.")
1778
return
1779
}
1780
1781
var uri string
1782
-
if s.config.Core.Dev {
1783
uri = "http"
1784
} else {
1785
uri = "https"
···
1796
Source: sourceAt,
1797
}
1798
1799
-
tx, err := s.db.BeginTx(r.Context(), nil)
1800
if err != nil {
1801
log.Println(err)
1802
-
s.pages.Notice(w, "repo", "Failed to save repository information.")
1803
return
1804
}
1805
defer func() {
1806
tx.Rollback()
1807
-
err = s.enforcer.E.LoadPolicy()
1808
if err != nil {
1809
log.Println("failed to rollback policies")
1810
}
···
1812
1813
resp, err := client.ForkRepo(user.Did, forkSourceUrl, forkName)
1814
if err != nil {
1815
-
s.pages.Notice(w, "repo", "Failed to create repository on knot server.")
1816
return
1817
}
1818
1819
switch resp.StatusCode {
1820
case http.StatusConflict:
1821
-
s.pages.Notice(w, "repo", "A repository with that name already exists.")
1822
return
1823
case http.StatusInternalServerError:
1824
-
s.pages.Notice(w, "repo", "Failed to create repository on knot. Try again later.")
1825
case http.StatusNoContent:
1826
// continue
1827
}
1828
1829
-
xrpcClient, err := s.oauth.AuthorizedClient(r)
1830
if err != nil {
1831
log.Println("failed to get authorized client", err)
1832
-
s.pages.Notice(w, "repo", "Failed to create repository.")
1833
return
1834
}
1835
···
1849
})
1850
if err != nil {
1851
log.Printf("failed to create record: %s", err)
1852
-
s.pages.Notice(w, "repo", "Failed to announce repository creation.")
1853
return
1854
}
1855
log.Println("created repo record: ", atresp.Uri)
···
1858
err = db.AddRepo(tx, repo)
1859
if err != nil {
1860
log.Println(err)
1861
-
s.pages.Notice(w, "repo", "Failed to save repository information.")
1862
return
1863
}
1864
1865
// acls
1866
p, _ := securejoin.SecureJoin(user.Did, forkName)
1867
-
err = s.enforcer.AddRepo(user.Did, knot, p)
1868
if err != nil {
1869
log.Println(err)
1870
-
s.pages.Notice(w, "repo", "Failed to set up repository permissions.")
1871
return
1872
}
1873
···
1878
return
1879
}
1880
1881
-
err = s.enforcer.E.SavePolicy()
1882
if err != nil {
1883
log.Println("failed to update ACLs", err)
1884
http.Error(w, err.Error(), http.StatusInternalServerError)
1885
return
1886
}
1887
1888
-
s.pages.HxLocation(w, fmt.Sprintf("/@%s/%s", user.Handle, forkName))
1889
return
1890
}
1891
}
1892
1893
-
func (s *State) RepoCompareNew(w http.ResponseWriter, r *http.Request) {
1894
-
user := s.oauth.GetUser(r)
1895
-
f, err := s.repoResolver.Resolve(r)
1896
if err != nil {
1897
log.Println("failed to get repo and knot", err)
1898
return
1899
}
1900
1901
-
us, err := knotclient.NewUnsignedClient(f.Knot, s.config.Core.Dev)
1902
if err != nil {
1903
log.Printf("failed to create unsigned client for %s", f.Knot)
1904
-
s.pages.Error503(w)
1905
return
1906
}
1907
1908
result, err := us.Branches(f.OwnerDid(), f.RepoName)
1909
if err != nil {
1910
-
s.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
1911
log.Println("failed to reach knotserver", err)
1912
return
1913
}
···
1938
1939
tags, err := us.Tags(f.OwnerDid(), f.RepoName)
1940
if err != nil {
1941
-
s.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
1942
log.Println("failed to reach knotserver", err)
1943
return
1944
}
1945
1946
repoinfo := f.RepoInfo(user)
1947
1948
-
s.pages.RepoCompareNew(w, pages.RepoCompareNewParams{
1949
LoggedInUser: user,
1950
RepoInfo: repoinfo,
1951
Branches: branches,
···
1955
})
1956
}
1957
1958
-
func (s *State) RepoCompare(w http.ResponseWriter, r *http.Request) {
1959
-
user := s.oauth.GetUser(r)
1960
-
f, err := s.repoResolver.Resolve(r)
1961
if err != nil {
1962
log.Println("failed to get repo and knot", err)
1963
return
···
1979
1980
if base == "" || head == "" {
1981
log.Printf("invalid comparison")
1982
-
s.pages.Error404(w)
1983
return
1984
}
1985
1986
-
us, err := knotclient.NewUnsignedClient(f.Knot, s.config.Core.Dev)
1987
if err != nil {
1988
log.Printf("failed to create unsigned client for %s", f.Knot)
1989
-
s.pages.Error503(w)
1990
return
1991
}
1992
1993
branches, err := us.Branches(f.OwnerDid(), f.RepoName)
1994
if err != nil {
1995
-
s.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
1996
log.Println("failed to reach knotserver", err)
1997
return
1998
}
1999
2000
tags, err := us.Tags(f.OwnerDid(), f.RepoName)
2001
if err != nil {
2002
-
s.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
2003
log.Println("failed to reach knotserver", err)
2004
return
2005
}
2006
2007
formatPatch, err := us.Compare(f.OwnerDid(), f.RepoName, base, head)
2008
if err != nil {
2009
-
s.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
2010
log.Println("failed to compare", err)
2011
return
2012
}
···
2014
2015
repoinfo := f.RepoInfo(user)
2016
2017
-
s.pages.RepoCompare(w, pages.RepoCompareParams{
2018
LoggedInUser: user,
2019
RepoInfo: repoinfo,
2020
Branches: branches.Branches,
···
1
+
package repo
2
3
import (
4
"database/sql"
···
19
"tangled.sh/tangled.sh/core/api/tangled"
20
"tangled.sh/tangled.sh/core/appview"
21
"tangled.sh/tangled.sh/core/appview/db"
22
+
"tangled.sh/tangled.sh/core/appview/idresolver"
23
"tangled.sh/tangled.sh/core/appview/oauth"
24
"tangled.sh/tangled.sh/core/appview/pages"
25
"tangled.sh/tangled.sh/core/appview/pages/markup"
···
28
"tangled.sh/tangled.sh/core/appview/reporesolver"
29
"tangled.sh/tangled.sh/core/knotclient"
30
"tangled.sh/tangled.sh/core/patchutil"
31
+
"tangled.sh/tangled.sh/core/rbac"
32
"tangled.sh/tangled.sh/core/types"
33
34
"github.com/bluesky-social/indigo/atproto/data"
···
41
lexutil "github.com/bluesky-social/indigo/lex/util"
42
)
43
44
+
type Repo struct {
45
+
repoResolver *reporesolver.RepoResolver
46
+
idResolver *idresolver.Resolver
47
+
config *appview.Config
48
+
oauth *oauth.OAuth
49
+
pages *pages.Pages
50
+
db *db.DB
51
+
enforcer *rbac.Enforcer
52
+
posthog posthog.Client
53
+
}
54
+
55
+
func New(
56
+
oauth *oauth.OAuth,
57
+
repoResolver *reporesolver.RepoResolver,
58
+
pages *pages.Pages,
59
+
idResolver *idresolver.Resolver,
60
+
db *db.DB,
61
+
config *appview.Config,
62
+
posthog posthog.Client,
63
+
enforcer *rbac.Enforcer,
64
+
) *Repo {
65
+
return &Repo{oauth: oauth,
66
+
repoResolver: repoResolver,
67
+
pages: pages,
68
+
idResolver: idResolver,
69
+
config: config,
70
+
db: db,
71
+
posthog: posthog,
72
+
enforcer: enforcer,
73
+
}
74
+
}
75
+
76
+
func (rp *Repo) RepoIndex(w http.ResponseWriter, r *http.Request) {
77
ref := chi.URLParam(r, "ref")
78
+
f, err := rp.repoResolver.Resolve(r)
79
if err != nil {
80
log.Println("failed to fully resolve repo", err)
81
return
82
}
83
84
+
us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev)
85
if err != nil {
86
log.Printf("failed to create unsigned client for %s", f.Knot)
87
+
rp.pages.Error503(w)
88
return
89
}
90
91
result, err := us.Index(f.OwnerDid(), f.RepoName, ref)
92
if err != nil {
93
+
rp.pages.Error503(w)
94
log.Println("failed to reach knotserver", err)
95
return
96
}
···
141
142
emails := uniqueEmails(commitsTrunc)
143
144
+
user := rp.oauth.GetUser(r)
145
repoInfo := f.RepoInfo(user)
146
147
+
secret, err := db.GetRegistrationKey(rp.db, f.Knot)
148
if err != nil {
149
log.Printf("failed to get registration key for %s: %s", f.Knot, err)
150
+
rp.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.")
151
}
152
153
+
signedClient, err := knotclient.NewSignedClient(f.Knot, secret, rp.config.Core.Dev)
154
if err != nil {
155
log.Printf("failed to create signed client for %s: %s", f.Knot, err)
156
return
···
158
159
var forkInfo *types.ForkInfo
160
if user != nil && (repoInfo.Roles.IsOwner() || repoInfo.Roles.IsCollaborator()) {
161
+
forkInfo, err = getForkInfo(repoInfo, rp, f, user, signedClient)
162
if err != nil {
163
log.Printf("Failed to fetch fork information: %v", err)
164
return
···
171
// non-fatal
172
}
173
174
+
rp.pages.RepoIndexPage(w, pages.RepoIndexParams{
175
LoggedInUser: user,
176
RepoInfo: repoInfo,
177
TagMap: tagMap,
···
180
TagsTrunc: tagsTrunc,
181
ForkInfo: forkInfo,
182
BranchesTrunc: branchesTrunc,
183
+
EmailToDidOrHandle: EmailToDidOrHandle(rp, emails),
184
Languages: repoLanguages,
185
})
186
return
···
188
189
func getForkInfo(
190
repoInfo repoinfo.RepoInfo,
191
+
rp *Repo,
192
f *reporesolver.ResolvedRepo,
193
user *oauth.User,
194
signedClient *knotclient.SignedClient,
···
207
return &forkInfo, nil
208
}
209
210
+
us, err := knotclient.NewUnsignedClient(repoInfo.Source.Knot, rp.config.Core.Dev)
211
if err != nil {
212
log.Printf("failed to create unsigned client for %s", repoInfo.Source.Knot)
213
return nil, err
···
250
return &forkInfo, nil
251
}
252
253
+
func (rp *Repo) RepoLog(w http.ResponseWriter, r *http.Request) {
254
+
f, err := rp.repoResolver.Resolve(r)
255
if err != nil {
256
log.Println("failed to fully resolve repo", err)
257
return
···
267
268
ref := chi.URLParam(r, "ref")
269
270
+
us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev)
271
if err != nil {
272
log.Println("failed to create unsigned client", err)
273
return
···
294
tagMap[hash] = append(tagMap[hash], tag.Name)
295
}
296
297
+
user := rp.oauth.GetUser(r)
298
+
rp.pages.RepoLog(w, pages.RepoLogParams{
299
LoggedInUser: user,
300
TagMap: tagMap,
301
RepoInfo: f.RepoInfo(user),
302
RepoLogResponse: *repolog,
303
+
EmailToDidOrHandle: EmailToDidOrHandle(rp, uniqueEmails(repolog.Commits)),
304
})
305
return
306
}
307
308
+
func (rp *Repo) RepoDescriptionEdit(w http.ResponseWriter, r *http.Request) {
309
+
f, err := rp.repoResolver.Resolve(r)
310
if err != nil {
311
log.Println("failed to get repo and knot", err)
312
w.WriteHeader(http.StatusBadRequest)
313
return
314
}
315
316
+
user := rp.oauth.GetUser(r)
317
+
rp.pages.EditRepoDescriptionFragment(w, pages.RepoDescriptionParams{
318
RepoInfo: f.RepoInfo(user),
319
})
320
return
321
}
322
323
+
func (rp *Repo) RepoDescription(w http.ResponseWriter, r *http.Request) {
324
+
f, err := rp.repoResolver.Resolve(r)
325
if err != nil {
326
log.Println("failed to get repo and knot", err)
327
w.WriteHeader(http.StatusBadRequest)
···
336
return
337
}
338
339
+
user := rp.oauth.GetUser(r)
340
341
switch r.Method {
342
case http.MethodGet:
343
+
rp.pages.RepoDescriptionFragment(w, pages.RepoDescriptionParams{
344
RepoInfo: f.RepoInfo(user),
345
})
346
return
347
case http.MethodPut:
348
+
user := rp.oauth.GetUser(r)
349
newDescription := r.FormValue("description")
350
+
client, err := rp.oauth.AuthorizedClient(r)
351
if err != nil {
352
log.Println("failed to get client")
353
+
rp.pages.Notice(w, "repo-notice", "Failed to update description, try again later.")
354
return
355
}
356
357
// optimistic update
358
+
err = db.UpdateDescription(rp.db, string(repoAt), newDescription)
359
if err != nil {
360
log.Println("failed to perferom update-description query", err)
361
+
rp.pages.Notice(w, "repo-notice", "Failed to update description, try again later.")
362
return
363
}
364
···
368
ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoNSID, user.Did, rkey)
369
if err != nil {
370
// failed to get record
371
+
rp.pages.Notice(w, "repo-notice", "Failed to update description, no record found on PDS.")
372
return
373
}
374
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
···
390
if err != nil {
391
log.Println("failed to perferom update-description query", err)
392
// failed to get record
393
+
rp.pages.Notice(w, "repo-notice", "Failed to update description, unable to save to PDS.")
394
return
395
}
396
397
newRepoInfo := f.RepoInfo(user)
398
newRepoInfo.Description = newDescription
399
400
+
rp.pages.RepoDescriptionFragment(w, pages.RepoDescriptionParams{
401
RepoInfo: newRepoInfo,
402
})
403
return
404
}
405
}
406
407
+
func (rp *Repo) RepoCommit(w http.ResponseWriter, r *http.Request) {
408
+
f, err := rp.repoResolver.Resolve(r)
409
if err != nil {
410
log.Println("failed to fully resolve repo", err)
411
return
412
}
413
ref := chi.URLParam(r, "ref")
414
protocol := "http"
415
+
if !rp.config.Core.Dev {
416
protocol = "https"
417
}
418
419
if !plumbing.IsHash(ref) {
420
+
rp.pages.Error404(w)
421
return
422
}
423
···
440
return
441
}
442
443
+
user := rp.oauth.GetUser(r)
444
+
rp.pages.RepoCommit(w, pages.RepoCommitParams{
445
LoggedInUser: user,
446
RepoInfo: f.RepoInfo(user),
447
RepoCommitResponse: result,
448
+
EmailToDidOrHandle: EmailToDidOrHandle(rp, []string{result.Diff.Commit.Author.Email}),
449
})
450
return
451
}
452
453
+
func (rp *Repo) RepoTree(w http.ResponseWriter, r *http.Request) {
454
+
f, err := rp.repoResolver.Resolve(r)
455
if err != nil {
456
log.Println("failed to fully resolve repo", err)
457
return
···
460
ref := chi.URLParam(r, "ref")
461
treePath := chi.URLParam(r, "*")
462
protocol := "http"
463
+
if !rp.config.Core.Dev {
464
protocol = "https"
465
}
466
resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/tree/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, treePath))
···
489
return
490
}
491
492
+
user := rp.oauth.GetUser(r)
493
494
var breadcrumbs [][]string
495
breadcrumbs = append(breadcrumbs, []string{f.RepoName, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), ref)})
···
502
baseTreeLink := path.Join(f.OwnerSlashRepo(), "tree", ref, treePath)
503
baseBlobLink := path.Join(f.OwnerSlashRepo(), "blob", ref, treePath)
504
505
+
rp.pages.RepoTree(w, pages.RepoTreeParams{
506
LoggedInUser: user,
507
BreadCrumbs: breadcrumbs,
508
BaseTreeLink: baseTreeLink,
···
513
return
514
}
515
516
+
func (rp *Repo) RepoTags(w http.ResponseWriter, r *http.Request) {
517
+
f, err := rp.repoResolver.Resolve(r)
518
if err != nil {
519
log.Println("failed to get repo and knot", err)
520
return
521
}
522
523
+
us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev)
524
if err != nil {
525
log.Println("failed to create unsigned client", err)
526
return
···
532
return
533
}
534
535
+
artifacts, err := db.GetArtifact(rp.db, db.FilterEq("repo_at", f.RepoAt))
536
if err != nil {
537
log.Println("failed grab artifacts", err)
538
return
···
560
}
561
}
562
563
+
user := rp.oauth.GetUser(r)
564
+
rp.pages.RepoTags(w, pages.RepoTagsParams{
565
LoggedInUser: user,
566
RepoInfo: f.RepoInfo(user),
567
RepoTagsResponse: *result,
···
571
return
572
}
573
574
+
func (rp *Repo) RepoBranches(w http.ResponseWriter, r *http.Request) {
575
+
f, err := rp.repoResolver.Resolve(r)
576
if err != nil {
577
log.Println("failed to get repo and knot", err)
578
return
579
}
580
581
+
us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev)
582
if err != nil {
583
log.Println("failed to create unsigned client", err)
584
return
···
607
return strings.Compare(a.Name, b.Name) * -1
608
})
609
610
+
user := rp.oauth.GetUser(r)
611
+
rp.pages.RepoBranches(w, pages.RepoBranchesParams{
612
LoggedInUser: user,
613
RepoInfo: f.RepoInfo(user),
614
RepoBranchesResponse: *result,
···
616
return
617
}
618
619
+
func (rp *Repo) RepoBlob(w http.ResponseWriter, r *http.Request) {
620
+
f, err := rp.repoResolver.Resolve(r)
621
if err != nil {
622
log.Println("failed to get repo and knot", err)
623
return
···
626
ref := chi.URLParam(r, "ref")
627
filePath := chi.URLParam(r, "*")
628
protocol := "http"
629
+
if !rp.config.Core.Dev {
630
protocol = "https"
631
}
632
resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath))
···
664
showRendered = r.URL.Query().Get("code") != "true"
665
}
666
667
+
user := rp.oauth.GetUser(r)
668
+
rp.pages.RepoBlob(w, pages.RepoBlobParams{
669
LoggedInUser: user,
670
RepoInfo: f.RepoInfo(user),
671
RepoBlobResponse: result,
···
676
return
677
}
678
679
+
func (rp *Repo) RepoBlobRaw(w http.ResponseWriter, r *http.Request) {
680
+
f, err := rp.repoResolver.Resolve(r)
681
if err != nil {
682
log.Println("failed to get repo and knot", err)
683
return
···
687
filePath := chi.URLParam(r, "*")
688
689
protocol := "http"
690
+
if !rp.config.Core.Dev {
691
protocol = "https"
692
}
693
resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath))
···
720
return
721
}
722
723
+
func (rp *Repo) AddCollaborator(w http.ResponseWriter, r *http.Request) {
724
+
f, err := rp.repoResolver.Resolve(r)
725
if err != nil {
726
log.Println("failed to get repo and knot", err)
727
return
···
733
return
734
}
735
736
+
collaboratorIdent, err := rp.idResolver.ResolveIdent(r.Context(), collaborator)
737
if err != nil {
738
w.Write([]byte("failed to resolve collaborator did to a handle"))
739
return
···
742
743
// TODO: create an atproto record for this
744
745
+
secret, err := db.GetRegistrationKey(rp.db, f.Knot)
746
if err != nil {
747
log.Printf("no key found for domain %s: %s\n", f.Knot, err)
748
return
749
}
750
751
+
ksClient, err := knotclient.NewSignedClient(f.Knot, secret, rp.config.Core.Dev)
752
if err != nil {
753
log.Println("failed to create client to ", f.Knot)
754
return
···
765
return
766
}
767
768
+
tx, err := rp.db.BeginTx(r.Context(), nil)
769
if err != nil {
770
log.Println("failed to start tx")
771
w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err)))
···
773
}
774
defer func() {
775
tx.Rollback()
776
+
err = rp.enforcer.E.LoadPolicy()
777
if err != nil {
778
log.Println("failed to rollback policies")
779
}
780
}()
781
782
+
err = rp.enforcer.AddCollaborator(collaboratorIdent.DID.String(), f.Knot, f.DidSlashRepo())
783
if err != nil {
784
w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err)))
785
return
786
}
787
788
+
err = db.AddCollaborator(rp.db, collaboratorIdent.DID.String(), f.OwnerDid(), f.RepoName, f.Knot)
789
if err != nil {
790
w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err)))
791
return
···
798
return
799
}
800
801
+
err = rp.enforcer.E.SavePolicy()
802
if err != nil {
803
log.Println("failed to update ACLs", err)
804
http.Error(w, err.Error(), http.StatusInternalServerError)
···
809
810
}
811
812
+
func (rp *Repo) DeleteRepo(w http.ResponseWriter, r *http.Request) {
813
+
user := rp.oauth.GetUser(r)
814
815
+
f, err := rp.repoResolver.Resolve(r)
816
if err != nil {
817
log.Println("failed to get repo and knot", err)
818
return
819
}
820
821
// remove record from pds
822
+
xrpcClient, err := rp.oauth.AuthorizedClient(r)
823
if err != nil {
824
log.Println("failed to get authorized client", err)
825
return
···
832
})
833
if err != nil {
834
log.Printf("failed to delete record: %s", err)
835
+
rp.pages.Notice(w, "settings-delete", "Failed to delete repository from PDS.")
836
return
837
}
838
log.Println("removed repo record ", f.RepoAt.String())
839
840
+
secret, err := db.GetRegistrationKey(rp.db, f.Knot)
841
if err != nil {
842
log.Printf("no key found for domain %s: %s\n", f.Knot, err)
843
return
844
}
845
846
+
ksClient, err := knotclient.NewSignedClient(f.Knot, secret, rp.config.Core.Dev)
847
if err != nil {
848
log.Println("failed to create client to ", f.Knot)
849
return
···
861
log.Println("removed repo from knot ", f.Knot)
862
}
863
864
+
tx, err := rp.db.BeginTx(r.Context(), nil)
865
if err != nil {
866
log.Println("failed to start tx")
867
w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err)))
···
869
}
870
defer func() {
871
tx.Rollback()
872
+
err = rp.enforcer.E.LoadPolicy()
873
if err != nil {
874
log.Println("failed to rollback policies")
875
}
876
}()
877
878
// remove collaborator RBAC
879
+
repoCollaborators, err := rp.enforcer.E.GetImplicitUsersForResourceByDomain(f.DidSlashRepo(), f.Knot)
880
if err != nil {
881
+
rp.pages.Notice(w, "settings-delete", "Failed to remove collaborators")
882
return
883
}
884
for _, c := range repoCollaborators {
885
did := c[0]
886
+
rp.enforcer.RemoveCollaborator(did, f.Knot, f.DidSlashRepo())
887
}
888
log.Println("removed collaborators")
889
890
// remove repo RBAC
891
+
err = rp.enforcer.RemoveRepo(f.OwnerDid(), f.Knot, f.DidSlashRepo())
892
if err != nil {
893
+
rp.pages.Notice(w, "settings-delete", "Failed to update RBAC rules")
894
return
895
}
896
897
// remove repo from db
898
err = db.RemoveRepo(tx, f.OwnerDid(), f.RepoName)
899
if err != nil {
900
+
rp.pages.Notice(w, "settings-delete", "Failed to update appview")
901
return
902
}
903
log.Println("removed repo from db")
···
909
return
910
}
911
912
+
err = rp.enforcer.E.SavePolicy()
913
if err != nil {
914
log.Println("failed to update ACLs", err)
915
http.Error(w, err.Error(), http.StatusInternalServerError)
916
return
917
}
918
919
+
rp.pages.HxRedirect(w, fmt.Sprintf("/%s", f.OwnerDid()))
920
}
921
922
+
func (rp *Repo) SetDefaultBranch(w http.ResponseWriter, r *http.Request) {
923
+
f, err := rp.repoResolver.Resolve(r)
924
if err != nil {
925
log.Println("failed to get repo and knot", err)
926
return
···
932
return
933
}
934
935
+
secret, err := db.GetRegistrationKey(rp.db, f.Knot)
936
if err != nil {
937
log.Printf("no key found for domain %s: %s\n", f.Knot, err)
938
return
939
}
940
941
+
ksClient, err := knotclient.NewSignedClient(f.Knot, secret, rp.config.Core.Dev)
942
if err != nil {
943
log.Println("failed to create client to ", f.Knot)
944
return
···
951
}
952
953
if ksResp.StatusCode != http.StatusNoContent {
954
+
rp.pages.Notice(w, "repo-settings", "Failed to set default branch. Try again later.")
955
return
956
}
957
958
w.Write([]byte(fmt.Sprint("default branch set to: ", branch)))
959
}
960
961
+
func (rp *Repo) RepoSettings(w http.ResponseWriter, r *http.Request) {
962
+
f, err := rp.repoResolver.Resolve(r)
963
if err != nil {
964
log.Println("failed to get repo and knot", err)
965
return
···
968
switch r.Method {
969
case http.MethodGet:
970
// for now, this is just pubkeys
971
+
user := rp.oauth.GetUser(r)
972
repoCollaborators, err := f.Collaborators(r.Context())
973
if err != nil {
974
log.Println("failed to get collaborators", err)
···
976
977
isCollaboratorInviteAllowed := false
978
if user != nil {
979
+
ok, err := rp.enforcer.IsCollaboratorInviteAllowed(user.Did, f.Knot, f.DidSlashRepo())
980
if err == nil && ok {
981
isCollaboratorInviteAllowed = true
982
}
983
}
984
985
+
us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev)
986
if err != nil {
987
log.Println("failed to create unsigned client", err)
988
return
···
994
return
995
}
996
997
+
rp.pages.RepoSettings(w, pages.RepoSettingsParams{
998
LoggedInUser: user,
999
RepoInfo: f.RepoInfo(user),
1000
Collaborators: repoCollaborators,
···
1004
}
1005
}
1006
1007
+
func (rp *Repo) RepoSingleIssue(w http.ResponseWriter, r *http.Request) {
1008
+
user := rp.oauth.GetUser(r)
1009
+
f, err := rp.repoResolver.Resolve(r)
1010
if err != nil {
1011
log.Println("failed to get repo and knot", err)
1012
return
···
1020
return
1021
}
1022
1023
+
issue, comments, err := db.GetIssueWithComments(rp.db, f.RepoAt, issueIdInt)
1024
if err != nil {
1025
log.Println("failed to get issue and comments", err)
1026
+
rp.pages.Notice(w, "issues", "Failed to load issue. Try again later.")
1027
return
1028
}
1029
1030
+
issueOwnerIdent, err := rp.idResolver.ResolveIdent(r.Context(), issue.OwnerDid)
1031
if err != nil {
1032
log.Println("failed to resolve issue owner", err)
1033
}
···
1036
for i, comment := range comments {
1037
identsToResolve[i] = comment.OwnerDid
1038
}
1039
+
resolvedIds := rp.idResolver.ResolveIdents(r.Context(), identsToResolve)
1040
didHandleMap := make(map[string]string)
1041
for _, identity := range resolvedIds {
1042
if !identity.Handle.IsInvalidHandle() {
···
1046
}
1047
}
1048
1049
+
rp.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{
1050
LoggedInUser: user,
1051
RepoInfo: f.RepoInfo(user),
1052
Issue: *issue,
···
1058
1059
}
1060
1061
+
func (rp *Repo) CloseIssue(w http.ResponseWriter, r *http.Request) {
1062
+
user := rp.oauth.GetUser(r)
1063
+
f, err := rp.repoResolver.Resolve(r)
1064
if err != nil {
1065
log.Println("failed to get repo and knot", err)
1066
return
···
1074
return
1075
}
1076
1077
+
issue, err := db.GetIssue(rp.db, f.RepoAt, issueIdInt)
1078
if err != nil {
1079
log.Println("failed to get issue", err)
1080
+
rp.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.")
1081
return
1082
}
1083
···
1095
1096
closed := tangled.RepoIssueStateClosed
1097
1098
+
client, err := rp.oauth.AuthorizedClient(r)
1099
if err != nil {
1100
log.Println("failed to get authorized client", err)
1101
return
···
1114
1115
if err != nil {
1116
log.Println("failed to update issue state", err)
1117
+
rp.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.")
1118
return
1119
}
1120
1121
+
err = db.CloseIssue(rp.db, f.RepoAt, issueIdInt)
1122
if err != nil {
1123
log.Println("failed to close issue", err)
1124
+
rp.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.")
1125
return
1126
}
1127
1128
+
rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issueIdInt))
1129
return
1130
} else {
1131
log.Println("user is not permitted to close issue")
···
1134
}
1135
}
1136
1137
+
func (rp *Repo) ReopenIssue(w http.ResponseWriter, r *http.Request) {
1138
+
user := rp.oauth.GetUser(r)
1139
+
f, err := rp.repoResolver.Resolve(r)
1140
if err != nil {
1141
log.Println("failed to get repo and knot", err)
1142
return
···
1150
return
1151
}
1152
1153
+
issue, err := db.GetIssue(rp.db, f.RepoAt, issueIdInt)
1154
if err != nil {
1155
log.Println("failed to get issue", err)
1156
+
rp.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.")
1157
return
1158
}
1159
···
1167
isIssueOwner := user.Did == issue.OwnerDid
1168
1169
if isCollaborator || isIssueOwner {
1170
+
err := db.ReopenIssue(rp.db, f.RepoAt, issueIdInt)
1171
if err != nil {
1172
log.Println("failed to reopen issue", err)
1173
+
rp.pages.Notice(w, "issue-action", "Failed to reopen issue. Try again later.")
1174
return
1175
}
1176
+
rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issueIdInt))
1177
return
1178
} else {
1179
log.Println("user is not the owner of the repo")
···
1182
}
1183
}
1184
1185
+
func (rp *Repo) NewIssueComment(w http.ResponseWriter, r *http.Request) {
1186
+
user := rp.oauth.GetUser(r)
1187
+
f, err := rp.repoResolver.Resolve(r)
1188
if err != nil {
1189
log.Println("failed to get repo and knot", err)
1190
return
···
1202
case http.MethodPost:
1203
body := r.FormValue("body")
1204
if body == "" {
1205
+
rp.pages.Notice(w, "issue", "Body is required")
1206
return
1207
}
1208
1209
commentId := mathrand.IntN(1000000)
1210
rkey := appview.TID()
1211
1212
+
err := db.NewIssueComment(rp.db, &db.Comment{
1213
OwnerDid: user.Did,
1214
RepoAt: f.RepoAt,
1215
Issue: issueIdInt,
···
1219
})
1220
if err != nil {
1221
log.Println("failed to create comment", err)
1222
+
rp.pages.Notice(w, "issue-comment", "Failed to create comment.")
1223
return
1224
}
1225
1226
createdAt := time.Now().Format(time.RFC3339)
1227
commentIdInt64 := int64(commentId)
1228
ownerDid := user.Did
1229
+
issueAt, err := db.GetIssueAt(rp.db, f.RepoAt, issueIdInt)
1230
if err != nil {
1231
log.Println("failed to get issue at", err)
1232
+
rp.pages.Notice(w, "issue-comment", "Failed to create comment.")
1233
return
1234
}
1235
1236
atUri := f.RepoAt.String()
1237
+
client, err := rp.oauth.AuthorizedClient(r)
1238
if err != nil {
1239
log.Println("failed to get authorized client", err)
1240
+
rp.pages.Notice(w, "issue-comment", "Failed to create comment.")
1241
return
1242
}
1243
_, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{
···
1257
})
1258
if err != nil {
1259
log.Println("failed to create comment", err)
1260
+
rp.pages.Notice(w, "issue-comment", "Failed to create comment.")
1261
return
1262
}
1263
1264
+
rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d#comment-%d", f.OwnerSlashRepo(), issueIdInt, commentId))
1265
return
1266
}
1267
}
1268
1269
+
func (rp *Repo) IssueComment(w http.ResponseWriter, r *http.Request) {
1270
+
user := rp.oauth.GetUser(r)
1271
+
f, err := rp.repoResolver.Resolve(r)
1272
if err != nil {
1273
log.Println("failed to get repo and knot", err)
1274
return
···
1290
return
1291
}
1292
1293
+
issue, err := db.GetIssue(rp.db, f.RepoAt, issueIdInt)
1294
if err != nil {
1295
log.Println("failed to get issue", err)
1296
+
rp.pages.Notice(w, "issues", "Failed to load issue. Try again later.")
1297
return
1298
}
1299
1300
+
comment, err := db.GetComment(rp.db, f.RepoAt, issueIdInt, commentIdInt)
1301
if err != nil {
1302
http.Error(w, "bad comment id", http.StatusBadRequest)
1303
return
1304
}
1305
1306
+
identity, err := rp.idResolver.ResolveIdent(r.Context(), comment.OwnerDid)
1307
if err != nil {
1308
log.Println("failed to resolve did")
1309
return
···
1316
didHandleMap[identity.DID.String()] = identity.DID.String()
1317
}
1318
1319
+
rp.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
1320
LoggedInUser: user,
1321
RepoInfo: f.RepoInfo(user),
1322
DidHandleMap: didHandleMap,
···
1325
})
1326
}
1327
1328
+
func (rp *Repo) EditIssueComment(w http.ResponseWriter, r *http.Request) {
1329
+
user := rp.oauth.GetUser(r)
1330
+
f, err := rp.repoResolver.Resolve(r)
1331
if err != nil {
1332
log.Println("failed to get repo and knot", err)
1333
return
···
1349
return
1350
}
1351
1352
+
issue, err := db.GetIssue(rp.db, f.RepoAt, issueIdInt)
1353
if err != nil {
1354
log.Println("failed to get issue", err)
1355
+
rp.pages.Notice(w, "issues", "Failed to load issue. Try again later.")
1356
return
1357
}
1358
1359
+
comment, err := db.GetComment(rp.db, f.RepoAt, issueIdInt, commentIdInt)
1360
if err != nil {
1361
http.Error(w, "bad comment id", http.StatusBadRequest)
1362
return
···
1369
1370
switch r.Method {
1371
case http.MethodGet:
1372
+
rp.pages.EditIssueCommentFragment(w, pages.EditIssueCommentParams{
1373
LoggedInUser: user,
1374
RepoInfo: f.RepoInfo(user),
1375
Issue: issue,
···
1378
case http.MethodPost:
1379
// extract form value
1380
newBody := r.FormValue("body")
1381
+
client, err := rp.oauth.AuthorizedClient(r)
1382
if err != nil {
1383
log.Println("failed to get authorized client", err)
1384
+
rp.pages.Notice(w, "issue-comment", "Failed to create comment.")
1385
return
1386
}
1387
rkey := comment.Rkey
1388
1389
// optimistic update
1390
edited := time.Now()
1391
+
err = db.EditComment(rp.db, comment.RepoAt, comment.Issue, comment.CommentId, newBody)
1392
if err != nil {
1393
log.Println("failed to perferom update-description query", err)
1394
+
rp.pages.Notice(w, "repo-notice", "Failed to update description, try again later.")
1395
return
1396
}
1397
···
1402
if err != nil {
1403
// failed to get record
1404
log.Println(err, rkey)
1405
+
rp.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "Failed to update description, no record found on PDS.")
1406
return
1407
}
1408
value, _ := ex.Value.MarshalJSON() // we just did get record; it is valid json
···
1442
comment.Edited = &edited
1443
1444
// return new comment body with htmx
1445
+
rp.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
1446
LoggedInUser: user,
1447
RepoInfo: f.RepoInfo(user),
1448
DidHandleMap: didHandleMap,
···
1455
1456
}
1457
1458
+
func (rp *Repo) DeleteIssueComment(w http.ResponseWriter, r *http.Request) {
1459
+
user := rp.oauth.GetUser(r)
1460
+
f, err := rp.repoResolver.Resolve(r)
1461
if err != nil {
1462
log.Println("failed to get repo and knot", err)
1463
return
···
1471
return
1472
}
1473
1474
+
issue, err := db.GetIssue(rp.db, f.RepoAt, issueIdInt)
1475
if err != nil {
1476
log.Println("failed to get issue", err)
1477
+
rp.pages.Notice(w, "issues", "Failed to load issue. Try again later.")
1478
return
1479
}
1480
···
1486
return
1487
}
1488
1489
+
comment, err := db.GetComment(rp.db, f.RepoAt, issueIdInt, commentIdInt)
1490
if err != nil {
1491
http.Error(w, "bad comment id", http.StatusBadRequest)
1492
return
···
1504
1505
// optimistic deletion
1506
deleted := time.Now()
1507
+
err = db.DeleteComment(rp.db, f.RepoAt, issueIdInt, commentIdInt)
1508
if err != nil {
1509
log.Println("failed to delete comment")
1510
+
rp.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "failed to delete comment")
1511
return
1512
}
1513
1514
// delete from pds
1515
if comment.Rkey != "" {
1516
+
client, err := rp.oauth.AuthorizedClient(r)
1517
if err != nil {
1518
log.Println("failed to get authorized client", err)
1519
+
rp.pages.Notice(w, "issue-comment", "Failed to delete comment.")
1520
return
1521
}
1522
_, err = client.RepoDeleteRecord(r.Context(), &comatproto.RepoDeleteRecord_Input{
···
1537
comment.Deleted = &deleted
1538
1539
// htmx fragment of comment after deletion
1540
+
rp.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
1541
LoggedInUser: user,
1542
RepoInfo: f.RepoInfo(user),
1543
DidHandleMap: didHandleMap,
···
1547
return
1548
}
1549
1550
+
func (rp *Repo) RepoIssues(w http.ResponseWriter, r *http.Request) {
1551
params := r.URL.Query()
1552
state := params.Get("state")
1553
isOpen := true
···
1566
page = pagination.FirstPage()
1567
}
1568
1569
+
user := rp.oauth.GetUser(r)
1570
+
f, err := rp.repoResolver.Resolve(r)
1571
if err != nil {
1572
log.Println("failed to get repo and knot", err)
1573
return
1574
}
1575
1576
+
issues, err := db.GetIssues(rp.db, f.RepoAt, isOpen, page)
1577
if err != nil {
1578
log.Println("failed to get issues", err)
1579
+
rp.pages.Notice(w, "issues", "Failed to load issues. Try again later.")
1580
return
1581
}
1582
···
1584
for i, issue := range issues {
1585
identsToResolve[i] = issue.OwnerDid
1586
}
1587
+
resolvedIds := rp.idResolver.ResolveIdents(r.Context(), identsToResolve)
1588
didHandleMap := make(map[string]string)
1589
for _, identity := range resolvedIds {
1590
if !identity.Handle.IsInvalidHandle() {
···
1594
}
1595
}
1596
1597
+
rp.pages.RepoIssues(w, pages.RepoIssuesParams{
1598
+
LoggedInUser: rp.oauth.GetUser(r),
1599
RepoInfo: f.RepoInfo(user),
1600
Issues: issues,
1601
DidHandleMap: didHandleMap,
···
1605
return
1606
}
1607
1608
+
func (rp *Repo) NewIssue(w http.ResponseWriter, r *http.Request) {
1609
+
user := rp.oauth.GetUser(r)
1610
1611
+
f, err := rp.repoResolver.Resolve(r)
1612
if err != nil {
1613
log.Println("failed to get repo and knot", err)
1614
return
···
1616
1617
switch r.Method {
1618
case http.MethodGet:
1619
+
rp.pages.RepoNewIssue(w, pages.RepoNewIssueParams{
1620
LoggedInUser: user,
1621
RepoInfo: f.RepoInfo(user),
1622
})
···
1625
body := r.FormValue("body")
1626
1627
if title == "" || body == "" {
1628
+
rp.pages.Notice(w, "issues", "Title and body are required")
1629
return
1630
}
1631
1632
+
tx, err := rp.db.BeginTx(r.Context(), nil)
1633
if err != nil {
1634
+
rp.pages.Notice(w, "issues", "Failed to create issue, try again later")
1635
return
1636
}
1637
···
1643
})
1644
if err != nil {
1645
log.Println("failed to create issue", err)
1646
+
rp.pages.Notice(w, "issues", "Failed to create issue.")
1647
return
1648
}
1649
1650
+
issueId, err := db.GetIssueId(rp.db, f.RepoAt)
1651
if err != nil {
1652
log.Println("failed to get issue id", err)
1653
+
rp.pages.Notice(w, "issues", "Failed to create issue.")
1654
return
1655
}
1656
1657
+
client, err := rp.oauth.AuthorizedClient(r)
1658
if err != nil {
1659
log.Println("failed to get authorized client", err)
1660
+
rp.pages.Notice(w, "issues", "Failed to create issue.")
1661
return
1662
}
1663
atUri := f.RepoAt.String()
···
1677
})
1678
if err != nil {
1679
log.Println("failed to create issue", err)
1680
+
rp.pages.Notice(w, "issues", "Failed to create issue.")
1681
return
1682
}
1683
1684
+
err = db.SetIssueAt(rp.db, f.RepoAt, issueId, resp.Uri)
1685
if err != nil {
1686
log.Println("failed to set issue at", err)
1687
+
rp.pages.Notice(w, "issues", "Failed to create issue.")
1688
return
1689
}
1690
1691
+
if !rp.config.Core.Dev {
1692
+
err = rp.posthog.Enqueue(posthog.Capture{
1693
DistinctId: user.Did,
1694
Event: "new_issue",
1695
Properties: posthog.Properties{"repo_at": f.RepoAt.String(), "issue_id": issueId},
···
1699
}
1700
}
1701
1702
+
rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issueId))
1703
return
1704
}
1705
}
1706
1707
+
func (rp *Repo) SyncRepoFork(w http.ResponseWriter, r *http.Request) {
1708
+
user := rp.oauth.GetUser(r)
1709
+
f, err := rp.repoResolver.Resolve(r)
1710
if err != nil {
1711
log.Printf("failed to resolve source repo: %v", err)
1712
return
···
1714
1715
switch r.Method {
1716
case http.MethodPost:
1717
+
secret, err := db.GetRegistrationKey(rp.db, f.Knot)
1718
if err != nil {
1719
+
rp.pages.Notice(w, "repo", fmt.Sprintf("No registration key found for knot %rp.", f.Knot))
1720
return
1721
}
1722
1723
+
client, err := knotclient.NewSignedClient(f.Knot, secret, rp.config.Core.Dev)
1724
if err != nil {
1725
+
rp.pages.Notice(w, "repo", "Failed to reach knot server.")
1726
return
1727
}
1728
1729
var uri string
1730
+
if rp.config.Core.Dev {
1731
uri = "http"
1732
} else {
1733
uri = "https"
···
1737
1738
_, err = client.SyncRepoFork(user.Did, forkSourceUrl, forkName, f.Ref)
1739
if err != nil {
1740
+
rp.pages.Notice(w, "repo", "Failed to sync repository fork.")
1741
return
1742
}
1743
1744
+
rp.pages.HxRefresh(w)
1745
return
1746
}
1747
}
1748
1749
+
func (rp *Repo) ForkRepo(w http.ResponseWriter, r *http.Request) {
1750
+
user := rp.oauth.GetUser(r)
1751
+
f, err := rp.repoResolver.Resolve(r)
1752
if err != nil {
1753
log.Printf("failed to resolve source repo: %v", err)
1754
return
···
1756
1757
switch r.Method {
1758
case http.MethodGet:
1759
+
user := rp.oauth.GetUser(r)
1760
+
knots, err := rp.enforcer.GetDomainsForUser(user.Did)
1761
if err != nil {
1762
+
rp.pages.Notice(w, "repo", "Invalid user account.")
1763
return
1764
}
1765
1766
+
rp.pages.ForkRepo(w, pages.ForkRepoParams{
1767
LoggedInUser: user,
1768
Knots: knots,
1769
RepoInfo: f.RepoInfo(user),
···
1773
1774
knot := r.FormValue("knot")
1775
if knot == "" {
1776
+
rp.pages.Notice(w, "repo", "Invalid form submission—missing knot domain.")
1777
return
1778
}
1779
1780
+
ok, err := rp.enforcer.E.Enforce(user.Did, knot, knot, "repo:create")
1781
if err != nil || !ok {
1782
+
rp.pages.Notice(w, "repo", "You do not have permission to create a repo in this knot.")
1783
return
1784
}
1785
···
1787
1788
// this check is *only* to see if the forked repo name already exists
1789
// in the user's account.
1790
+
existingRepo, err := db.GetRepo(rp.db, user.Did, f.RepoName)
1791
if err != nil {
1792
if errors.Is(err, sql.ErrNoRows) {
1793
// no existing repo with this name found, we can use the name as is
1794
} else {
1795
log.Println("error fetching existing repo from db", err)
1796
+
rp.pages.Notice(w, "repo", "Failed to fork this repository. Try again later.")
1797
return
1798
}
1799
} else if existingRepo != nil {
1800
// repo with this name already exists, append random string
1801
forkName = fmt.Sprintf("%s-%s", forkName, randomString(3))
1802
}
1803
+
secret, err := db.GetRegistrationKey(rp.db, knot)
1804
if err != nil {
1805
+
rp.pages.Notice(w, "repo", fmt.Sprintf("No registration key found for knot %rp.", knot))
1806
return
1807
}
1808
1809
+
client, err := knotclient.NewSignedClient(knot, secret, rp.config.Core.Dev)
1810
if err != nil {
1811
+
rp.pages.Notice(w, "repo", "Failed to reach knot server.")
1812
return
1813
}
1814
1815
var uri string
1816
+
if rp.config.Core.Dev {
1817
uri = "http"
1818
} else {
1819
uri = "https"
···
1830
Source: sourceAt,
1831
}
1832
1833
+
tx, err := rp.db.BeginTx(r.Context(), nil)
1834
if err != nil {
1835
log.Println(err)
1836
+
rp.pages.Notice(w, "repo", "Failed to save repository information.")
1837
return
1838
}
1839
defer func() {
1840
tx.Rollback()
1841
+
err = rp.enforcer.E.LoadPolicy()
1842
if err != nil {
1843
log.Println("failed to rollback policies")
1844
}
···
1846
1847
resp, err := client.ForkRepo(user.Did, forkSourceUrl, forkName)
1848
if err != nil {
1849
+
rp.pages.Notice(w, "repo", "Failed to create repository on knot server.")
1850
return
1851
}
1852
1853
switch resp.StatusCode {
1854
case http.StatusConflict:
1855
+
rp.pages.Notice(w, "repo", "A repository with that name already exists.")
1856
return
1857
case http.StatusInternalServerError:
1858
+
rp.pages.Notice(w, "repo", "Failed to create repository on knot. Try again later.")
1859
case http.StatusNoContent:
1860
// continue
1861
}
1862
1863
+
xrpcClient, err := rp.oauth.AuthorizedClient(r)
1864
if err != nil {
1865
log.Println("failed to get authorized client", err)
1866
+
rp.pages.Notice(w, "repo", "Failed to create repository.")
1867
return
1868
}
1869
···
1883
})
1884
if err != nil {
1885
log.Printf("failed to create record: %s", err)
1886
+
rp.pages.Notice(w, "repo", "Failed to announce repository creation.")
1887
return
1888
}
1889
log.Println("created repo record: ", atresp.Uri)
···
1892
err = db.AddRepo(tx, repo)
1893
if err != nil {
1894
log.Println(err)
1895
+
rp.pages.Notice(w, "repo", "Failed to save repository information.")
1896
return
1897
}
1898
1899
// acls
1900
p, _ := securejoin.SecureJoin(user.Did, forkName)
1901
+
err = rp.enforcer.AddRepo(user.Did, knot, p)
1902
if err != nil {
1903
log.Println(err)
1904
+
rp.pages.Notice(w, "repo", "Failed to set up repository permissions.")
1905
return
1906
}
1907
···
1912
return
1913
}
1914
1915
+
err = rp.enforcer.E.SavePolicy()
1916
if err != nil {
1917
log.Println("failed to update ACLs", err)
1918
http.Error(w, err.Error(), http.StatusInternalServerError)
1919
return
1920
}
1921
1922
+
rp.pages.HxLocation(w, fmt.Sprintf("/@%s/%s", user.Handle, forkName))
1923
return
1924
}
1925
}
1926
1927
+
func (rp *Repo) RepoCompareNew(w http.ResponseWriter, r *http.Request) {
1928
+
user := rp.oauth.GetUser(r)
1929
+
f, err := rp.repoResolver.Resolve(r)
1930
if err != nil {
1931
log.Println("failed to get repo and knot", err)
1932
return
1933
}
1934
1935
+
us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev)
1936
if err != nil {
1937
log.Printf("failed to create unsigned client for %s", f.Knot)
1938
+
rp.pages.Error503(w)
1939
return
1940
}
1941
1942
result, err := us.Branches(f.OwnerDid(), f.RepoName)
1943
if err != nil {
1944
+
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
1945
log.Println("failed to reach knotserver", err)
1946
return
1947
}
···
1972
1973
tags, err := us.Tags(f.OwnerDid(), f.RepoName)
1974
if err != nil {
1975
+
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
1976
log.Println("failed to reach knotserver", err)
1977
return
1978
}
1979
1980
repoinfo := f.RepoInfo(user)
1981
1982
+
rp.pages.RepoCompareNew(w, pages.RepoCompareNewParams{
1983
LoggedInUser: user,
1984
RepoInfo: repoinfo,
1985
Branches: branches,
···
1989
})
1990
}
1991
1992
+
func (rp *Repo) RepoCompare(w http.ResponseWriter, r *http.Request) {
1993
+
user := rp.oauth.GetUser(r)
1994
+
f, err := rp.repoResolver.Resolve(r)
1995
if err != nil {
1996
log.Println("failed to get repo and knot", err)
1997
return
···
2013
2014
if base == "" || head == "" {
2015
log.Printf("invalid comparison")
2016
+
rp.pages.Error404(w)
2017
return
2018
}
2019
2020
+
us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev)
2021
if err != nil {
2022
log.Printf("failed to create unsigned client for %s", f.Knot)
2023
+
rp.pages.Error503(w)
2024
return
2025
}
2026
2027
branches, err := us.Branches(f.OwnerDid(), f.RepoName)
2028
if err != nil {
2029
+
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
2030
log.Println("failed to reach knotserver", err)
2031
return
2032
}
2033
2034
tags, err := us.Tags(f.OwnerDid(), f.RepoName)
2035
if err != nil {
2036
+
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
2037
log.Println("failed to reach knotserver", err)
2038
return
2039
}
2040
2041
formatPatch, err := us.Compare(f.OwnerDid(), f.RepoName, base, head)
2042
if err != nil {
2043
+
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
2044
log.Println("failed to compare", err)
2045
return
2046
}
···
2048
2049
repoinfo := f.RepoInfo(user)
2050
2051
+
rp.pages.RepoCompare(w, pages.RepoCompareParams{
2052
LoggedInUser: user,
2053
RepoInfo: repoinfo,
2054
Branches: branches.Branches,
+4
-4
appview/state/repo_util.go
appview/repo/repo_util.go
+4
-4
appview/state/repo_util.go
appview/repo/repo_util.go
···
1
-
package state
2
3
import (
4
"context"
···
56
return
57
}
58
59
-
func EmailToDidOrHandle(s *State, emails []string) map[string]string {
60
-
emailToDid, err := db.GetEmailToDid(s.db, emails, true) // only get verified emails for mapping
61
if err != nil {
62
log.Printf("error fetching dids for emails: %v", err)
63
return nil
···
67
for _, v := range emailToDid {
68
dids = append(dids, v)
69
}
70
-
resolvedIdents := s.idResolver.ResolveIdents(context.Background(), dids)
71
72
didHandleMap := make(map[string]string)
73
for _, identity := range resolvedIdents {
···
1
+
package repo
2
3
import (
4
"context"
···
56
return
57
}
58
59
+
func EmailToDidOrHandle(r *Repo, emails []string) map[string]string {
60
+
emailToDid, err := db.GetEmailToDid(r.db, emails, true) // only get verified emails for mapping
61
if err != nil {
62
log.Printf("error fetching dids for emails: %v", err)
63
return nil
···
67
for _, v := range emailToDid {
68
dids = append(dids, v)
69
}
70
+
resolvedIdents := r.idResolver.ResolveIdents(context.Background(), dids)
71
72
didHandleMap := make(map[string]string)
73
for _, identity := range resolvedIdents {
+7
-85
appview/state/router.go
+7
-85
appview/state/router.go
···
9
"tangled.sh/tangled.sh/core/appview/middleware"
10
oauthhandler "tangled.sh/tangled.sh/core/appview/oauth/handler"
11
"tangled.sh/tangled.sh/core/appview/pulls"
12
"tangled.sh/tangled.sh/core/appview/settings"
13
"tangled.sh/tangled.sh/core/appview/state/userutil"
14
)
···
69
r.With(mw.ResolveRepo()).Route("/{repo}", func(r chi.Router) {
70
r.Use(mw.GoImport())
71
72
-
r.Get("/", s.RepoIndex)
73
-
r.Get("/commits/{ref}", s.RepoLog)
74
-
r.Route("/tree/{ref}", func(r chi.Router) {
75
-
r.Get("/", s.RepoIndex)
76
-
r.Get("/*", s.RepoTree)
77
-
})
78
-
r.Get("/commit/{ref}", s.RepoCommit)
79
-
r.Get("/branches", s.RepoBranches)
80
-
r.Route("/tags", func(r chi.Router) {
81
-
r.Get("/", s.RepoTags)
82
-
r.Route("/{tag}", func(r chi.Router) {
83
-
r.Use(middleware.AuthMiddleware(s.oauth))
84
-
// require auth to download for now
85
-
r.Get("/download/{file}", s.DownloadArtifact)
86
-
87
-
// require repo:push to upload or delete artifacts
88
-
//
89
-
// additionally: only the uploader can truly delete an artifact
90
-
// (record+blob will live on their pds)
91
-
r.Group(func(r chi.Router) {
92
-
r.With(mw.RepoPermissionMiddleware("repo:push"))
93
-
r.Post("/upload", s.AttachArtifact)
94
-
r.Delete("/{file}", s.DeleteArtifact)
95
-
})
96
-
})
97
-
})
98
-
r.Get("/blob/{ref}/*", s.RepoBlob)
99
-
r.Get("/raw/{ref}/*", s.RepoBlobRaw)
100
-
101
-
r.Route("/issues", func(r chi.Router) {
102
-
r.With(middleware.Paginate).Get("/", s.RepoIssues)
103
-
r.Get("/{issue}", s.RepoSingleIssue)
104
-
105
-
r.Group(func(r chi.Router) {
106
-
r.Use(middleware.AuthMiddleware(s.oauth))
107
-
r.Get("/new", s.NewIssue)
108
-
r.Post("/new", s.NewIssue)
109
-
r.Post("/{issue}/comment", s.NewIssueComment)
110
-
r.Route("/{issue}/comment/{comment_id}/", func(r chi.Router) {
111
-
r.Get("/", s.IssueComment)
112
-
r.Delete("/", s.DeleteIssueComment)
113
-
r.Get("/edit", s.EditIssueComment)
114
-
r.Post("/edit", s.EditIssueComment)
115
-
})
116
-
r.Post("/{issue}/close", s.CloseIssue)
117
-
r.Post("/{issue}/reopen", s.ReopenIssue)
118
-
})
119
-
})
120
-
121
-
r.Route("/fork", func(r chi.Router) {
122
-
r.Use(middleware.AuthMiddleware(s.oauth))
123
-
r.Get("/", s.ForkRepo)
124
-
r.Post("/", s.ForkRepo)
125
-
r.With(mw.RepoPermissionMiddleware("repo:owner")).Route("/sync", func(r chi.Router) {
126
-
r.Post("/", s.SyncRepoFork)
127
-
})
128
-
})
129
-
130
-
r.Route("/compare", func(r chi.Router) {
131
-
r.Get("/", s.RepoCompareNew) // start an new comparison
132
-
133
-
// we have to wildcard here since we want to support GitHub's compare syntax
134
-
// /compare/{ref1}...{ref2}
135
-
// for example:
136
-
// /compare/master...some/feature
137
-
// /compare/master...example.com:another/feature <- this is a fork
138
-
r.Get("/{base}/{head}", s.RepoCompare)
139
-
r.Get("/*", s.RepoCompare)
140
-
})
141
142
r.Mount("/pulls", s.PullsRouter(mw))
143
···
146
r.Post("/git-upload-pack", s.UploadPack)
147
r.Post("/git-receive-pack", s.ReceivePack)
148
149
-
// settings routes, needs auth
150
-
r.Group(func(r chi.Router) {
151
-
r.Use(middleware.AuthMiddleware(s.oauth))
152
-
// repo description can only be edited by owner
153
-
r.With(mw.RepoPermissionMiddleware("repo:owner")).Route("/description", func(r chi.Router) {
154
-
r.Put("/", s.RepoDescription)
155
-
r.Get("/", s.RepoDescription)
156
-
r.Get("/edit", s.RepoDescriptionEdit)
157
-
})
158
-
r.With(mw.RepoPermissionMiddleware("repo:settings")).Route("/settings", func(r chi.Router) {
159
-
r.Get("/", s.RepoSettings)
160
-
r.With(mw.RepoPermissionMiddleware("repo:invite")).Put("/collaborator", s.AddCollaborator)
161
-
r.With(mw.RepoPermissionMiddleware("repo:delete")).Delete("/delete", s.DeleteRepo)
162
-
r.Put("/branches/default", s.SetDefaultBranch)
163
-
})
164
-
})
165
})
166
})
167
···
266
pulls := pulls.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config)
267
return pulls.Router(mw)
268
}
···
9
"tangled.sh/tangled.sh/core/appview/middleware"
10
oauthhandler "tangled.sh/tangled.sh/core/appview/oauth/handler"
11
"tangled.sh/tangled.sh/core/appview/pulls"
12
+
"tangled.sh/tangled.sh/core/appview/repo"
13
"tangled.sh/tangled.sh/core/appview/settings"
14
"tangled.sh/tangled.sh/core/appview/state/userutil"
15
)
···
70
r.With(mw.ResolveRepo()).Route("/{repo}", func(r chi.Router) {
71
r.Use(mw.GoImport())
72
73
+
r.Mount("/", s.RepoRouter(mw))
74
75
r.Mount("/pulls", s.PullsRouter(mw))
76
···
79
r.Post("/git-upload-pack", s.UploadPack)
80
r.Post("/git-receive-pack", s.ReceivePack)
81
82
})
83
})
84
···
183
pulls := pulls.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config)
184
return pulls.Router(mw)
185
}
186
+
187
+
func (s *State) RepoRouter(mw *middleware.Middleware) http.Handler {
188
+
repo := repo.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config, s.posthog, s.enforcer)
189
+
return repo.Router(mw)
190
+
}