this repo has no description
1package reporesolver 2 3import ( 4 "context" 5 "database/sql" 6 "errors" 7 "fmt" 8 "log" 9 "net/http" 10 "path" 11 "regexp" 12 "strings" 13 14 "github.com/bluesky-social/indigo/atproto/identity" 15 "github.com/go-chi/chi/v5" 16 "tangled.org/core/appview/config" 17 "tangled.org/core/appview/db" 18 "tangled.org/core/appview/models" 19 "tangled.org/core/appview/oauth" 20 "tangled.org/core/appview/pages" 21 "tangled.org/core/appview/pages/repoinfo" 22 "tangled.org/core/idresolver" 23 "tangled.org/core/rbac" 24) 25 26type ResolvedRepo struct { 27 models.Repo 28 OwnerId identity.Identity 29 CurrentDir string 30 Ref string 31 32 rr *RepoResolver 33} 34 35type RepoResolver struct { 36 config *config.Config 37 enforcer *rbac.Enforcer 38 idResolver *idresolver.Resolver 39 execer db.Execer 40} 41 42func New(config *config.Config, enforcer *rbac.Enforcer, resolver *idresolver.Resolver, execer db.Execer) *RepoResolver { 43 return &RepoResolver{config: config, enforcer: enforcer, idResolver: resolver, execer: execer} 44} 45 46// NOTE: this... should not even be here. the entire package will be removed in future refactor 47func GetBaseRepoPath(r *http.Request, repo *models.Repo) string { 48 var ( 49 user = chi.URLParam(r, "user") 50 name = chi.URLParam(r, "repo") 51 ) 52 if user == "" || name == "" { 53 return repo.DidSlashRepo() 54 } 55 return path.Join(user, name) 56} 57 58func (rr *RepoResolver) Resolve(r *http.Request) (*ResolvedRepo, error) { 59 repo, ok := r.Context().Value("repo").(*models.Repo) 60 if !ok { 61 log.Println("malformed middleware: `repo` not exist in context") 62 return nil, fmt.Errorf("malformed middleware") 63 } 64 id, ok := r.Context().Value("resolvedId").(identity.Identity) 65 if !ok { 66 log.Println("malformed middleware") 67 return nil, fmt.Errorf("malformed middleware") 68 } 69 70 currentDir := path.Dir(extractPathAfterRef(r.URL.EscapedPath())) 71 ref := chi.URLParam(r, "ref") 72 73 return &ResolvedRepo{ 74 Repo: *repo, 75 OwnerId: id, 76 CurrentDir: currentDir, 77 Ref: ref, 78 79 rr: rr, 80 }, nil 81} 82 83func (f *ResolvedRepo) Collaborators(ctx context.Context) ([]pages.Collaborator, error) { 84 repoCollaborators, err := f.rr.enforcer.E.GetImplicitUsersForResourceByDomain(f.DidSlashRepo(), f.Knot) 85 if err != nil { 86 return nil, err 87 } 88 89 var collaborators []pages.Collaborator 90 for _, item := range repoCollaborators { 91 // currently only two roles: owner and member 92 var role string 93 switch item[3] { 94 case "repo:owner": 95 role = "owner" 96 case "repo:collaborator": 97 role = "collaborator" 98 default: 99 continue 100 } 101 102 did := item[0] 103 104 c := pages.Collaborator{ 105 Did: did, 106 Role: role, 107 } 108 collaborators = append(collaborators, c) 109 } 110 111 return collaborators, nil 112} 113 114// this function is a bit weird since it now returns RepoInfo from an entirely different 115// package. we should refactor this or get rid of RepoInfo entirely. 116func (f *ResolvedRepo) RepoInfo(user *oauth.User) repoinfo.RepoInfo { 117 repoAt := f.RepoAt() 118 isStarred := false 119 if user != nil { 120 isStarred = db.GetStarStatus(f.rr.execer, user.Did, repoAt) 121 } 122 123 starCount, err := db.GetStarCount(f.rr.execer, repoAt) 124 if err != nil { 125 log.Println("failed to get star count for ", repoAt) 126 } 127 issueCount, err := db.GetIssueCount(f.rr.execer, repoAt) 128 if err != nil { 129 log.Println("failed to get issue count for ", repoAt) 130 } 131 pullCount, err := db.GetPullCount(f.rr.execer, repoAt) 132 if err != nil { 133 log.Println("failed to get issue count for ", repoAt) 134 } 135 source, err := db.GetRepoSource(f.rr.execer, repoAt) 136 if errors.Is(err, sql.ErrNoRows) { 137 source = "" 138 } else if err != nil { 139 log.Println("failed to get repo source for ", repoAt, err) 140 } 141 142 var sourceRepo *models.Repo 143 if source != "" { 144 sourceRepo, err = db.GetRepoByAtUri(f.rr.execer, source) 145 if err != nil { 146 log.Println("failed to get repo by at uri", err) 147 } 148 } 149 150 knot := f.Knot 151 152 repoInfo := repoinfo.RepoInfo{ 153 OwnerDid: f.OwnerId.DID.String(), 154 OwnerHandle: f.OwnerId.Handle.String(), 155 Name: f.Name, 156 Rkey: f.Rkey, 157 Description: f.Description, 158 Website: f.Website, 159 Topics: f.Topics, 160 IsStarred: isStarred, 161 Knot: knot, 162 Spindle: f.Spindle, 163 Roles: f.RolesInRepo(user), 164 Stats: models.RepoStats{ 165 StarCount: starCount, 166 IssueCount: issueCount, 167 PullCount: pullCount, 168 }, 169 CurrentDir: f.CurrentDir, 170 Ref: f.Ref, 171 } 172 173 if sourceRepo != nil { 174 repoInfo.Source = sourceRepo 175 } 176 177 return repoInfo 178} 179 180func (f *ResolvedRepo) RolesInRepo(u *oauth.User) repoinfo.RolesInRepo { 181 if u != nil { 182 r := f.rr.enforcer.GetPermissionsInRepo(u.Did, f.Knot, f.DidSlashRepo()) 183 return repoinfo.RolesInRepo{Roles: r} 184 } else { 185 return repoinfo.RolesInRepo{} 186 } 187} 188 189// extractPathAfterRef gets the actual repository path 190// after the ref. for example: 191// 192// /@icyphox.sh/foorepo/blob/main/abc/xyz/ => abc/xyz/ 193func extractPathAfterRef(fullPath string) string { 194 fullPath = strings.TrimPrefix(fullPath, "/") 195 196 // match blob/, tree/, or raw/ followed by any ref and then a slash 197 // 198 // captures everything after the final slash 199 pattern := `(?:blob|tree|raw)/[^/]+/(.*)$` 200 201 re := regexp.MustCompile(pattern) 202 matches := re.FindStringSubmatch(fullPath) 203 204 if len(matches) > 1 { 205 return matches[1] 206 } 207 208 return "" 209}