forked from tangled.org/core
Monorepo for Tangled

appview: rework RepoLanguages

- move endpoint on knot to /{did}/{name}/languages
- remove buggy percentage calculation
- fix endpoint to work on empty repos
- allowing calling endpoint with empty ref

authored by oppi.li and committed by Tangled ff73ca23 d759587b

Changed files
+59 -84
appview
knotclient
pages
state
knotserver
types
+17 -13
appview/knotclient/signer.go
··· 106 106 return s.client.Do(req) 107 107 } 108 108 109 - func (s *SignedClient) RepoLanguages(ownerDid, source, name, branch string) (*types.RepoLanguageResponse, error) { 109 + func (s *SignedClient) RepoLanguages(ownerDid, repoName, ref string) (*types.RepoLanguageResponse, error) { 110 110 const ( 111 111 Method = "GET" 112 112 ) 113 - endpoint := fmt.Sprintf("/repo/languages/%s", url.PathEscape(branch)) 113 + endpoint := fmt.Sprintf("/%s/%s/languages/%s", ownerDid, repoName, url.PathEscape(ref)) 114 114 115 - body, _ := json.Marshal(map[string]any{ 116 - "did": ownerDid, 117 - "source": source, 118 - "name": name, 119 - }) 120 - 121 - req, err := s.newRequest(Method, endpoint, body) 115 + req, err := s.newRequest(Method, endpoint, nil) 122 116 if err != nil { 123 117 return nil, err 124 118 } ··· 128 122 return nil, err 129 123 } 130 124 131 - var languagePercentages types.RepoLanguageResponse 132 - if err := json.NewDecoder(resp.Body).Decode(&languagePercentages); err != nil { 133 - log.Printf("failed to decode fork status: %s", err) 125 + var result types.RepoLanguageResponse 126 + if resp.StatusCode != http.StatusOK { 127 + log.Println("failed to calculate languages", resp.Status) 128 + return &types.RepoLanguageResponse{}, nil 129 + } 130 + 131 + body, err := io.ReadAll(resp.Body) 132 + if err != nil { 134 133 return nil, err 135 134 } 136 135 137 - return &languagePercentages, nil 136 + err = json.Unmarshal(body, &result) 137 + if err != nil { 138 + return nil, err 139 + } 140 + 141 + return &result, nil 138 142 } 139 143 140 144 func (s *SignedClient) RepoForkAheadBehind(ownerDid, source, name, branch, hiddenRef string) (*http.Response, error) {
+12 -12
appview/pages/pages.go
··· 403 403 } 404 404 405 405 type RepoIndexParams struct { 406 - LoggedInUser *oauth.User 407 - RepoInfo repoinfo.RepoInfo 408 - Active string 409 - TagMap map[string][]string 410 - CommitsTrunc []*object.Commit 411 - TagsTrunc []*types.TagReference 412 - BranchesTrunc []types.Branch 413 - ForkInfo *types.ForkInfo 406 + LoggedInUser *oauth.User 407 + RepoInfo repoinfo.RepoInfo 408 + Active string 409 + TagMap map[string][]string 410 + CommitsTrunc []*object.Commit 411 + TagsTrunc []*types.TagReference 412 + BranchesTrunc []types.Branch 413 + ForkInfo *types.ForkInfo 414 + HTMLReadme template.HTML 415 + Raw bool 416 + EmailToDidOrHandle map[string]string 417 + Languages *types.RepoLanguageResponse 414 418 types.RepoIndexResponse 415 - HTMLReadme template.HTML 416 - Raw bool 417 - EmailToDidOrHandle map[string]string 418 - LanguagePercentages map[string]float64 419 419 } 420 420 421 421 func (p *Pages) RepoIndexPage(w io.Writer, params RepoIndexParams) error {
+13 -14
appview/state/repo.go
··· 138 138 139 139 var forkInfo *types.ForkInfo 140 140 if user != nil && (repoInfo.Roles.IsOwner() || repoInfo.Roles.IsCollaborator()) { 141 - forkInfo, err = getForkInfo(repoInfo, s, f, w, user, signedClient) 141 + forkInfo, err = getForkInfo(repoInfo, s, f, user, signedClient) 142 142 if err != nil { 143 143 log.Printf("Failed to fetch fork information: %v", err) 144 144 return 145 145 } 146 146 } 147 147 148 - repoLanguages, err := signedClient.RepoLanguages(user.Did, string(f.RepoAt), repoInfo.Name, f.Ref) 148 + repoLanguages, err := signedClient.RepoLanguages(f.OwnerDid(), f.RepoName, ref) 149 149 if err != nil { 150 150 log.Printf("failed to compute language percentages: %s", err) 151 - return 151 + // non-fatal 152 152 } 153 153 154 154 s.pages.RepoIndexPage(w, pages.RepoIndexParams{ 155 - LoggedInUser: user, 156 - RepoInfo: repoInfo, 157 - TagMap: tagMap, 158 - RepoIndexResponse: result, 159 - CommitsTrunc: commitsTrunc, 160 - TagsTrunc: tagsTrunc, 161 - ForkInfo: forkInfo, 162 - BranchesTrunc: branchesTrunc, 163 - EmailToDidOrHandle: EmailToDidOrHandle(s, emails), 164 - LanguagePercentages: repoLanguages.Languages, 155 + LoggedInUser: user, 156 + RepoInfo: repoInfo, 157 + TagMap: tagMap, 158 + RepoIndexResponse: result, 159 + CommitsTrunc: commitsTrunc, 160 + TagsTrunc: tagsTrunc, 161 + ForkInfo: forkInfo, 162 + BranchesTrunc: branchesTrunc, 163 + EmailToDidOrHandle: EmailToDidOrHandle(s, emails), 164 + Languages: repoLanguages, 165 165 }) 166 166 return 167 167 } ··· 170 170 repoInfo repoinfo.RepoInfo, 171 171 s *State, 172 172 f *FullyResolvedRepo, 173 - w http.ResponseWriter, 174 173 user *oauth.User, 175 174 signedClient *knotclient.SignedClient, 176 175 ) (*types.ForkInfo, error) {
+6 -1
knotserver/handler.go
··· 80 80 r.Post("/add", h.AddRepoCollaborator) 81 81 }) 82 82 83 + r.Route("/languages", func(r chi.Router) { 84 + r.With(h.VerifySignature) 85 + r.Get("/", h.RepoLanguages) 86 + r.Get("/{ref}", h.RepoLanguages) 87 + }) 88 + 83 89 r.Get("/", h.RepoIndex) 84 90 r.Get("/info/refs", h.InfoRefs) 85 91 r.Post("/git-upload-pack", h.UploadPack) ··· 126 132 r.Route("/repo", func(r chi.Router) { 127 133 r.Use(h.VerifySignature) 128 134 r.Put("/new", h.NewRepo) 129 - r.Get("/languages/{branch}", h.RepoLanguages) 130 135 r.Delete("/", h.RemoveRepo) 131 136 r.Route("/fork", func(r chi.Router) { 132 137 r.Post("/", h.RepoFork)
+10 -43
knotserver/routes.go
··· 715 715 } 716 716 717 717 func (h *Handle) RepoLanguages(w http.ResponseWriter, r *http.Request) { 718 - l := h.l.With("handler", "RepoForkSync") 718 + path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 719 + ref := chi.URLParam(r, "ref") 720 + ref, _ = url.PathUnescape(ref) 719 721 720 - data := struct { 721 - Did string `json:"did"` 722 - Source string `json:"source"` 723 - Name string `json:"name,omitempty"` 724 - }{} 722 + l := h.l.With("handler", "RepoLanguages") 725 723 726 - if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 727 - writeError(w, "invalid request body", http.StatusBadRequest) 728 - return 729 - } 730 - 731 - did := data.Did 732 - source := data.Source 733 - 734 - if did == "" || source == "" { 735 - l.Error("invalid request body, empty did or name") 736 - w.WriteHeader(http.StatusBadRequest) 737 - return 738 - } 739 - 740 - var name string 741 - if data.Name != "" { 742 - name = data.Name 743 - } else { 744 - name = filepath.Base(source) 745 - } 746 - 747 - branch := chi.URLParam(r, "branch") 748 - branch, _ = url.PathUnescape(branch) 749 - 750 - relativeRepoPath := filepath.Join(did, name) 751 - repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath) 752 - 753 - gr, err := git.Open(repoPath, branch) 724 + gr, err := git.Open(path, ref) 754 725 if err != nil { 755 - log.Println(err) 726 + l.Error("opening repo", "error", err.Error()) 756 727 notFound(w) 757 728 return 758 729 } 759 730 760 731 languageFileCount := make(map[string]int) 761 - languagePercentage := make(map[string]float64) 762 732 763 733 err = recurseEntireTree(gr, func(absPath string) { 764 734 lang, safe := enry.GetLanguageByExtension(absPath) ··· 782 752 } 783 753 }, "") 784 754 if err != nil { 785 - log.Println(err) 755 + l.Error("failed to recurse file tree", "error", err.Error()) 786 756 writeError(w, err.Error(), http.StatusNoContent) 787 757 return 788 758 } 789 759 790 - for path, fileCount := range languageFileCount { 791 - percentage := float64(fileCount) / float64(len(languageFileCount)) * 100.0 792 - languagePercentage[path] = percentage 793 - } 760 + resp := types.RepoLanguageResponse{Languages: languageFileCount} 794 761 795 - w.Header().Set("Content-Type", "application/json") 796 - json.NewEncoder(w).Encode(types.RepoLanguageResponse{Languages: languagePercentage}) 762 + writeJSON(w, resp) 763 + return 797 764 } 798 765 799 766 func recurseEntireTree(git *git.GitRepo, callback func(absPath string), filePath string) error {
+1 -1
types/repo.go
··· 112 112 113 113 type RepoLanguageResponse struct { 114 114 // Language: Percentage 115 - Languages map[string]float64 `json:"languages"` 115 + Languages map[string]int `json:"languages"` 116 116 }