forked from tangled.org/core
this repo has no description
at master 6.0 kB view raw
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 securejoin "github.com/cyphar/filepath-securejoin" 16 "github.com/go-chi/chi/v5" 17 "tangled.org/core/appview/config" 18 "tangled.org/core/appview/db" 19 "tangled.org/core/appview/models" 20 "tangled.org/core/appview/oauth" 21 "tangled.org/core/appview/pages" 22 "tangled.org/core/appview/pages/repoinfo" 23 "tangled.org/core/idresolver" 24 "tangled.org/core/rbac" 25) 26 27type ResolvedRepo struct { 28 models.Repo 29 OwnerId identity.Identity 30 CurrentDir string 31 Ref string 32 33 rr *RepoResolver 34} 35 36type RepoResolver struct { 37 config *config.Config 38 enforcer *rbac.Enforcer 39 idResolver *idresolver.Resolver 40 execer db.Execer 41} 42 43func New(config *config.Config, enforcer *rbac.Enforcer, resolver *idresolver.Resolver, execer db.Execer) *RepoResolver { 44 return &RepoResolver{config: config, enforcer: enforcer, idResolver: resolver, execer: execer} 45} 46 47func (rr *RepoResolver) Resolve(r *http.Request) (*ResolvedRepo, 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 id, ok := r.Context().Value("resolvedId").(identity.Identity) 54 if !ok { 55 log.Println("malformed middleware") 56 return nil, fmt.Errorf("malformed middleware") 57 } 58 59 currentDir := path.Dir(extractPathAfterRef(r.URL.EscapedPath())) 60 ref := chi.URLParam(r, "ref") 61 62 return &ResolvedRepo{ 63 Repo: *repo, 64 OwnerId: id, 65 CurrentDir: currentDir, 66 Ref: ref, 67 68 rr: rr, 69 }, nil 70} 71 72func (f *ResolvedRepo) OwnerDid() string { 73 return f.OwnerId.DID.String() 74} 75 76func (f *ResolvedRepo) OwnerHandle() string { 77 return f.OwnerId.Handle.String() 78} 79 80func (f *ResolvedRepo) OwnerSlashRepo() string { 81 handle := f.OwnerId.Handle 82 83 var p string 84 if handle != "" && !handle.IsInvalidHandle() { 85 p, _ = securejoin.SecureJoin(fmt.Sprintf("@%s", handle), f.Name) 86 } else { 87 p, _ = securejoin.SecureJoin(f.OwnerDid(), f.Name) 88 } 89 90 return p 91} 92 93func (f *ResolvedRepo) Collaborators(ctx context.Context) ([]pages.Collaborator, error) { 94 repoCollaborators, err := f.rr.enforcer.E.GetImplicitUsersForResourceByDomain(f.DidSlashRepo(), f.Knot) 95 if err != nil { 96 return nil, err 97 } 98 99 var collaborators []pages.Collaborator 100 for _, item := range repoCollaborators { 101 // currently only two roles: owner and member 102 var role string 103 switch item[3] { 104 case "repo:owner": 105 role = "owner" 106 case "repo:collaborator": 107 role = "collaborator" 108 default: 109 continue 110 } 111 112 did := item[0] 113 114 c := pages.Collaborator{ 115 Did: did, 116 Handle: "", 117 Role: role, 118 } 119 collaborators = append(collaborators, c) 120 } 121 122 // populate all collborators with handles 123 identsToResolve := make([]string, len(collaborators)) 124 for i, collab := range collaborators { 125 identsToResolve[i] = collab.Did 126 } 127 128 resolvedIdents := f.rr.idResolver.ResolveIdents(ctx, identsToResolve) 129 for i, resolved := range resolvedIdents { 130 if resolved != nil { 131 collaborators[i].Handle = resolved.Handle.String() 132 } 133 } 134 135 return collaborators, nil 136} 137 138// this function is a bit weird since it now returns RepoInfo from an entirely different 139// package. we should refactor this or get rid of RepoInfo entirely. 140func (f *ResolvedRepo) RepoInfo(user *oauth.User) repoinfo.RepoInfo { 141 repoAt := f.RepoAt() 142 isStarred := false 143 if user != nil { 144 isStarred = db.GetStarStatus(f.rr.execer, user.Did, repoAt) 145 } 146 147 starCount, err := db.GetStarCount(f.rr.execer, repoAt) 148 if err != nil { 149 log.Println("failed to get star count for ", repoAt) 150 } 151 issueCount, err := db.GetIssueCount(f.rr.execer, repoAt) 152 if err != nil { 153 log.Println("failed to get issue count for ", repoAt) 154 } 155 pullCount, err := db.GetPullCount(f.rr.execer, repoAt) 156 if err != nil { 157 log.Println("failed to get issue count for ", repoAt) 158 } 159 source, err := db.GetRepoSource(f.rr.execer, repoAt) 160 if errors.Is(err, sql.ErrNoRows) { 161 source = "" 162 } else if err != nil { 163 log.Println("failed to get repo source for ", repoAt, err) 164 } 165 166 var sourceRepo *models.Repo 167 if source != "" { 168 sourceRepo, err = db.GetRepoByAtUri(f.rr.execer, source) 169 if err != nil { 170 log.Println("failed to get repo by at uri", err) 171 } 172 } 173 174 var sourceHandle *identity.Identity 175 if sourceRepo != nil { 176 sourceHandle, err = f.rr.idResolver.ResolveIdent(context.Background(), sourceRepo.Did) 177 if err != nil { 178 log.Println("failed to resolve source repo", err) 179 } 180 } 181 182 knot := f.Knot 183 184 repoInfo := repoinfo.RepoInfo{ 185 OwnerDid: f.OwnerDid(), 186 OwnerHandle: f.OwnerHandle(), 187 Name: f.Name, 188 Rkey: f.Repo.Rkey, 189 RepoAt: repoAt, 190 Description: f.Description, 191 IsStarred: isStarred, 192 Knot: knot, 193 Spindle: f.Spindle, 194 Roles: f.RolesInRepo(user), 195 Stats: models.RepoStats{ 196 StarCount: starCount, 197 IssueCount: issueCount, 198 PullCount: pullCount, 199 }, 200 CurrentDir: f.CurrentDir, 201 Ref: f.Ref, 202 } 203 204 if sourceRepo != nil { 205 repoInfo.Source = sourceRepo 206 repoInfo.SourceHandle = sourceHandle.Handle.String() 207 } 208 209 return repoInfo 210} 211 212func (f *ResolvedRepo) RolesInRepo(u *oauth.User) repoinfo.RolesInRepo { 213 if u != nil { 214 r := f.rr.enforcer.GetPermissionsInRepo(u.Did, f.Knot, f.DidSlashRepo()) 215 return repoinfo.RolesInRepo{Roles: r} 216 } else { 217 return repoinfo.RolesInRepo{} 218 } 219} 220 221// extractPathAfterRef gets the actual repository path 222// after the ref. for example: 223// 224// /@icyphox.sh/foorepo/blob/main/abc/xyz/ => abc/xyz/ 225func extractPathAfterRef(fullPath string) string { 226 fullPath = strings.TrimPrefix(fullPath, "/") 227 228 // match blob/, tree/, or raw/ followed by any ref and then a slash 229 // 230 // captures everything after the final slash 231 pattern := `(?:blob|tree|raw)/[^/]+/(.*)$` 232 233 re := regexp.MustCompile(pattern) 234 matches := re.FindStringSubmatch(fullPath) 235 236 if len(matches) > 1 { 237 return matches[1] 238 } 239 240 return "" 241}