forked from tangled.org/core
Monorepo for Tangled — https://tangled.org
at master 3.3 kB view raw
1package repo 2 3import ( 4 "fmt" 5 "net/http" 6 "net/url" 7 "strings" 8 "time" 9 10 "tangled.org/core/api/tangled" 11 "tangled.org/core/appview/pages" 12 "tangled.org/core/appview/reporesolver" 13 xrpcclient "tangled.org/core/appview/xrpcclient" 14 "tangled.org/core/types" 15 16 indigoxrpc "github.com/bluesky-social/indigo/xrpc" 17 "github.com/go-chi/chi/v5" 18 "github.com/go-git/go-git/v5/plumbing" 19) 20 21func (rp *Repo) Tree(w http.ResponseWriter, r *http.Request) { 22 l := rp.logger.With("handler", "RepoTree") 23 f, err := rp.repoResolver.Resolve(r) 24 if err != nil { 25 l.Error("failed to fully resolve repo", "err", err) 26 return 27 } 28 ref := chi.URLParam(r, "ref") 29 ref, _ = url.PathUnescape(ref) 30 // if the tree path has a trailing slash, let's strip it 31 // so we don't 404 32 treePath := chi.URLParam(r, "*") 33 treePath, _ = url.PathUnescape(treePath) 34 treePath = strings.TrimSuffix(treePath, "/") 35 scheme := "http" 36 if !rp.config.Core.Dev { 37 scheme = "https" 38 } 39 host := fmt.Sprintf("%s://%s", scheme, f.Knot) 40 xrpcc := &indigoxrpc.Client{ 41 Host: host, 42 } 43 repo := fmt.Sprintf("%s/%s", f.Did, f.Name) 44 xrpcResp, err := tangled.RepoTree(r.Context(), xrpcc, treePath, ref, repo) 45 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 46 l.Error("failed to call XRPC repo.tree", "err", xrpcerr) 47 rp.pages.Error503(w) 48 return 49 } 50 // Convert XRPC response to internal types.RepoTreeResponse 51 files := make([]types.NiceTree, len(xrpcResp.Files)) 52 for i, xrpcFile := range xrpcResp.Files { 53 file := types.NiceTree{ 54 Name: xrpcFile.Name, 55 Mode: xrpcFile.Mode, 56 Size: int64(xrpcFile.Size), 57 } 58 // Convert last commit info if present 59 if xrpcFile.Last_commit != nil { 60 commitWhen, _ := time.Parse(time.RFC3339, xrpcFile.Last_commit.When) 61 file.LastCommit = &types.LastCommitInfo{ 62 Hash: plumbing.NewHash(xrpcFile.Last_commit.Hash), 63 Message: xrpcFile.Last_commit.Message, 64 When: commitWhen, 65 } 66 } 67 files[i] = file 68 } 69 result := types.RepoTreeResponse{ 70 Ref: xrpcResp.Ref, 71 Files: files, 72 } 73 if xrpcResp.Parent != nil { 74 result.Parent = *xrpcResp.Parent 75 } 76 if xrpcResp.Dotdot != nil { 77 result.DotDot = *xrpcResp.Dotdot 78 } 79 if xrpcResp.Readme != nil { 80 result.ReadmeFileName = xrpcResp.Readme.Filename 81 result.Readme = xrpcResp.Readme.Contents 82 } 83 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f) 84 // redirects tree paths trying to access a blob; in this case the result.Files is unpopulated, 85 // so we can safely redirect to the "parent" (which is the same file). 86 if len(result.Files) == 0 && result.Parent == treePath { 87 redirectTo := fmt.Sprintf("/%s/blob/%s/%s", ownerSlashRepo, url.PathEscape(ref), result.Parent) 88 http.Redirect(w, r, redirectTo, http.StatusFound) 89 return 90 } 91 user := rp.oauth.GetUser(r) 92 var breadcrumbs [][]string 93 breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", ownerSlashRepo, url.PathEscape(ref))}) 94 if treePath != "" { 95 for idx, elem := range strings.Split(treePath, "/") { 96 breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], url.PathEscape(elem))}) 97 } 98 } 99 sortFiles(result.Files) 100 101 rp.pages.RepoTree(w, pages.RepoTreeParams{ 102 LoggedInUser: user, 103 BreadCrumbs: breadcrumbs, 104 TreePath: treePath, 105 RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 106 RepoTreeResponse: result, 107 }) 108}