forked from tangled.org/core
Something went wrong. Try again.
7.7 kB · 306 lines
Go
at ci
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307package reporesolver
import ( "context" "database/sql" "errors" "fmt" "log" "net/http" "net/url" "path" "strings"
"github.com/bluesky-social/indigo/atproto/identity" "github.com/bluesky-social/indigo/atproto/syntax" securejoin "github.com/cyphar/filepath-securejoin" "github.com/go-chi/chi/v5" "tangled.sh/tangled.sh/core/appview/config" "tangled.sh/tangled.sh/core/appview/db" "tangled.sh/tangled.sh/core/appview/oauth" "tangled.sh/tangled.sh/core/appview/pages" "tangled.sh/tangled.sh/core/appview/pages/repoinfo" "tangled.sh/tangled.sh/core/idresolver" "tangled.sh/tangled.sh/core/knotclient" "tangled.sh/tangled.sh/core/rbac")
type ResolvedRepo struct { Knot string OwnerId identity.Identity RepoName string RepoAt syntax.ATURI Description string Spindle string CreatedAt string Ref string CurrentDir string
rr *RepoResolver}
type RepoResolver struct { config *config.Config enforcer *rbac.Enforcer idResolver *idresolver.Resolver execer db.Execer}
func New(config *config.Config, enforcer *rbac.Enforcer, resolver *idresolver.Resolver, execer db.Execer) *RepoResolver { return &RepoResolver{config: config, enforcer: enforcer, idResolver: resolver, execer: execer}}
func (rr *RepoResolver) Resolve(r *http.Request) (*ResolvedRepo, error) { repoName := chi.URLParam(r, "repo") knot, ok := r.Context().Value("knot").(string) if !ok { log.Println("malformed middleware") return nil, fmt.Errorf("malformed middleware") } id, ok := r.Context().Value("resolvedId").(identity.Identity) if !ok { log.Println("malformed middleware") return nil, fmt.Errorf("malformed middleware") }
repoAt, ok := r.Context().Value("repoAt").(string) if !ok { log.Println("malformed middleware") return nil, fmt.Errorf("malformed middleware") }
parsedRepoAt, err := syntax.ParseATURI(repoAt) if err != nil { log.Println("malformed repo at-uri") return nil, fmt.Errorf("malformed middleware") }
ref := chi.URLParam(r, "ref")
if ref == "" { us, err := knotclient.NewUnsignedClient(knot, rr.config.Core.Dev) if err != nil { return nil, err }
defaultBranch, err := us.DefaultBranch(id.DID.String(), repoName) if err != nil { return nil, err }
ref = defaultBranch.Branch }
currentDir := path.Dir(extractPathAfterRef(r.URL.EscapedPath(), ref))
// pass through values from the middleware description, ok := r.Context().Value("repoDescription").(string) addedAt, ok := r.Context().Value("repoAddedAt").(string) spindle, ok := r.Context().Value("repoSpindle").(string)
return &ResolvedRepo{ Knot: knot, OwnerId: id, RepoName: repoName, RepoAt: parsedRepoAt, Description: description, CreatedAt: addedAt, Ref: ref, CurrentDir: currentDir, Spindle: spindle,
rr: rr, }, nil}
func (f *ResolvedRepo) OwnerDid() string { return f.OwnerId.DID.String()}
func (f *ResolvedRepo) OwnerHandle() string { return f.OwnerId.Handle.String()}
func (f *ResolvedRepo) OwnerSlashRepo() string { handle := f.OwnerId.Handle
var p string if handle != "" && !handle.IsInvalidHandle() { p, _ = securejoin.SecureJoin(fmt.Sprintf("@%s", handle), f.RepoName) } else { p, _ = securejoin.SecureJoin(f.OwnerDid(), f.RepoName) }
return p}
func (f *ResolvedRepo) DidSlashRepo() string { p, _ := securejoin.SecureJoin(f.OwnerDid(), f.RepoName) return p}
func (f *ResolvedRepo) Collaborators(ctx context.Context) ([]pages.Collaborator, error) { repoCollaborators, err := f.rr.enforcer.E.GetImplicitUsersForResourceByDomain(f.DidSlashRepo(), f.Knot) if err != nil { return nil, err }
var collaborators []pages.Collaborator for _, item := range repoCollaborators { // currently only two roles: owner and member var role string switch item[3] { case "repo:owner": role = "owner" case "repo:collaborator": role = "collaborator" default: continue }
did := item[0]
c := pages.Collaborator{ Did: did, Handle: "", Role: role, } collaborators = append(collaborators, c) }
// populate all collborators with handles identsToResolve := make([]string, len(collaborators)) for i, collab := range collaborators { identsToResolve[i] = collab.Did }
resolvedIdents := f.rr.idResolver.ResolveIdents(ctx, identsToResolve) for i, resolved := range resolvedIdents { if resolved != nil { collaborators[i].Handle = resolved.Handle.String() } }
return collaborators, nil}
// this function is a bit weird since it now returns RepoInfo from an entirely different// package. we should refactor this or get rid of RepoInfo entirely.func (f *ResolvedRepo) RepoInfo(user *oauth.User) repoinfo.RepoInfo { isStarred := false if user != nil { isStarred = db.GetStarStatus(f.rr.execer, user.Did, syntax.ATURI(f.RepoAt)) }
starCount, err := db.GetStarCount(f.rr.execer, f.RepoAt) if err != nil { log.Println("failed to get star count for ", f.RepoAt) } issueCount, err := db.GetIssueCount(f.rr.execer, f.RepoAt) if err != nil { log.Println("failed to get issue count for ", f.RepoAt) } pullCount, err := db.GetPullCount(f.rr.execer, f.RepoAt) if err != nil { log.Println("failed to get issue count for ", f.RepoAt) } source, err := db.GetRepoSource(f.rr.execer, f.RepoAt) if errors.Is(err, sql.ErrNoRows) { source = "" } else if err != nil { log.Println("failed to get repo source for ", f.RepoAt, err) }
var sourceRepo *db.Repo if source != "" { sourceRepo, err = db.GetRepoByAtUri(f.rr.execer, source) if err != nil { log.Println("failed to get repo by at uri", err) } }
var sourceHandle *identity.Identity if sourceRepo != nil { sourceHandle, err = f.rr.idResolver.ResolveIdent(context.Background(), sourceRepo.Did) if err != nil { log.Println("failed to resolve source repo", err) } }
knot := f.Knot var disableFork bool us, err := knotclient.NewUnsignedClient(knot, f.rr.config.Core.Dev) if err != nil { log.Printf("failed to create unsigned client for %s: %v", knot, err) } else { result, err := us.Branches(f.OwnerDid(), f.RepoName) if err != nil { log.Printf("failed to get branches for %s/%s: %v", f.OwnerDid(), f.RepoName, err) }
if len(result.Branches) == 0 { disableFork = true } }
repoInfo := repoinfo.RepoInfo{ OwnerDid: f.OwnerDid(), OwnerHandle: f.OwnerHandle(), Name: f.RepoName, RepoAt: f.RepoAt, Description: f.Description, Ref: f.Ref, IsStarred: isStarred, Knot: knot, Spindle: f.Spindle, Roles: f.RolesInRepo(user), Stats: db.RepoStats{ StarCount: starCount, IssueCount: issueCount, PullCount: pullCount, }, DisableFork: disableFork, CurrentDir: f.CurrentDir, }
if sourceRepo != nil { repoInfo.Source = sourceRepo repoInfo.SourceHandle = sourceHandle.Handle.String() }
return repoInfo}
func (f *ResolvedRepo) RolesInRepo(u *oauth.User) repoinfo.RolesInRepo { if u != nil { r := f.rr.enforcer.GetPermissionsInRepo(u.Did, f.Knot, f.DidSlashRepo()) return repoinfo.RolesInRepo{r} } else { return repoinfo.RolesInRepo{} }}
// extractPathAfterRef gets the actual repository path// after the ref. for example://// /@icyphox.sh/foorepo/blob/main/abc/xyz/ => abc/xyz/func extractPathAfterRef(fullPath, ref string) string { fullPath = strings.TrimPrefix(fullPath, "/")
ref = url.PathEscape(ref)
prefixes := []string{ fmt.Sprintf("blob/%s/", ref), fmt.Sprintf("tree/%s/", ref), fmt.Sprintf("raw/%s/", ref), }
for _, prefix := range prefixes { idx := strings.Index(fullPath, prefix) if idx != -1 { return fullPath[idx+len(prefix):] } }
return ""}