this repo has no description
1package state
2
3import (
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
20func (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
114func (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
155func (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
196func (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
253func (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
293func (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
335func (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
386func (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
441func (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
480type FullyResolvedRepo struct {
481 Knot string
482 OwnerId identity.Identity
483 RepoName string
484}
485
486func (f *FullyResolvedRepo) OwnerDid() string {
487 return f.OwnerId.DID.String()
488}
489
490func (f *FullyResolvedRepo) OwnerHandle() string {
491 return f.OwnerId.Handle.String()
492}
493
494func (f *FullyResolvedRepo) OwnerSlashRepo() string {
495 p, _ := securejoin.SecureJoin(f.OwnerDid(), f.RepoName)
496 return p
497}
498
499func 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
519func 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}