forked from
tangled.org/core
Monorepo for Tangled
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 if repo.RepoDid != "" {
34 return repo.RepoDid
35 }
36 var (
37 user = chi.URLParam(r, "user")
38 name = chi.URLParam(r, "repo")
39 )
40 if user == "" || name == "" {
41 return repo.DidSlashRepo()
42 }
43 return path.Join(user, name)
44}
45
46// TODO: move this out of `RepoResolver` struct
47func (rr *RepoResolver) Resolve(r *http.Request) (*models.Repo, 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
54 return repo, nil
55}
56
57// 1. [x] replace `RepoInfo` to `reporesolver.GetRepoInfo(r *http.Request, repo, user)`
58// 2. [x] remove `rr`, `CurrentDir`, `Ref` fields from `ResolvedRepo`
59// 3. [x] remove `ResolvedRepo`
60// 4. [ ] replace reporesolver to reposervice
61func (rr *RepoResolver) GetRepoInfo(r *http.Request, user *oauth.MultiAccountUser) repoinfo.RepoInfo {
62 ownerId, ook := r.Context().Value("resolvedId").(identity.Identity)
63 repo, rok := r.Context().Value("repo").(*models.Repo)
64 if !ook || !rok {
65 log.Println("malformed request, failed to get repo from context")
66 }
67
68 // get dir/ref
69 currentDir := extractCurrentDir(r.URL.EscapedPath())
70 ref := chi.URLParam(r, "ref")
71
72 repoAt := repo.RepoAt()
73 isStarred := false
74 roles := repoinfo.RolesInRepo{}
75 if user != nil && user.Active != nil {
76 isStarred = db.GetStarStatus(rr.execer, user.Active.Did, repoAt)
77 roles.Roles = rr.enforcer.GetPermissionsInRepo(user.Active.Did, repo.Knot, repo.DidSlashRepo())
78 }
79
80 stats := repo.RepoStats
81 if stats == nil {
82 var starCount int
83 var starErr error
84 if repo.RepoDid != "" {
85 starCount, starErr = db.GetStarCountByRepoDid(rr.execer, repo.RepoDid, repoAt)
86 } else {
87 starCount, starErr = db.GetStarCount(rr.execer, repoAt)
88 }
89 if starErr != nil {
90 log.Println("failed to get star count for ", repoAt)
91 }
92 issueCount, err := db.GetIssueCount(rr.execer, repoAt)
93 if err != nil {
94 log.Println("failed to get issue count for ", repoAt)
95 }
96 pullCount, err := db.GetPullCount(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 var sourceRepo *models.Repo
108 var err error
109 if repo.Source != "" {
110 sourceRepo, err = db.GetRepoByAtUri(rr.execer, repo.Source)
111 if err != nil {
112 log.Println("failed to get repo by at uri", err)
113 }
114 }
115
116 repoInfo := repoinfo.RepoInfo{
117 // this is basically a models.Repo
118 OwnerDid: ownerId.DID.String(),
119 OwnerHandle: ownerId.Handle.String(),
120 RepoDid: repo.RepoDid,
121 Name: repo.Name,
122 Rkey: repo.Rkey,
123 Description: repo.Description,
124 Website: repo.Website,
125 Topics: repo.Topics,
126 Knot: repo.Knot,
127 Spindle: repo.Spindle,
128 Stats: *stats,
129
130 // fork repo upstream
131 Source: sourceRepo,
132
133 // page context
134 CurrentDir: currentDir,
135 Ref: ref,
136
137 // info related to the session
138 IsStarred: isStarred,
139 Roles: roles,
140 }
141
142 return repoInfo
143}
144
145// extractCurrentDir gets the current directory for markdown link resolution.
146// for blob paths, returns the parent dir. for tree paths, returns the path itself.
147//
148// /@user/repo/blob/main/docs/README.md => docs
149// /@user/repo/tree/main/docs => docs
150func extractCurrentDir(fullPath string) string {
151 fullPath = strings.TrimPrefix(fullPath, "/")
152
153 blobPattern := regexp.MustCompile(`blob/[^/]+/(.*)$`)
154 if matches := blobPattern.FindStringSubmatch(fullPath); len(matches) > 1 {
155 return path.Dir(matches[1])
156 }
157
158 treePattern := regexp.MustCompile(`tree/[^/]+/(.*)$`)
159 if matches := treePattern.FindStringSubmatch(fullPath); len(matches) > 1 {
160 dir := strings.TrimSuffix(matches[1], "/")
161 if dir == "" {
162 return "."
163 }
164 return dir
165 }
166
167 return "."
168}
169
170// extractPathAfterRef gets the actual repository path
171// after the ref. for example:
172//
173// /@icyphox.sh/foorepo/blob/main/abc/xyz/ => abc/xyz/
174func extractPathAfterRef(fullPath string) string {
175 fullPath = strings.TrimPrefix(fullPath, "/")
176
177 // match blob/, tree/, or raw/ followed by any ref and then a slash
178 //
179 // captures everything after the final slash
180 pattern := `(?:blob|tree|raw)/[^/]+/(.*)$`
181
182 re := regexp.MustCompile(pattern)
183 matches := re.FindStringSubmatch(fullPath)
184
185 if len(matches) > 1 {
186 return matches[1]
187 }
188
189 return ""
190}