Monorepo for Tangled
tangled.org
4.7 kB
175 lines
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/cache"
14 "tangled.org/core/appview/config"
15 "tangled.org/core/appview/db"
16 "tangled.org/core/appview/models"
17 "tangled.org/core/appview/oauth"
18 "tangled.org/core/appview/pages/repoinfo"
19 "tangled.org/core/rbac"
20)
21
22var (
23 blobPattern = regexp.MustCompile(`blob/[^/]+/(.*)$`)
24 treePattern = regexp.MustCompile(`tree/[^/]+/(.*)$`)
25)
26
27type RepoResolver struct {
28 config *config.Config
29 enforcer *rbac.Enforcer
30 execer db.Execer
31 rdb *cache.Cache
32}
33
34func New(config *config.Config, enforcer *rbac.Enforcer, execer db.Execer, rdb *cache.Cache) *RepoResolver {
35 return &RepoResolver{config: config, enforcer: enforcer, execer: execer, rdb: rdb}
36}
37
38// NOTE: this... should not even be here. the entire package will be removed in future refactor
39func GetBaseRepoPath(r *http.Request, repo *models.Repo) string {
40 if repo.RepoDid != "" {
41 return repo.RepoDid
42 }
43 var (
44 user = chi.URLParam(r, "user")
45 name = chi.URLParam(r, "repo")
46 )
47 if user == "" || name == "" {
48 return repo.RepoIdentifier()
49 }
50 return path.Join(user, name)
51}
52
53// TODO: move this out of `RepoResolver` struct
54func (rr *RepoResolver) Resolve(r *http.Request) (*models.Repo, error) {
55 repo, ok := r.Context().Value("repo").(*models.Repo)
56 if !ok {
57 log.Println("malformed middleware: `repo` not exist in context")
58 return nil, fmt.Errorf("malformed middleware")
59 }
60
61 return repo, nil
62}
63
64// 1. [x] replace `RepoInfo` to `reporesolver.GetRepoInfo(r *http.Request, repo, user)`
65// 2. [x] remove `rr`, `CurrentDir`, `Ref` fields from `ResolvedRepo`
66// 3. [x] remove `ResolvedRepo`
67// 4. [ ] replace reporesolver to reposervice
68func (rr *RepoResolver) GetRepoInfo(r *http.Request, user *oauth.MultiAccountUser) repoinfo.RepoInfo {
69 ownerId, ook := r.Context().Value("resolvedId").(identity.Identity)
70 repo, rok := r.Context().Value("repo").(*models.Repo)
71 if !ook || !rok {
72 log.Println("malformed request, failed to get repo from context")
73 }
74
75 // get dir/ref
76 currentDir := extractCurrentDir(r.URL.EscapedPath())
77 ref := chi.URLParam(r, "ref")
78
79 repoAt := repo.RepoAt()
80 isStarred := false
81 roles := repoinfo.RolesInRepo{}
82 if user != nil {
83 isStarred = db.GetStarStatus(rr.execer, user.Did, repoAt)
84 roles.Roles = rr.enforcer.GetPermissionsInRepo(user.Did, repo.Knot, repo.RepoIdentifier())
85 }
86
87 stats := repo.RepoStats
88 if stats == nil {
89 starCount, starErr := db.GetStarCount(rr.execer, repoAt)
90 if starErr != nil {
91 log.Println("failed to get star count for ", repoAt)
92 }
93 issueCount, err := db.GetIssueCount(rr.execer, repoAt)
94 if err != nil {
95 log.Println("failed to get issue count for ", repoAt)
96 }
97 pullCount, err := db.GetPullCount(rr.execer, repoAt)
98 if err != nil {
99 log.Println("failed to get pull count for ", repoAt)
100 }
101 stats = &models.RepoStats{
102 StarCount: starCount,
103 IssueCount: issueCount,
104 PullCount: pullCount,
105 }
106 }
107
108 var sourceRepo *models.Repo
109 var err error
110 if repo.Source != "" {
111 if strings.HasPrefix(repo.Source, "did:") {
112 sourceRepo, err = db.GetRepoByDid(rr.execer, repo.Source)
113 } else {
114 sourceRepo, err = db.GetRepoByAtUri(rr.execer, repo.Source)
115 }
116 if err != nil {
117 log.Println("failed to get source repo", err)
118 }
119 }
120
121 ownerHandle := ownerId.Handle.String()
122 if h := cache.LookupPreferredHandle(r.Context(), rr.rdb, rr.execer, ownerId.DID.String()); h != "" {
123 ownerHandle = h
124 }
125
126 repoInfo := repoinfo.RepoInfo{
127 // this is basically a models.Repo
128 OwnerDid: ownerId.DID.String(),
129 OwnerHandle: ownerHandle,
130 Name: repo.Name,
131 Rkey: repo.Rkey,
132 Description: repo.Description,
133 Website: repo.Website,
134 Topics: repo.Topics,
135 Knot: repo.Knot,
136 Spindle: repo.Spindle,
137 Stats: *stats,
138
139 // fork repo upstream
140 Source: sourceRepo,
141
142 // page context
143 CurrentDir: currentDir,
144 Ref: ref,
145
146 // info related to the session
147 IsStarred: isStarred,
148 Roles: roles,
149 }
150
151 return repoInfo
152}
153
154// extractCurrentDir gets the current directory for markdown link resolution.
155// for blob paths, returns the parent dir. for tree paths, returns the path itself.
156//
157// /@user/repo/blob/main/docs/README.md => docs
158// /@user/repo/tree/main/docs => docs
159func extractCurrentDir(fullPath string) string {
160 fullPath = strings.TrimPrefix(fullPath, "/")
161
162 if matches := blobPattern.FindStringSubmatch(fullPath); len(matches) > 1 {
163 return path.Dir(matches[1])
164 }
165
166 if matches := treePattern.FindStringSubmatch(fullPath); len(matches) > 1 {
167 dir := strings.TrimSuffix(matches[1], "/")
168 if dir == "" {
169 return "."
170 }
171 return dir
172 }
173
174 return "."
175}