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