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