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