531 lines
14 kB
1
package state
2
3
import (
4
"encoding/json"
5
"fmt"
6
"io"
7
"log"
8
"net/http"
9
"path"
10
"strings"
11
12
"github.com/bluesky-social/indigo/atproto/identity"
13
securejoin "github.com/cyphar/filepath-securejoin"
14
"github.com/go-chi/chi/v5"
15
"github.com/sotangled/tangled/appview/auth"
16
"github.com/sotangled/tangled/appview/pages"
17
"github.com/sotangled/tangled/types"
18
)
19
20
func (s *State) RepoIndex(w http.ResponseWriter, r *http.Request) {
21
ref := chi.URLParam(r, "ref")
22
f, err := fullyResolvedRepo(r)
23
if err != nil {
24
log.Println("failed to fully resolve repo", err)
25
return
26
}
27
var reqUrl string
28
if ref != "" {
29
reqUrl = fmt.Sprintf("http://%s/%s/%s/tree/%s", f.Knot, f.OwnerDid(), f.RepoName, ref)
30
} else {
31
reqUrl = fmt.Sprintf("http://%s/%s/%s", f.Knot, f.OwnerDid(), f.RepoName)
32
}
33
34
resp, err := http.Get(reqUrl)
35
if err != nil {
36
s.pages.Error503(w)
37
log.Println("failed to reach knotserver", err)
38
return
39
}
40
defer resp.Body.Close()
41
42
body, err := io.ReadAll(resp.Body)
43
if err != nil {
44
log.Fatalf("Error reading response body: %v", err)
45
return
46
}
47
48
var result types.RepoIndexResponse
49
err = json.Unmarshal(body, &result)
50
if err != nil {
51
log.Fatalf("Error unmarshalling response body: %v", err)
52
return
53
}
54
55
branchesResp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/branches", f.Knot, f.OwnerDid(), f.RepoName))
56
if err != nil {
57
log.Println("failed to reach knotserver for branches", err)
58
return
59
}
60
defer branchesResp.Body.Close()
61
62
tagsResp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/tags", f.Knot, f.OwnerDid(), f.RepoName))
63
if err != nil {
64
log.Println("failed to reach knotserver for tags", err)
65
return
66
}
67
defer tagsResp.Body.Close()
68
69
branchesBody, err := io.ReadAll(branchesResp.Body)
70
if err != nil {
71
log.Println("failed to read branches response", err)
72
return
73
}
74
75
tagsBody, err := io.ReadAll(tagsResp.Body)
76
if err != nil {
77
log.Println("failed to read tags response", err)
78
return
79
}
80
81
var branchesResult types.RepoBranchesResponse
82
err = json.Unmarshal(branchesBody, &branchesResult)
83
if err != nil {
84
log.Println("failed to parse branches response", err)
85
return
86
}
87
88
var tagsResult types.RepoTagsResponse
89
err = json.Unmarshal(tagsBody, &tagsResult)
90
if err != nil {
91
log.Println("failed to parse tags response", err)
92
return
93
}
94
95
log.Println(resp.Status, result)
96
97
user := s.auth.GetUser(r)
98
s.pages.RepoIndexPage(w, pages.RepoIndexParams{
99
LoggedInUser: user,
100
RepoInfo: pages.RepoInfo{
101
OwnerDid: f.OwnerDid(),
102
OwnerHandle: f.OwnerHandle(),
103
Name: f.RepoName,
104
SettingsAllowed: settingsAllowed(s, user, f),
105
},
106
RepoIndexResponse: result,
107
Branches: branchesResult.Branches,
108
Tags: tagsResult.Tags,
109
})
110
111
return
112
}
113
114
func (s *State) RepoLog(w http.ResponseWriter, r *http.Request) {
115
f, err := fullyResolvedRepo(r)
116
if err != nil {
117
log.Println("failed to fully resolve repo", err)
118
return
119
}
120
121
ref := chi.URLParam(r, "ref")
122
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/log/%s", f.Knot, f.OwnerDid(), f.RepoName, ref))
123
if err != nil {
124
log.Println("failed to reach knotserver", err)
125
return
126
}
127
128
body, err := io.ReadAll(resp.Body)
129
if err != nil {
130
log.Fatalf("Error reading response body: %v", err)
131
return
132
}
133
134
var result types.RepoLogResponse
135
err = json.Unmarshal(body, &result)
136
if err != nil {
137
log.Println("failed to parse json response", err)
138
return
139
}
140
141
user := s.auth.GetUser(r)
142
s.pages.RepoLog(w, pages.RepoLogParams{
143
LoggedInUser: user,
144
RepoInfo: pages.RepoInfo{
145
OwnerDid: f.OwnerDid(),
146
OwnerHandle: f.OwnerHandle(),
147
Name: f.RepoName,
148
SettingsAllowed: settingsAllowed(s, user, f),
149
},
150
RepoLogResponse: result,
151
})
152
return
153
}
154
155
func (s *State) RepoCommit(w http.ResponseWriter, r *http.Request) {
156
f, err := fullyResolvedRepo(r)
157
if err != nil {
158
log.Println("failed to fully resolve repo", err)
159
return
160
}
161
162
ref := chi.URLParam(r, "ref")
163
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/commit/%s", f.Knot, f.OwnerDid(), f.RepoName, ref))
164
if err != nil {
165
log.Println("failed to reach knotserver", err)
166
return
167
}
168
169
body, err := io.ReadAll(resp.Body)
170
if err != nil {
171
log.Fatalf("Error reading response body: %v", err)
172
return
173
}
174
175
var result types.RepoCommitResponse
176
err = json.Unmarshal(body, &result)
177
if err != nil {
178
log.Println("failed to parse response:", err)
179
return
180
}
181
182
user := s.auth.GetUser(r)
183
s.pages.RepoCommit(w, pages.RepoCommitParams{
184
LoggedInUser: user,
185
RepoInfo: pages.RepoInfo{
186
OwnerDid: f.OwnerDid(),
187
OwnerHandle: f.OwnerHandle(),
188
Name: f.RepoName,
189
SettingsAllowed: settingsAllowed(s, user, f),
190
},
191
RepoCommitResponse: result,
192
})
193
return
194
}
195
196
func (s *State) RepoTree(w http.ResponseWriter, r *http.Request) {
197
f, err := fullyResolvedRepo(r)
198
if err != nil {
199
log.Println("failed to fully resolve repo", err)
200
return
201
}
202
203
ref := chi.URLParam(r, "ref")
204
treePath := chi.URLParam(r, "*")
205
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/tree/%s/%s", f.Knot, f.OwnerDid(), f.RepoName, ref, treePath))
206
if err != nil {
207
log.Println("failed to reach knotserver", err)
208
return
209
}
210
211
body, err := io.ReadAll(resp.Body)
212
if err != nil {
213
log.Fatalf("Error reading response body: %v", err)
214
return
215
}
216
217
var result types.RepoTreeResponse
218
err = json.Unmarshal(body, &result)
219
if err != nil {
220
log.Println("failed to parse response:", err)
221
return
222
}
223
224
user := s.auth.GetUser(r)
225
226
var breadcrumbs [][]string
227
breadcrumbs = append(breadcrumbs, []string{f.RepoName, fmt.Sprintf("/%s/%s/tree/%s", f.OwnerDid(), f.RepoName, ref)})
228
if treePath != "" {
229
for idx, elem := range strings.Split(treePath, "/") {
230
breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], elem)})
231
}
232
}
233
234
baseTreeLink := path.Join(f.OwnerDid(), f.RepoName, "tree", ref, treePath)
235
baseBlobLink := path.Join(f.OwnerDid(), f.RepoName, "blob", ref, treePath)
236
237
s.pages.RepoTree(w, pages.RepoTreeParams{
238
LoggedInUser: user,
239
BreadCrumbs: breadcrumbs,
240
BaseTreeLink: baseTreeLink,
241
BaseBlobLink: baseBlobLink,
242
RepoInfo: pages.RepoInfo{
243
OwnerDid: f.OwnerDid(),
244
OwnerHandle: f.OwnerHandle(),
245
Name: f.RepoName,
246
SettingsAllowed: settingsAllowed(s, user, f),
247
},
248
RepoTreeResponse: result,
249
})
250
return
251
}
252
253
func (s *State) RepoTags(w http.ResponseWriter, r *http.Request) {
254
f, err := fullyResolvedRepo(r)
255
if err != nil {
256
log.Println("failed to get repo and knot", err)
257
return
258
}
259
260
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/tags", f.Knot, f.OwnerDid(), f.RepoName))
261
if err != nil {
262
log.Println("failed to reach knotserver", err)
263
return
264
}
265
266
body, err := io.ReadAll(resp.Body)
267
if err != nil {
268
log.Fatalf("Error reading response body: %v", err)
269
return
270
}
271
272
var result types.RepoTagsResponse
273
err = json.Unmarshal(body, &result)
274
if err != nil {
275
log.Println("failed to parse response:", err)
276
return
277
}
278
279
user := s.auth.GetUser(r)
280
s.pages.RepoTags(w, pages.RepoTagsParams{
281
LoggedInUser: user,
282
RepoInfo: pages.RepoInfo{
283
OwnerDid: f.OwnerDid(),
284
OwnerHandle: f.OwnerHandle(),
285
Name: f.RepoName,
286
SettingsAllowed: settingsAllowed(s, user, f),
287
},
288
RepoTagsResponse: result,
289
})
290
return
291
}
292
293
func (s *State) RepoBranches(w http.ResponseWriter, r *http.Request) {
294
f, err := fullyResolvedRepo(r)
295
if err != nil {
296
log.Println("failed to get repo and knot", err)
297
return
298
}
299
300
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/branches", f.Knot, f.OwnerDid(), f.RepoName))
301
if err != nil {
302
log.Println("failed to reach knotserver", err)
303
return
304
}
305
306
body, err := io.ReadAll(resp.Body)
307
if err != nil {
308
log.Fatalf("Error reading response body: %v", err)
309
return
310
}
311
312
var result types.RepoBranchesResponse
313
err = json.Unmarshal(body, &result)
314
if err != nil {
315
log.Println("failed to parse response:", err)
316
return
317
}
318
319
log.Println(result)
320
321
user := s.auth.GetUser(r)
322
s.pages.RepoBranches(w, pages.RepoBranchesParams{
323
LoggedInUser: user,
324
RepoInfo: pages.RepoInfo{
325
OwnerDid: f.OwnerDid(),
326
OwnerHandle: f.OwnerHandle(),
327
Name: f.RepoName,
328
SettingsAllowed: settingsAllowed(s, user, f),
329
},
330
RepoBranchesResponse: result,
331
})
332
return
333
}
334
335
func (s *State) RepoBlob(w http.ResponseWriter, r *http.Request) {
336
f, err := fullyResolvedRepo(r)
337
if err != nil {
338
log.Println("failed to get repo and knot", err)
339
return
340
}
341
342
ref := chi.URLParam(r, "ref")
343
filePath := chi.URLParam(r, "*")
344
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/blob/%s/%s", f.Knot, f.OwnerDid(), f.RepoName, ref, filePath))
345
if err != nil {
346
log.Println("failed to reach knotserver", err)
347
return
348
}
349
350
body, err := io.ReadAll(resp.Body)
351
if err != nil {
352
log.Fatalf("Error reading response body: %v", err)
353
return
354
}
355
356
var result types.RepoBlobResponse
357
err = json.Unmarshal(body, &result)
358
if err != nil {
359
log.Println("failed to parse response:", err)
360
return
361
}
362
363
var breadcrumbs [][]string
364
breadcrumbs = append(breadcrumbs, []string{f.RepoName, fmt.Sprintf("/%s/%s/tree/%s", f.OwnerDid(), f.RepoName, ref)})
365
if filePath != "" {
366
for idx, elem := range strings.Split(filePath, "/") {
367
breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], elem)})
368
}
369
}
370
371
user := s.auth.GetUser(r)
372
s.pages.RepoBlob(w, pages.RepoBlobParams{
373
LoggedInUser: user,
374
RepoInfo: pages.RepoInfo{
375
OwnerDid: f.OwnerDid(),
376
OwnerHandle: f.OwnerHandle(),
377
Name: f.RepoName,
378
SettingsAllowed: settingsAllowed(s, user, f),
379
},
380
RepoBlobResponse: result,
381
BreadCrumbs: breadcrumbs,
382
})
383
return
384
}
385
386
func (s *State) AddCollaborator(w http.ResponseWriter, r *http.Request) {
387
f, err := fullyResolvedRepo(r)
388
if err != nil {
389
log.Println("failed to get repo and knot", err)
390
return
391
}
392
393
collaborator := r.FormValue("collaborator")
394
if collaborator == "" {
395
http.Error(w, "malformed form", http.StatusBadRequest)
396
return
397
}
398
399
collaboratorIdent, err := s.resolver.ResolveIdent(r.Context(), collaborator)
400
if err != nil {
401
w.Write([]byte("failed to resolve collaborator did to a handle"))
402
return
403
}
404
log.Printf("adding %s to %s\n", collaboratorIdent.Handle.String(), f.Knot)
405
406
// TODO: create an atproto record for this
407
408
secret, err := s.db.GetRegistrationKey(f.Knot)
409
if err != nil {
410
log.Printf("no key found for domain %s: %s\n", f.Knot, err)
411
return
412
}
413
414
ksClient, err := NewSignedClient(f.Knot, secret)
415
if err != nil {
416
log.Println("failed to create client to ", f.Knot)
417
return
418
}
419
420
ksResp, err := ksClient.AddCollaborator(f.OwnerDid(), f.RepoName, collaboratorIdent.DID.String())
421
if err != nil {
422
log.Printf("failed to make request to %s: %s", f.Knot, err)
423
return
424
}
425
426
if ksResp.StatusCode != http.StatusNoContent {
427
w.Write([]byte(fmt.Sprint("knotserver failed to add collaborator: ", err)))
428
return
429
}
430
431
err = s.enforcer.AddCollaborator(collaboratorIdent.DID.String(), f.Knot, f.OwnerSlashRepo())
432
if err != nil {
433
w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err)))
434
return
435
}
436
437
w.Write([]byte(fmt.Sprint("added collaborator: ", collaboratorIdent.Handle.String())))
438
439
}
440
441
func (s *State) RepoSettings(w http.ResponseWriter, r *http.Request) {
442
f, err := fullyResolvedRepo(r)
443
if err != nil {
444
log.Println("failed to get repo and knot", err)
445
return
446
}
447
448
switch r.Method {
449
case http.MethodGet:
450
// for now, this is just pubkeys
451
user := s.auth.GetUser(r)
452
repoCollaborators, err := s.enforcer.E.GetImplicitUsersForResourceByDomain(f.OwnerSlashRepo(), f.Knot)
453
if err != nil {
454
log.Println("failed to get collaborators", err)
455
}
456
log.Println(repoCollaborators)
457
458
isCollaboratorInviteAllowed := false
459
if user != nil {
460
ok, err := s.enforcer.IsCollaboratorInviteAllowed(user.Did, f.Knot, f.OwnerSlashRepo())
461
if err == nil && ok {
462
isCollaboratorInviteAllowed = true
463
}
464
}
465
466
s.pages.RepoSettings(w, pages.RepoSettingsParams{
467
LoggedInUser: user,
468
RepoInfo: pages.RepoInfo{
469
OwnerDid: f.OwnerDid(),
470
OwnerHandle: f.OwnerHandle(),
471
Name: f.RepoName,
472
SettingsAllowed: settingsAllowed(s, user, f),
473
},
474
Collaborators: repoCollaborators,
475
IsCollaboratorInviteAllowed: isCollaboratorInviteAllowed,
476
})
477
}
478
}
479
480
type FullyResolvedRepo struct {
481
Knot string
482
OwnerId identity.Identity
483
RepoName string
484
}
485
486
func (f *FullyResolvedRepo) OwnerDid() string {
487
return f.OwnerId.DID.String()
488
}
489
490
func (f *FullyResolvedRepo) OwnerHandle() string {
491
return f.OwnerId.Handle.String()
492
}
493
494
func (f *FullyResolvedRepo) OwnerSlashRepo() string {
495
p, _ := securejoin.SecureJoin(f.OwnerDid(), f.RepoName)
496
return p
497
}
498
499
func fullyResolvedRepo(r *http.Request) (*FullyResolvedRepo, error) {
500
repoName := chi.URLParam(r, "repo")
501
knot, ok := r.Context().Value("knot").(string)
502
if !ok {
503
log.Println("malformed middleware")
504
return nil, fmt.Errorf("malformed middleware")
505
}
506
id, ok := r.Context().Value("resolvedId").(identity.Identity)
507
if !ok {
508
log.Println("malformed middleware")
509
return nil, fmt.Errorf("malformed middleware")
510
}
511
512
return &FullyResolvedRepo{
513
Knot: knot,
514
OwnerId: id,
515
RepoName: repoName,
516
}, nil
517
}
518
519
func settingsAllowed(s *State, u *auth.User, f *FullyResolvedRepo) bool {
520
settingsAllowed := false
521
if u != nil {
522
ok, err := s.enforcer.IsSettingsAllowed(u.Did, f.Knot, f.OwnerSlashRepo())
523
if err == nil && ok {
524
settingsAllowed = true
525
} else {
526
log.Println(err, ok)
527
}
528
}
529
530
return settingsAllowed
531
}
532