Monorepo for Tangled
at push-pmqotzqwskqq 190 lines 5.2 kB view raw
1package reporesolver 2 3import ( 4 "fmt" 5 "log" 6 "net/http" 7 "path" 8 "regexp" 9 "strings" 10 11 "github.com/bluesky-social/indigo/atproto/identity" 12 "github.com/go-chi/chi/v5" 13 "tangled.org/core/appview/config" 14 "tangled.org/core/appview/db" 15 "tangled.org/core/appview/models" 16 "tangled.org/core/appview/oauth" 17 "tangled.org/core/appview/pages/repoinfo" 18 "tangled.org/core/rbac" 19) 20 21type RepoResolver struct { 22 config *config.Config 23 enforcer *rbac.Enforcer 24 execer db.Execer 25} 26 27func New(config *config.Config, enforcer *rbac.Enforcer, execer db.Execer) *RepoResolver { 28 return &RepoResolver{config: config, enforcer: enforcer, execer: execer} 29} 30 31// NOTE: this... should not even be here. the entire package will be removed in future refactor 32func GetBaseRepoPath(r *http.Request, repo *models.Repo) string { 33 if repo.RepoDid != "" { 34 return repo.RepoDid 35 } 36 var ( 37 user = chi.URLParam(r, "user") 38 name = chi.URLParam(r, "repo") 39 ) 40 if user == "" || name == "" { 41 return repo.DidSlashRepo() 42 } 43 return path.Join(user, name) 44} 45 46// TODO: move this out of `RepoResolver` struct 47func (rr *RepoResolver) Resolve(r *http.Request) (*models.Repo, error) { 48 repo, ok := r.Context().Value("repo").(*models.Repo) 49 if !ok { 50 log.Println("malformed middleware: `repo` not exist in context") 51 return nil, fmt.Errorf("malformed middleware") 52 } 53 54 return repo, nil 55} 56 57// 1. [x] replace `RepoInfo` to `reporesolver.GetRepoInfo(r *http.Request, repo, user)` 58// 2. [x] remove `rr`, `CurrentDir`, `Ref` fields from `ResolvedRepo` 59// 3. [x] remove `ResolvedRepo` 60// 4. [ ] replace reporesolver to reposervice 61func (rr *RepoResolver) GetRepoInfo(r *http.Request, user *oauth.MultiAccountUser) repoinfo.RepoInfo { 62 ownerId, ook := r.Context().Value("resolvedId").(identity.Identity) 63 repo, rok := r.Context().Value("repo").(*models.Repo) 64 if !ook || !rok { 65 log.Println("malformed request, failed to get repo from context") 66 } 67 68 // get dir/ref 69 currentDir := extractCurrentDir(r.URL.EscapedPath()) 70 ref := chi.URLParam(r, "ref") 71 72 repoAt := repo.RepoAt() 73 isStarred := false 74 roles := repoinfo.RolesInRepo{} 75 if user != nil && user.Active != nil { 76 isStarred = db.GetStarStatus(rr.execer, user.Active.Did, repoAt) 77 roles.Roles = rr.enforcer.GetPermissionsInRepo(user.Active.Did, repo.Knot, repo.DidSlashRepo()) 78 } 79 80 stats := repo.RepoStats 81 if stats == nil { 82 var starCount int 83 var starErr error 84 if repo.RepoDid != "" { 85 starCount, starErr = db.GetStarCountByRepoDid(rr.execer, repo.RepoDid, repoAt) 86 } else { 87 starCount, starErr = db.GetStarCount(rr.execer, repoAt) 88 } 89 if starErr != nil { 90 log.Println("failed to get star count for ", repoAt) 91 } 92 issueCount, err := db.GetIssueCount(rr.execer, repoAt) 93 if err != nil { 94 log.Println("failed to get issue count for ", repoAt) 95 } 96 pullCount, err := db.GetPullCount(rr.execer, repoAt) 97 if err != nil { 98 log.Println("failed to get pull count for ", repoAt) 99 } 100 stats = &models.RepoStats{ 101 StarCount: starCount, 102 IssueCount: issueCount, 103 PullCount: pullCount, 104 } 105 } 106 107 var sourceRepo *models.Repo 108 var err error 109 if repo.Source != "" { 110 sourceRepo, err = db.GetRepoByAtUri(rr.execer, repo.Source) 111 if err != nil { 112 log.Println("failed to get repo by at uri", err) 113 } 114 } 115 116 repoInfo := repoinfo.RepoInfo{ 117 // this is basically a models.Repo 118 OwnerDid: ownerId.DID.String(), 119 OwnerHandle: ownerId.Handle.String(), 120 RepoDid: repo.RepoDid, 121 Name: repo.Name, 122 Rkey: repo.Rkey, 123 Description: repo.Description, 124 Website: repo.Website, 125 Topics: repo.Topics, 126 Knot: repo.Knot, 127 Spindle: repo.Spindle, 128 Stats: *stats, 129 130 // fork repo upstream 131 Source: sourceRepo, 132 133 // page context 134 CurrentDir: currentDir, 135 Ref: ref, 136 137 // info related to the session 138 IsStarred: isStarred, 139 Roles: roles, 140 } 141 142 return repoInfo 143} 144 145// extractCurrentDir gets the current directory for markdown link resolution. 146// for blob paths, returns the parent dir. for tree paths, returns the path itself. 147// 148// /@user/repo/blob/main/docs/README.md => docs 149// /@user/repo/tree/main/docs => docs 150func extractCurrentDir(fullPath string) string { 151 fullPath = strings.TrimPrefix(fullPath, "/") 152 153 blobPattern := regexp.MustCompile(`blob/[^/]+/(.*)$`) 154 if matches := blobPattern.FindStringSubmatch(fullPath); len(matches) > 1 { 155 return path.Dir(matches[1]) 156 } 157 158 treePattern := regexp.MustCompile(`tree/[^/]+/(.*)$`) 159 if matches := treePattern.FindStringSubmatch(fullPath); len(matches) > 1 { 160 dir := strings.TrimSuffix(matches[1], "/") 161 if dir == "" { 162 return "." 163 } 164 return dir 165 } 166 167 return "." 168} 169 170// extractPathAfterRef gets the actual repository path 171// after the ref. for example: 172// 173// /@icyphox.sh/foorepo/blob/main/abc/xyz/ => abc/xyz/ 174func extractPathAfterRef(fullPath string) string { 175 fullPath = strings.TrimPrefix(fullPath, "/") 176 177 // match blob/, tree/, or raw/ followed by any ref and then a slash 178 // 179 // captures everything after the final slash 180 pattern := `(?:blob|tree|raw)/[^/]+/(.*)$` 181 182 re := regexp.MustCompile(pattern) 183 matches := re.FindStringSubmatch(fullPath) 184 185 if len(matches) > 1 { 186 return matches[1] 187 } 188 189 return "" 190}