this repo has no description
1package state
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io"
8 "log"
9 "math/rand/v2"
10 "net/http"
11 "path"
12 "strconv"
13 "strings"
14
15 "github.com/bluesky-social/indigo/atproto/identity"
16 securejoin "github.com/cyphar/filepath-securejoin"
17 "github.com/go-chi/chi/v5"
18 "github.com/sotangled/tangled/appview/auth"
19 "github.com/sotangled/tangled/appview/db"
20 "github.com/sotangled/tangled/appview/pages"
21 "github.com/sotangled/tangled/types"
22)
23
24func (s *State) RepoIndex(w http.ResponseWriter, r *http.Request) {
25 ref := chi.URLParam(r, "ref")
26 f, err := fullyResolvedRepo(r)
27 if err != nil {
28 log.Println("failed to fully resolve repo", err)
29 return
30 }
31 var reqUrl string
32 if ref != "" {
33 reqUrl = fmt.Sprintf("http://%s/%s/%s/tree/%s", f.Knot, f.OwnerDid(), f.RepoName, ref)
34 } else {
35 reqUrl = fmt.Sprintf("http://%s/%s/%s", f.Knot, f.OwnerDid(), f.RepoName)
36 }
37
38 resp, err := http.Get(reqUrl)
39 if err != nil {
40 s.pages.Error503(w)
41 log.Println("failed to reach knotserver", err)
42 return
43 }
44 defer resp.Body.Close()
45
46 body, err := io.ReadAll(resp.Body)
47 if err != nil {
48 log.Fatalf("Error reading response body: %v", err)
49 return
50 }
51
52 var result types.RepoIndexResponse
53 err = json.Unmarshal(body, &result)
54 if err != nil {
55 log.Fatalf("Error unmarshalling response body: %v", err)
56 return
57 }
58
59 user := s.auth.GetUser(r)
60 s.pages.RepoIndexPage(w, pages.RepoIndexParams{
61 LoggedInUser: user,
62 RepoInfo: pages.RepoInfo{
63 OwnerDid: f.OwnerDid(),
64 OwnerHandle: f.OwnerHandle(),
65 Name: f.RepoName,
66 SettingsAllowed: settingsAllowed(s, user, f),
67 },
68 RepoIndexResponse: result,
69 })
70
71 return
72}
73
74func (s *State) RepoLog(w http.ResponseWriter, r *http.Request) {
75 f, err := fullyResolvedRepo(r)
76 if err != nil {
77 log.Println("failed to fully resolve repo", err)
78 return
79 }
80
81 ref := chi.URLParam(r, "ref")
82 resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/log/%s", f.Knot, f.OwnerDid(), f.RepoName, ref))
83 if err != nil {
84 log.Println("failed to reach knotserver", err)
85 return
86 }
87
88 body, err := io.ReadAll(resp.Body)
89 if err != nil {
90 log.Fatalf("Error reading response body: %v", err)
91 return
92 }
93
94 var result types.RepoLogResponse
95 err = json.Unmarshal(body, &result)
96 if err != nil {
97 log.Println("failed to parse json response", err)
98 return
99 }
100
101 user := s.auth.GetUser(r)
102 s.pages.RepoLog(w, pages.RepoLogParams{
103 LoggedInUser: user,
104 RepoInfo: pages.RepoInfo{
105 OwnerDid: f.OwnerDid(),
106 OwnerHandle: f.OwnerHandle(),
107 Name: f.RepoName,
108 SettingsAllowed: settingsAllowed(s, user, f),
109 },
110 RepoLogResponse: result,
111 })
112 return
113}
114
115func (s *State) RepoCommit(w http.ResponseWriter, r *http.Request) {
116 f, err := fullyResolvedRepo(r)
117 if err != nil {
118 log.Println("failed to fully resolve repo", err)
119 return
120 }
121
122 ref := chi.URLParam(r, "ref")
123 resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/commit/%s", f.Knot, f.OwnerDid(), f.RepoName, ref))
124 if err != nil {
125 log.Println("failed to reach knotserver", err)
126 return
127 }
128
129 body, err := io.ReadAll(resp.Body)
130 if err != nil {
131 log.Fatalf("Error reading response body: %v", err)
132 return
133 }
134
135 var result types.RepoCommitResponse
136 err = json.Unmarshal(body, &result)
137 if err != nil {
138 log.Println("failed to parse response:", err)
139 return
140 }
141
142 user := s.auth.GetUser(r)
143 s.pages.RepoCommit(w, pages.RepoCommitParams{
144 LoggedInUser: user,
145 RepoInfo: pages.RepoInfo{
146 OwnerDid: f.OwnerDid(),
147 OwnerHandle: f.OwnerHandle(),
148 Name: f.RepoName,
149 SettingsAllowed: settingsAllowed(s, user, f),
150 },
151 RepoCommitResponse: result,
152 })
153 return
154}
155
156func (s *State) RepoTree(w http.ResponseWriter, r *http.Request) {
157 f, err := fullyResolvedRepo(r)
158 if err != nil {
159 log.Println("failed to fully resolve repo", err)
160 return
161 }
162
163 ref := chi.URLParam(r, "ref")
164 treePath := chi.URLParam(r, "*")
165 resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/tree/%s/%s", f.Knot, f.OwnerDid(), f.RepoName, ref, treePath))
166 if err != nil {
167 log.Println("failed to reach knotserver", err)
168 return
169 }
170
171 body, err := io.ReadAll(resp.Body)
172 if err != nil {
173 log.Fatalf("Error reading response body: %v", err)
174 return
175 }
176
177 var result types.RepoTreeResponse
178 err = json.Unmarshal(body, &result)
179 if err != nil {
180 log.Println("failed to parse response:", err)
181 return
182 }
183
184 user := s.auth.GetUser(r)
185
186 var breadcrumbs [][]string
187 breadcrumbs = append(breadcrumbs, []string{f.RepoName, fmt.Sprintf("/%s/%s/tree/%s", f.OwnerDid(), f.RepoName, ref)})
188 if treePath != "" {
189 for idx, elem := range strings.Split(treePath, "/") {
190 breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], elem)})
191 }
192 }
193
194 baseTreeLink := path.Join(f.OwnerDid(), f.RepoName, "tree", ref, treePath)
195 baseBlobLink := path.Join(f.OwnerDid(), f.RepoName, "blob", ref, treePath)
196
197 s.pages.RepoTree(w, pages.RepoTreeParams{
198 LoggedInUser: user,
199 BreadCrumbs: breadcrumbs,
200 BaseTreeLink: baseTreeLink,
201 BaseBlobLink: baseBlobLink,
202 RepoInfo: pages.RepoInfo{
203 OwnerDid: f.OwnerDid(),
204 OwnerHandle: f.OwnerHandle(),
205 Name: f.RepoName,
206 SettingsAllowed: settingsAllowed(s, user, f),
207 },
208 RepoTreeResponse: result,
209 })
210 return
211}
212
213func (s *State) RepoTags(w http.ResponseWriter, r *http.Request) {
214 f, err := fullyResolvedRepo(r)
215 if err != nil {
216 log.Println("failed to get repo and knot", err)
217 return
218 }
219
220 resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/tags", f.Knot, f.OwnerDid(), f.RepoName))
221 if err != nil {
222 log.Println("failed to reach knotserver", err)
223 return
224 }
225
226 body, err := io.ReadAll(resp.Body)
227 if err != nil {
228 log.Fatalf("Error reading response body: %v", err)
229 return
230 }
231
232 var result types.RepoTagsResponse
233 err = json.Unmarshal(body, &result)
234 if err != nil {
235 log.Println("failed to parse response:", err)
236 return
237 }
238
239 user := s.auth.GetUser(r)
240 s.pages.RepoTags(w, pages.RepoTagsParams{
241 LoggedInUser: user,
242 RepoInfo: pages.RepoInfo{
243 OwnerDid: f.OwnerDid(),
244 OwnerHandle: f.OwnerHandle(),
245 Name: f.RepoName,
246 SettingsAllowed: settingsAllowed(s, user, f),
247 },
248 RepoTagsResponse: result,
249 })
250 return
251}
252
253func (s *State) RepoBranches(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/branches", 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.RepoBranchesResponse
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.RepoBranches(w, pages.RepoBranchesParams{
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 RepoBranchesResponse: result,
289 })
290 return
291}
292
293func (s *State) RepoBlob(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 ref := chi.URLParam(r, "ref")
301 filePath := chi.URLParam(r, "*")
302 resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/blob/%s/%s", f.Knot, f.OwnerDid(), f.RepoName, ref, filePath))
303 if err != nil {
304 log.Println("failed to reach knotserver", err)
305 return
306 }
307
308 body, err := io.ReadAll(resp.Body)
309 if err != nil {
310 log.Fatalf("Error reading response body: %v", err)
311 return
312 }
313
314 var result types.RepoBlobResponse
315 err = json.Unmarshal(body, &result)
316 if err != nil {
317 log.Println("failed to parse response:", err)
318 return
319 }
320
321 var breadcrumbs [][]string
322 breadcrumbs = append(breadcrumbs, []string{f.RepoName, fmt.Sprintf("/%s/%s/tree/%s", f.OwnerDid(), f.RepoName, ref)})
323 if filePath != "" {
324 for idx, elem := range strings.Split(filePath, "/") {
325 breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], elem)})
326 }
327 }
328
329 user := s.auth.GetUser(r)
330 s.pages.RepoBlob(w, pages.RepoBlobParams{
331 LoggedInUser: user,
332 RepoInfo: pages.RepoInfo{
333 OwnerDid: f.OwnerDid(),
334 OwnerHandle: f.OwnerHandle(),
335 Name: f.RepoName,
336 SettingsAllowed: settingsAllowed(s, user, f),
337 },
338 RepoBlobResponse: result,
339 BreadCrumbs: breadcrumbs,
340 })
341 return
342}
343
344func (s *State) AddCollaborator(w http.ResponseWriter, r *http.Request) {
345 f, err := fullyResolvedRepo(r)
346 if err != nil {
347 log.Println("failed to get repo and knot", err)
348 return
349 }
350
351 collaborator := r.FormValue("collaborator")
352 if collaborator == "" {
353 http.Error(w, "malformed form", http.StatusBadRequest)
354 return
355 }
356
357 collaboratorIdent, err := s.resolver.ResolveIdent(r.Context(), collaborator)
358 if err != nil {
359 w.Write([]byte("failed to resolve collaborator did to a handle"))
360 return
361 }
362 log.Printf("adding %s to %s\n", collaboratorIdent.Handle.String(), f.Knot)
363
364 // TODO: create an atproto record for this
365
366 secret, err := s.db.GetRegistrationKey(f.Knot)
367 if err != nil {
368 log.Printf("no key found for domain %s: %s\n", f.Knot, err)
369 return
370 }
371
372 ksClient, err := NewSignedClient(f.Knot, secret)
373 if err != nil {
374 log.Println("failed to create client to ", f.Knot)
375 return
376 }
377
378 ksResp, err := ksClient.AddCollaborator(f.OwnerDid(), f.RepoName, collaboratorIdent.DID.String())
379 if err != nil {
380 log.Printf("failed to make request to %s: %s", f.Knot, err)
381 return
382 }
383
384 if ksResp.StatusCode != http.StatusNoContent {
385 w.Write([]byte(fmt.Sprint("knotserver failed to add collaborator: ", err)))
386 return
387 }
388
389 err = s.enforcer.AddCollaborator(collaboratorIdent.DID.String(), f.Knot, f.OwnerSlashRepo())
390 if err != nil {
391 w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err)))
392 return
393 }
394
395 err = s.db.AddCollaborator(collaboratorIdent.DID.String(), f.OwnerDid(), f.RepoName, f.Knot)
396 if err != nil {
397 w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err)))
398 return
399 }
400
401 w.Write([]byte(fmt.Sprint("added collaborator: ", collaboratorIdent.Handle.String())))
402
403}
404
405func (s *State) RepoSettings(w http.ResponseWriter, r *http.Request) {
406 f, err := fullyResolvedRepo(r)
407 if err != nil {
408 log.Println("failed to get repo and knot", err)
409 return
410 }
411
412 switch r.Method {
413 case http.MethodGet:
414 // for now, this is just pubkeys
415 user := s.auth.GetUser(r)
416 repoCollaborators, err := f.Collaborators(r.Context(), s)
417 if err != nil {
418 log.Println("failed to get collaborators", err)
419 }
420
421 isCollaboratorInviteAllowed := false
422 if user != nil {
423 ok, err := s.enforcer.IsCollaboratorInviteAllowed(user.Did, f.Knot, f.OwnerSlashRepo())
424 if err == nil && ok {
425 isCollaboratorInviteAllowed = true
426 }
427 }
428
429 s.pages.RepoSettings(w, pages.RepoSettingsParams{
430 LoggedInUser: user,
431 RepoInfo: pages.RepoInfo{
432 OwnerDid: f.OwnerDid(),
433 OwnerHandle: f.OwnerHandle(),
434 Name: f.RepoName,
435 SettingsAllowed: settingsAllowed(s, user, f),
436 },
437 Collaborators: repoCollaborators,
438 IsCollaboratorInviteAllowed: isCollaboratorInviteAllowed,
439 })
440 }
441}
442
443type FullyResolvedRepo struct {
444 Knot string
445 OwnerId identity.Identity
446 RepoName string
447 RepoAt string
448}
449
450func (f *FullyResolvedRepo) OwnerDid() string {
451 return f.OwnerId.DID.String()
452}
453
454func (f *FullyResolvedRepo) OwnerHandle() string {
455 return f.OwnerId.Handle.String()
456}
457
458func (f *FullyResolvedRepo) OwnerSlashRepo() string {
459 p, _ := securejoin.SecureJoin(f.OwnerDid(), f.RepoName)
460 return p
461}
462
463func (f *FullyResolvedRepo) Collaborators(ctx context.Context, s *State) ([]pages.Collaborator, error) {
464 repoCollaborators, err := s.enforcer.E.GetImplicitUsersForResourceByDomain(f.OwnerSlashRepo(), f.Knot)
465 if err != nil {
466 return nil, err
467 }
468
469 var collaborators []pages.Collaborator
470 for _, item := range repoCollaborators {
471 // currently only two roles: owner and member
472 var role string
473 if item[3] == "repo:owner" {
474 role = "owner"
475 } else if item[3] == "repo:collaborator" {
476 role = "collaborator"
477 } else {
478 continue
479 }
480
481 did := item[0]
482
483 c := pages.Collaborator{
484 Did: did,
485 Handle: "",
486 Role: role,
487 }
488 collaborators = append(collaborators, c)
489 }
490
491 // populate all collborators with handles
492 identsToResolve := make([]string, len(collaborators))
493 for i, collab := range collaborators {
494 identsToResolve[i] = collab.Did
495 }
496
497 resolvedIdents := s.resolver.ResolveIdents(ctx, identsToResolve)
498 for i, resolved := range resolvedIdents {
499 if resolved != nil {
500 collaborators[i].Handle = resolved.Handle.String()
501 }
502 }
503
504 return collaborators, nil
505}
506
507func (s *State) RepoSingleIssue(w http.ResponseWriter, r *http.Request) {
508 user := s.auth.GetUser(r)
509 f, err := fullyResolvedRepo(r)
510 if err != nil {
511 log.Println("failed to get repo and knot", err)
512 return
513 }
514
515 issueId := chi.URLParam(r, "issue")
516 issueIdInt, err := strconv.Atoi(issueId)
517 if err != nil {
518 http.Error(w, "bad issue id", http.StatusBadRequest)
519 log.Println("failed to parse issue id", err)
520 return
521 }
522
523 issue, comments, err := s.db.GetIssueWithComments(f.RepoAt, issueIdInt)
524 if err != nil {
525 log.Println("failed to get issue and comments", err)
526 s.pages.Notice(w, "issues", "Failed to load issue. Try again later.")
527 return
528 }
529
530 issueOwnerIdent, err := s.resolver.ResolveIdent(r.Context(), issue.OwnerDid)
531 if err != nil {
532 log.Println("failed to resolve issue owner", err)
533 }
534
535 s.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{
536 LoggedInUser: user,
537 RepoInfo: pages.RepoInfo{
538 OwnerDid: f.OwnerDid(),
539 OwnerHandle: f.OwnerHandle(),
540 Name: f.RepoName,
541 SettingsAllowed: settingsAllowed(s, user, f),
542 },
543 Issue: *issue,
544 Comments: comments,
545
546 IssueOwnerHandle: issueOwnerIdent.Handle.String(),
547 })
548
549}
550
551func (s *State) CloseIssue(w http.ResponseWriter, r *http.Request) {
552 user := s.auth.GetUser(r)
553 f, err := fullyResolvedRepo(r)
554 if err != nil {
555 log.Println("failed to get repo and knot", err)
556 return
557 }
558
559 issueId := chi.URLParam(r, "issue")
560 issueIdInt, err := strconv.Atoi(issueId)
561 if err != nil {
562 http.Error(w, "bad issue id", http.StatusBadRequest)
563 log.Println("failed to parse issue id", err)
564 return
565 }
566
567 if user.Did == f.OwnerDid() {
568 err := s.db.CloseIssue(f.RepoAt, issueIdInt)
569 if err != nil {
570 log.Println("failed to close issue", err)
571 s.pages.Notice(w, "issues", "Failed to close issue. Try again later.")
572 return
573 }
574 s.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issueIdInt))
575 return
576 } else {
577 log.Println("user is not the owner of the repo")
578 http.Error(w, "for biden", http.StatusUnauthorized)
579 return
580 }
581}
582
583func (s *State) ReopenIssue(w http.ResponseWriter, r *http.Request) {
584 user := s.auth.GetUser(r)
585 f, err := fullyResolvedRepo(r)
586 if err != nil {
587 log.Println("failed to get repo and knot", err)
588 return
589 }
590
591 issueId := chi.URLParam(r, "issue")
592 issueIdInt, err := strconv.Atoi(issueId)
593 if err != nil {
594 http.Error(w, "bad issue id", http.StatusBadRequest)
595 log.Println("failed to parse issue id", err)
596 return
597 }
598
599 if user.Did == f.OwnerDid() {
600 err := s.db.ReopenIssue(f.RepoAt, issueIdInt)
601 if err != nil {
602 log.Println("failed to reopen issue", err)
603 s.pages.Notice(w, "issues", "Failed to reopen issue. Try again later.")
604 return
605 }
606 s.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issueIdInt))
607 return
608 } else {
609 log.Println("user is not the owner of the repo")
610 http.Error(w, "forbidden", http.StatusUnauthorized)
611 return
612 }
613}
614
615func (s *State) IssueComment(w http.ResponseWriter, r *http.Request) {
616 user := s.auth.GetUser(r)
617 f, err := fullyResolvedRepo(r)
618 if err != nil {
619 log.Println("failed to get repo and knot", err)
620 return
621 }
622
623 issueId := chi.URLParam(r, "issue")
624 issueIdInt, err := strconv.Atoi(issueId)
625 if err != nil {
626 http.Error(w, "bad issue id", http.StatusBadRequest)
627 log.Println("failed to parse issue id", err)
628 return
629 }
630
631 switch r.Method {
632 case http.MethodPost:
633 body := r.FormValue("body")
634 if body == "" {
635 s.pages.Notice(w, "issue", "Body is required")
636 return
637 }
638
639 commentId := rand.IntN(1000000)
640 fmt.Println(commentId)
641 fmt.Println("comment id", commentId)
642
643 err := s.db.NewComment(&db.Comment{
644 OwnerDid: user.Did,
645 RepoAt: f.RepoAt,
646 Issue: issueIdInt,
647 CommentId: commentId,
648 Body: body,
649 })
650 if err != nil {
651 log.Println("failed to create comment", err)
652 s.pages.Notice(w, "issue-comment", "Failed to create comment.")
653 return
654 }
655
656 s.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d#comment-%d", f.OwnerSlashRepo(), issueIdInt, commentId))
657 return
658 }
659}
660
661func (s *State) RepoIssues(w http.ResponseWriter, r *http.Request) {
662 user := s.auth.GetUser(r)
663 f, err := fullyResolvedRepo(r)
664 if err != nil {
665 log.Println("failed to get repo and knot", err)
666 return
667 }
668
669 issues, err := s.db.GetIssues(f.RepoAt)
670 if err != nil {
671 log.Println("failed to get issues", err)
672 s.pages.Notice(w, "issues", "Failed to load issues. Try again later.")
673 return
674 }
675
676 s.pages.RepoIssues(w, pages.RepoIssuesParams{
677 LoggedInUser: s.auth.GetUser(r),
678 RepoInfo: pages.RepoInfo{
679 OwnerDid: f.OwnerDid(),
680 OwnerHandle: f.OwnerHandle(),
681 Name: f.RepoName,
682 SettingsAllowed: settingsAllowed(s, user, f),
683 },
684 Issues: issues,
685 })
686 return
687}
688
689func (s *State) NewIssue(w http.ResponseWriter, r *http.Request) {
690 user := s.auth.GetUser(r)
691
692 f, err := fullyResolvedRepo(r)
693 if err != nil {
694 log.Println("failed to get repo and knot", err)
695 return
696 }
697
698 switch r.Method {
699 case http.MethodGet:
700 s.pages.RepoNewIssue(w, pages.RepoNewIssueParams{
701 LoggedInUser: user,
702 RepoInfo: pages.RepoInfo{
703 Name: f.RepoName,
704 OwnerDid: f.OwnerDid(),
705 OwnerHandle: f.OwnerHandle(),
706 SettingsAllowed: settingsAllowed(s, user, f),
707 },
708 })
709 case http.MethodPost:
710 title := r.FormValue("title")
711 body := r.FormValue("body")
712
713 if title == "" || body == "" {
714 s.pages.Notice(w, "issue", "Title and body are required")
715 return
716 }
717
718 issueId, err := s.db.NewIssue(&db.Issue{
719 RepoAt: f.RepoAt,
720 Title: title,
721 Body: body,
722 OwnerDid: user.Did,
723 })
724 if err != nil {
725 log.Println("failed to create issue", err)
726 s.pages.Notice(w, "issue", "Failed to create issue.")
727 return
728 }
729
730 s.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issueId))
731 return
732 }
733}
734
735func fullyResolvedRepo(r *http.Request) (*FullyResolvedRepo, error) {
736 repoName := chi.URLParam(r, "repo")
737 knot, ok := r.Context().Value("knot").(string)
738 if !ok {
739 log.Println("malformed middleware")
740 return nil, fmt.Errorf("malformed middleware")
741 }
742 id, ok := r.Context().Value("resolvedId").(identity.Identity)
743 if !ok {
744 log.Println("malformed middleware")
745 return nil, fmt.Errorf("malformed middleware")
746 }
747
748 repoAt, ok := r.Context().Value("repoAt").(string)
749 if !ok {
750 log.Println("malformed middleware")
751 return nil, fmt.Errorf("malformed middleware")
752 }
753
754 return &FullyResolvedRepo{
755 Knot: knot,
756 OwnerId: id,
757 RepoName: repoName,
758 RepoAt: repoAt,
759 }, nil
760}
761
762func settingsAllowed(s *State, u *auth.User, f *FullyResolvedRepo) bool {
763 settingsAllowed := false
764 if u != nil {
765 ok, err := s.enforcer.IsSettingsAllowed(u.Did, f.Knot, f.OwnerSlashRepo())
766 if err == nil && ok {
767 settingsAllowed = true
768 } else {
769 log.Println(err, ok)
770 }
771 }
772
773 return settingsAllowed
774}