forked from
tangled.org/core
Monorepo for Tangled — https://tangled.org
1package reporesolver
2
3import (
4 "fmt"
5 "log"
6 "net/http"
7 "path"
8 "regexp"
9 "strings"
10
11 "github.com/bluesky-social/indigo/atproto/identity"
12 "github.com/go-chi/chi/v5"
13 "tangled.org/core/appview/config"
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
21type RepoResolver struct {
22 config *config.Config
23 enforcer *rbac.Enforcer
24 execer db.Execer
25}
26
27func New(config *config.Config, enforcer *rbac.Enforcer, execer db.Execer) *RepoResolver {
28 return &RepoResolver{config: config, enforcer: enforcer, execer: execer}
29}
30
31// NOTE: this... should not even be here. the entire package will be removed in future refactor
32func GetBaseRepoPath(r *http.Request, repo *models.Repo) string {
33 var (
34 user = chi.URLParam(r, "user")
35 name = chi.URLParam(r, "repo")
36 )
37 if user == "" || name == "" {
38 return repo.DidSlashRepo()
39 }
40 return path.Join(user, name)
41}
42
43// TODO: move this out of `RepoResolver` struct
44func (rr *RepoResolver) Resolve(r *http.Request) (*models.Repo, error) {
45 repo, ok := r.Context().Value("repo").(*models.Repo)
46 if !ok {
47 log.Println("malformed middleware: `repo` not exist in context")
48 return nil, fmt.Errorf("malformed middleware")
49 }
50
51 return repo, nil
52}
53
54// 1. [x] replace `RepoInfo` to `reporesolver.GetRepoInfo(r *http.Request, repo, user)`
55// 2. [x] remove `rr`, `CurrentDir`, `Ref` fields from `ResolvedRepo`
56// 3. [x] remove `ResolvedRepo`
57// 4. [ ] replace reporesolver to reposervice
58func (rr *RepoResolver) GetRepoInfo(r *http.Request, user *oauth.User) repoinfo.RepoInfo {
59 ownerId, ook := r.Context().Value("resolvedId").(identity.Identity)
60 repo, rok := r.Context().Value("repo").(*models.Repo)
61 if !ook || !rok {
62 log.Println("malformed request, failed to get repo from context")
63 }
64
65 // get dir/ref
66 currentDir := path.Dir(extractPathAfterRef(r.URL.EscapedPath()))
67 ref := chi.URLParam(r, "ref")
68
69 repoAt := repo.RepoAt()
70 isStarred := false
71 roles := repoinfo.RolesInRepo{}
72 if user != nil {
73 isStarred = db.GetStarStatus(rr.execer, user.Did, repoAt)
74 roles.Roles = rr.enforcer.GetPermissionsInRepo(user.Did, repo.Knot, repo.DidSlashRepo())
75 }
76
77 stats := repo.RepoStats
78 if stats == nil {
79 starCount, err := db.GetStarCount(rr.execer, repoAt)
80 if err != nil {
81 log.Println("failed to get star count for ", repoAt)
82 }
83 issueCount, err := db.GetIssueCount(rr.execer, repoAt)
84 if err != nil {
85 log.Println("failed to get issue count for ", repoAt)
86 }
87 pullCount, err := db.GetPullCount(rr.execer, repoAt)
88 if err != nil {
89 log.Println("failed to get pull count for ", repoAt)
90 }
91 stats = &models.RepoStats{
92 StarCount: starCount,
93 IssueCount: issueCount,
94 PullCount: pullCount,
95 }
96 }
97
98 var sourceRepo *models.Repo
99 var err error
100 if repo.Source != "" {
101 sourceRepo, err = db.GetRepoByAtUri(rr.execer, repo.Source)
102 if err != nil {
103 log.Println("failed to get repo by at uri", err)
104 }
105 }
106
107 repoInfo := repoinfo.RepoInfo{
108 // this is basically a models.Repo
109 OwnerDid: ownerId.DID.String(),
110 OwnerHandle: ownerId.Handle.String(),
111 Name: repo.Name,
112 Rkey: repo.Rkey,
113 Description: repo.Description,
114 Website: repo.Website,
115 Topics: repo.Topics,
116 Knot: repo.Knot,
117 Spindle: repo.Spindle,
118 Stats: *stats,
119
120 // fork repo upstream
121 Source: sourceRepo,
122
123 // page context
124 CurrentDir: currentDir,
125 Ref: ref,
126
127 // info related to the session
128 IsStarred: isStarred,
129 Roles: roles,
130 }
131
132 return repoInfo
133}
134
135// extractPathAfterRef gets the actual repository path
136// after the ref. for example:
137//
138// /@icyphox.sh/foorepo/blob/main/abc/xyz/ => abc/xyz/
139func extractPathAfterRef(fullPath string) string {
140 fullPath = strings.TrimPrefix(fullPath, "/")
141
142 // match blob/, tree/, or raw/ followed by any ref and then a slash
143 //
144 // captures everything after the final slash
145 pattern := `(?:blob|tree|raw)/[^/]+/(.*)$`
146
147 re := regexp.MustCompile(pattern)
148 matches := re.FindStringSubmatch(fullPath)
149
150 if len(matches) > 1 {
151 return matches[1]
152 }
153
154 return ""
155}