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 var sourceHandle *identity.Identity 151 if sourceRepo != nil { 152 sourceHandle, err = f.rr.idResolver.ResolveIdent(context.Background(), sourceRepo.Did) 153 if err != nil { 154 log.Println("failed to resolve source repo", err) 155 } 156 } 157 158 knot := f.Knot 159 160 repoInfo := repoinfo.RepoInfo{ 161 OwnerDid: f.OwnerId.DID.String(), 162 OwnerHandle: f.OwnerId.Handle.String(), 163 Name: f.Name, 164 Rkey: f.Rkey, 165 RepoAt: repoAt, 166 Description: f.Description, 167 Website: f.Website, 168 Topics: f.Topics, 169 IsStarred: isStarred, 170 Knot: knot, 171 Spindle: f.Spindle, 172 Roles: f.RolesInRepo(user), 173 Stats: models.RepoStats{ 174 StarCount: starCount, 175 IssueCount: issueCount, 176 PullCount: pullCount, 177 }, 178 CurrentDir: f.CurrentDir, 179 Ref: f.Ref, 180 } 181 182 if sourceRepo != nil { 183 repoInfo.Source = sourceRepo 184 repoInfo.SourceHandle = sourceHandle.Handle.String() 185 } 186 187 return repoInfo 188} 189 190func (f *ResolvedRepo) RolesInRepo(u *oauth.User) repoinfo.RolesInRepo { 191 if u != nil { 192 r := f.rr.enforcer.GetPermissionsInRepo(u.Did, f.Knot, f.DidSlashRepo()) 193 return repoinfo.RolesInRepo{Roles: r} 194 } else { 195 return repoinfo.RolesInRepo{} 196 } 197} 198 199// extractPathAfterRef gets the actual repository path 200// after the ref. for example: 201// 202// /@icyphox.sh/foorepo/blob/main/abc/xyz/ => abc/xyz/ 203func extractPathAfterRef(fullPath string) string { 204 fullPath = strings.TrimPrefix(fullPath, "/") 205 206 // match blob/, tree/, or raw/ followed by any ref and then a slash 207 // 208 // captures everything after the final slash 209 pattern := `(?:blob|tree|raw)/[^/]+/(.*)$` 210 211 re := regexp.MustCompile(pattern) 212 matches := re.FindStringSubmatch(fullPath) 213 214 if len(matches) > 1 { 215 return matches[1] 216 } 217 218 return "" 219}