Monorepo for Tangled tangled.org
3

Configure Feed

Select the types of activity you want to include in your feed.

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/cache" 14 "tangled.org/core/appview/config" 15 "tangled.org/core/appview/db" 16 "tangled.org/core/appview/knotacl" 17 "tangled.org/core/appview/models" 18 "tangled.org/core/appview/oauth" 19 "tangled.org/core/appview/pages/repoinfo" 20) 21 22var ( 23 blobPattern = regexp.MustCompile(`blob/[^/]+/(.*)$`) 24 treePattern = regexp.MustCompile(`tree/[^/]+/(.*)$`) 25) 26 27type RepoResolver struct { 28 config *config.Config 29 acl *knotacl.Service 30 execer db.Execer 31 rdb *cache.Cache 32} 33 34func New(config *config.Config, acl *knotacl.Service, execer db.Execer, rdb *cache.Cache) *RepoResolver { 35 return &RepoResolver{config: config, acl: acl, execer: execer, rdb: rdb} 36} 37 38func CanonicalRepoPath(handle string, repo *models.Repo) string { 39 return path.Join(handle, repo.Slug()) 40} 41 42func CanonicalRedirectTarget(req *http.Request, canonical string) string { 43 parts := strings.SplitN(strings.TrimPrefix(req.URL.Path, "/"), "/", 3) 44 target := "/" + canonical 45 if len(parts) == 3 { 46 target += "/" + parts[2] 47 } 48 if req.URL.RawQuery != "" { 49 target += "?" + req.URL.RawQuery 50 } 51 return target 52} 53 54// NOTE: this... should not even be here. the entire package will be removed in future refactor 55func GetBaseRepoPath(r *http.Request, repo *models.Repo) string { 56 if id, ok := r.Context().Value("resolvedId").(identity.Identity); ok && !id.Handle.IsInvalidHandle() { 57 if h := id.Handle.String(); h != "" { 58 return CanonicalRepoPath(h, repo) 59 } 60 } 61 var ( 62 user = chi.URLParam(r, "user") 63 name = chi.URLParam(r, "repo") 64 ) 65 if user != "" && name != "" { 66 return path.Join(user, name) 67 } 68 if repo.Name != "" { 69 return path.Join(repo.Did, repo.Name) 70 } 71 return repo.RepoIdentifier() 72} 73 74// TODO: move this out of `RepoResolver` struct 75func (rr *RepoResolver) Resolve(r *http.Request) (*models.Repo, error) { 76 repo, ok := r.Context().Value("repo").(*models.Repo) 77 if !ok { 78 log.Println("malformed middleware: `repo` not exist in context") 79 return nil, fmt.Errorf("malformed middleware") 80 } 81 82 return repo, nil 83} 84 85// 1. [x] replace `RepoInfo` to `reporesolver.GetRepoInfo(r *http.Request, repo, user)` 86// 2. [x] remove `rr`, `CurrentDir`, `Ref` fields from `ResolvedRepo` 87// 3. [x] remove `ResolvedRepo` 88// 4. [ ] replace reporesolver to reposervice 89func (rr *RepoResolver) GetRepoInfo(r *http.Request, user *oauth.MultiAccountUser) repoinfo.RepoInfo { 90 ownerId, ook := r.Context().Value("resolvedId").(identity.Identity) 91 repo, rok := r.Context().Value("repo").(*models.Repo) 92 if !ook || !rok { 93 log.Println("malformed request, failed to get repo from context") 94 } 95 96 // get dir/ref 97 currentDir := extractCurrentDir(r.URL.EscapedPath()) 98 ref := chi.URLParam(r, "ref") 99 100 repoDid := repo.RepoDid 101 isStarred := false 102 roles := repoinfo.RolesInRepo{} 103 if user != nil { 104 isStarred = db.GetStarStatus(rr.execer, user.Did, repoDid) 105 roles = rr.acl.RolesInRepo(r.Context(), repo, user.Did) 106 } 107 108 stats := repo.RepoStats 109 if stats == nil { 110 starCount, starErr := db.GetStarCount(rr.execer, models.StarSubjectRepo, repoDid) 111 if starErr != nil { 112 log.Println("failed to get star count for ", repoDid) 113 } 114 issueCount, err := db.GetIssueCount(rr.execer, repoDid) 115 if err != nil { 116 log.Println("failed to get issue count for ", repoDid) 117 } 118 pullCount, err := db.GetPullCount(rr.execer, repoDid) 119 if err != nil { 120 log.Println("failed to get pull count for ", repoDid) 121 } 122 stats = &models.RepoStats{ 123 StarCount: starCount, 124 IssueCount: issueCount, 125 PullCount: pullCount, 126 } 127 } 128 129 var sourceRepo *models.Repo 130 var err error 131 if repo.Source != "" { 132 if strings.HasPrefix(repo.Source, "did:") { 133 sourceRepo, err = db.GetRepoByDid(rr.execer, repo.Source) 134 } else { 135 sourceRepo, err = db.GetRepoByAtUri(rr.execer, repo.Source) 136 } 137 if err != nil { 138 log.Println("failed to get source repo", err) 139 } 140 } 141 142 ownerHandle := ownerId.Handle.String() 143 if h := cache.LookupPreferredHandle(r.Context(), rr.rdb, rr.execer, ownerId.DID.String()); h != "" { 144 ownerHandle = h 145 } 146 147 repoInfo := repoinfo.RepoInfo{ 148 // this is basically a models.Repo 149 OwnerDid: ownerId.DID.String(), 150 OwnerHandle: ownerHandle, 151 RepoDid: repo.RepoDid, 152 Name: repo.Name, 153 Rkey: repo.Rkey, 154 Description: repo.Description, 155 Website: repo.Website, 156 Topics: repo.Topics, 157 Knot: repo.Knot, 158 Spindle: repo.Spindle, 159 Stats: *stats, 160 161 // fork repo upstream 162 Source: sourceRepo, 163 164 // page context 165 CurrentDir: currentDir, 166 Ref: ref, 167 168 // info related to the session 169 IsStarred: isStarred, 170 Roles: roles, 171 } 172 173 return repoInfo 174} 175 176// extractCurrentDir gets the current directory for markdown link resolution. 177// for blob paths, returns the parent dir. for tree paths, returns the path itself. 178// 179// /@user/repo/blob/main/docs/README.md => docs 180// /@user/repo/tree/main/docs => docs 181func extractCurrentDir(fullPath string) string { 182 fullPath = strings.TrimPrefix(fullPath, "/") 183 184 if matches := blobPattern.FindStringSubmatch(fullPath); len(matches) > 1 { 185 return path.Dir(matches[1]) 186 } 187 188 if matches := treePattern.FindStringSubmatch(fullPath); len(matches) > 1 { 189 dir := strings.TrimSuffix(matches[1], "/") 190 if dir == "" { 191 return "." 192 } 193 return dir 194 } 195 196 return "." 197}