Monorepo for Tangled tangled.org

appview: diet the reporesolver

Ideally we should completely remove it.

Signed-off-by: Seongmin Lee <git@boltless.me>

authored by boltless.me and committed by Tangled e4fc543c a6290643

Changed files
+98 -99
appview
db
issues
pulls
repo
reporesolver
state
rbac
+11
appview/db/repos.go
··· 411 return nullableSource.String, nil 412 } 413 414 func GetForksByDid(e Execer, did string) ([]models.Repo, error) { 415 var repos []models.Repo 416
··· 411 return nullableSource.String, nil 412 } 413 414 + func GetRepoSourceRepo(e Execer, repoAt syntax.ATURI) (*models.Repo, error) { 415 + source, err := GetRepoSource(e, repoAt) 416 + if source == "" || errors.Is(err, sql.ErrNoRows) { 417 + return nil, nil 418 + } 419 + if err != nil { 420 + return nil, err 421 + } 422 + return GetRepoByAtUri(e, source) 423 + } 424 + 425 func GetForksByDid(e Execer, did string) ([]models.Repo, error) { 426 var repos []models.Repo 427
+8 -17
appview/issues/issues.go
··· 7 "fmt" 8 "log/slog" 9 "net/http" 10 - "slices" 11 "time" 12 13 comatproto "github.com/bluesky-social/indigo/api/atproto" ··· 286 return 287 } 288 289 - collaborators, err := f.Collaborators(r.Context()) 290 - if err != nil { 291 - l.Error("failed to fetch repo collaborators", "err", err) 292 - } 293 - isCollaborator := slices.ContainsFunc(collaborators, func(collab pages.Collaborator) bool { 294 - return user.Did == collab.Did 295 - }) 296 isIssueOwner := user.Did == issue.Did 297 298 // TODO: make this more granular 299 - if isIssueOwner || isCollaborator { 300 err = db.CloseIssues( 301 rp.db, 302 db.FilterEq("id", issue.Id), ··· 338 return 339 } 340 341 - collaborators, err := f.Collaborators(r.Context()) 342 - if err != nil { 343 - l.Error("failed to fetch repo collaborators", "err", err) 344 - } 345 - isCollaborator := slices.ContainsFunc(collaborators, func(collab pages.Collaborator) bool { 346 - return user.Did == collab.Did 347 - }) 348 isIssueOwner := user.Did == issue.Did 349 350 - if isCollaborator || isIssueOwner { 351 err := db.ReopenIssues( 352 rp.db, 353 db.FilterEq("id", issue.Id),
··· 7 "fmt" 8 "log/slog" 9 "net/http" 10 "time" 11 12 comatproto "github.com/bluesky-social/indigo/api/atproto" ··· 285 return 286 } 287 288 + roles := f.RolesInRepo(user) 289 + isRepoOwner := roles.IsOwner() 290 + isCollaborator := roles.IsCollaborator() 291 isIssueOwner := user.Did == issue.Did 292 293 // TODO: make this more granular 294 + if isIssueOwner || isRepoOwner || isCollaborator { 295 err = db.CloseIssues( 296 rp.db, 297 db.FilterEq("id", issue.Id), ··· 333 return 334 } 335 336 + roles := f.RolesInRepo(user) 337 + isRepoOwner := roles.IsOwner() 338 + isCollaborator := roles.IsCollaborator() 339 isIssueOwner := user.Did == issue.Did 340 341 + if isCollaborator || isRepoOwner || isIssueOwner { 342 err := db.ReopenIssues( 343 rp.db, 344 db.FilterEq("id", issue.Id),
+4 -2
appview/pulls/pulls.go
··· 877 } 878 879 // Determine PR type based on input parameters 880 - isPushAllowed := f.RepoInfo(user).Roles.IsPushAllowed() 881 isBranchBased := isPushAllowed && sourceBranch != "" && fromFork == "" 882 isForkBased := fromFork != "" && sourceBranch != "" 883 isPatchBased := patch != "" && !isBranchBased && !isForkBased ··· 1673 return 1674 } 1675 1676 - if !f.RepoInfo(user).Roles.IsPushAllowed() { 1677 log.Println("unauthorized user") 1678 w.WriteHeader(http.StatusUnauthorized) 1679 return
··· 877 } 878 879 // Determine PR type based on input parameters 880 + roles := f.RolesInRepo(user) 881 + isPushAllowed := roles.IsPushAllowed() 882 isBranchBased := isPushAllowed && sourceBranch != "" && fromFork == "" 883 isForkBased := fromFork != "" && sourceBranch != "" 884 isPatchBased := patch != "" && !isBranchBased && !isForkBased ··· 1674 return 1675 } 1676 1677 + roles := f.RolesInRepo(user) 1678 + if !roles.IsPushAllowed() { 1679 log.Println("unauthorized user") 1680 w.WriteHeader(http.StatusUnauthorized) 1681 return
+30 -2
appview/repo/settings.go
··· 10 11 "tangled.org/core/api/tangled" 12 "tangled.org/core/appview/db" 13 "tangled.org/core/appview/oauth" 14 "tangled.org/core/appview/pages" 15 xrpcclient "tangled.org/core/appview/xrpcclient" ··· 271 f, err := rp.repoResolver.Resolve(r) 272 user := rp.oauth.GetUser(r) 273 274 - repoCollaborators, err := f.Collaborators(r.Context()) 275 if err != nil { 276 l.Error("failed to get collaborators", "err", err) 277 } ··· 281 RepoInfo: f.RepoInfo(user), 282 Tabs: settingsTabs, 283 Tab: "access", 284 - Collaborators: repoCollaborators, 285 }) 286 } 287
··· 10 11 "tangled.org/core/api/tangled" 12 "tangled.org/core/appview/db" 13 + "tangled.org/core/appview/models" 14 "tangled.org/core/appview/oauth" 15 "tangled.org/core/appview/pages" 16 xrpcclient "tangled.org/core/appview/xrpcclient" ··· 272 f, err := rp.repoResolver.Resolve(r) 273 user := rp.oauth.GetUser(r) 274 275 + collaborators, err := func(repo *models.Repo) ([]pages.Collaborator, error) { 276 + repoCollaborators, err := rp.enforcer.E.GetImplicitUsersForResourceByDomain(repo.DidSlashRepo(), repo.Knot) 277 + if err != nil { 278 + return nil, err 279 + } 280 + var collaborators []pages.Collaborator 281 + for _, item := range repoCollaborators { 282 + // currently only two roles: owner and member 283 + var role string 284 + switch item[3] { 285 + case "repo:owner": 286 + role = "owner" 287 + case "repo:collaborator": 288 + role = "collaborator" 289 + default: 290 + continue 291 + } 292 + 293 + did := item[0] 294 + 295 + c := pages.Collaborator{ 296 + Did: did, 297 + Role: role, 298 + } 299 + collaborators = append(collaborators, c) 300 + } 301 + return collaborators, nil 302 + }(&f.Repo) 303 if err != nil { 304 l.Error("failed to get collaborators", "err", err) 305 } ··· 309 RepoInfo: f.RepoInfo(user), 310 Tabs: settingsTabs, 311 Tab: "access", 312 + Collaborators: collaborators, 313 }) 314 } 315
+36 -77
appview/reporesolver/resolver.go
··· 1 package reporesolver 2 3 import ( 4 - "context" 5 - "database/sql" 6 - "errors" 7 "fmt" 8 "log" 9 "net/http" ··· 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 ··· 33 } 34 35 type RepoResolver struct { 36 - config *config.Config 37 - enforcer *rbac.Enforcer 38 - idResolver *idresolver.Resolver 39 - execer db.Execer 40 } 41 42 - func 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 ··· 80 }, nil 81 } 82 83 - func (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. 116 func (f *ResolvedRepo) RepoInfo(user *oauth.User) repoinfo.RepoInfo { ··· 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, ··· 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
··· 1 package reporesolver 2 3 import ( 4 "fmt" 5 "log" 6 "net/http" ··· 14 "tangled.org/core/appview/db" 15 "tangled.org/core/appview/models" 16 "tangled.org/core/appview/oauth" 17 "tangled.org/core/appview/pages/repoinfo" 18 "tangled.org/core/rbac" 19 ) 20 ··· 28 } 29 30 type RepoResolver struct { 31 + config *config.Config 32 + enforcer *rbac.Enforcer 33 + execer db.Execer 34 } 35 36 + func New(config *config.Config, enforcer *rbac.Enforcer, execer db.Execer) *RepoResolver { 37 + return &RepoResolver{config: config, enforcer: enforcer, execer: execer} 38 } 39 40 // NOTE: this... should not even be here. the entire package will be removed in future refactor ··· 74 }, nil 75 } 76 77 // this function is a bit weird since it now returns RepoInfo from an entirely different 78 // package. we should refactor this or get rid of RepoInfo entirely. 79 func (f *ResolvedRepo) RepoInfo(user *oauth.User) repoinfo.RepoInfo { ··· 83 isStarred = db.GetStarStatus(f.rr.execer, user.Did, repoAt) 84 } 85 86 + stats := f.RepoStats 87 + if stats == nil { 88 + starCount, err := db.GetStarCount(f.rr.execer, repoAt) 89 + if err != nil { 90 + log.Println("failed to get star count for ", repoAt) 91 + } 92 + issueCount, err := db.GetIssueCount(f.rr.execer, repoAt) 93 + if err != nil { 94 + log.Println("failed to get issue count for ", repoAt) 95 + } 96 + pullCount, err := db.GetPullCount(f.rr.execer, repoAt) 97 if err != nil { 98 + log.Println("failed to get pull count for ", repoAt) 99 + } 100 + stats = &models.RepoStats{ 101 + StarCount: starCount, 102 + IssueCount: issueCount, 103 + PullCount: pullCount, 104 } 105 } 106 107 + sourceRepo, err := db.GetRepoSourceRepo(f.rr.execer, repoAt) 108 + if err != nil { 109 + log.Println("failed to get repo by at uri", err) 110 + } 111 112 repoInfo := repoinfo.RepoInfo{ 113 + // this is basically a models.Repo 114 OwnerDid: f.OwnerId.DID.String(), 115 OwnerHandle: f.OwnerId.Handle.String(), 116 Name: f.Name, ··· 118 Description: f.Description, 119 Website: f.Website, 120 Topics: f.Topics, 121 + Knot: f.Knot, 122 Spindle: f.Spindle, 123 + Stats: *stats, 124 + 125 + // fork repo upstream 126 + Source: sourceRepo, 127 + 128 CurrentDir: f.CurrentDir, 129 Ref: f.Ref, 130 131 + // info related to the session 132 + IsStarred: isStarred, 133 + Roles: f.RolesInRepo(user), 134 } 135 136 return repoInfo
+1 -1
appview/state/state.go
··· 96 } 97 validator := validator.New(d, res, enforcer) 98 99 - repoResolver := reporesolver.New(config, enforcer, res, d) 100 101 wrapper := db.DbWrapper{Execer: d} 102 jc, err := jetstream.NewJetstreamClient(
··· 96 } 97 validator := validator.New(d, res, enforcer) 98 99 + repoResolver := reporesolver.New(config, enforcer, d) 100 101 wrapper := db.DbWrapper{Execer: d} 102 jc, err := jetstream.NewJetstreamClient(
+8
rbac/rbac.go
··· 285 return e.E.Enforce(user, domain, repo, "repo:delete") 286 } 287 288 func (e *Enforcer) IsPushAllowed(user, domain, repo string) (bool, error) { 289 return e.E.Enforce(user, domain, repo, "repo:push") 290 }
··· 285 return e.E.Enforce(user, domain, repo, "repo:delete") 286 } 287 288 + func (e *Enforcer) IsRepoOwner(user, domain, repo string) (bool, error) { 289 + return e.E.Enforce(user, domain, repo, "repo:owner") 290 + } 291 + 292 + func (e *Enforcer) IsRepoCollaborator(user, domain, repo string) (bool, error) { 293 + return e.E.Enforce(user, domain, repo, "repo:collaborator") 294 + } 295 + 296 func (e *Enforcer) IsPushAllowed(user, domain, repo string) (bool, error) { 297 return e.E.Enforce(user, domain, repo, "repo:push") 298 }