forked from
tangled.org/core
Monorepo for Tangled — https://tangled.org
1package state
2
3import (
4 "context"
5 "crypto/rand"
6 "fmt"
7 "log"
8 "math/big"
9 "net/http"
10
11 "github.com/bluesky-social/indigo/atproto/identity"
12 "github.com/bluesky-social/indigo/atproto/syntax"
13 "github.com/go-chi/chi/v5"
14 "github.com/go-git/go-git/v5/plumbing/object"
15 "go.opentelemetry.io/otel/attribute"
16 "tangled.sh/tangled.sh/core/appview/auth"
17 "tangled.sh/tangled.sh/core/appview/db"
18 "tangled.sh/tangled.sh/core/appview/pages/repoinfo"
19 "tangled.sh/tangled.sh/core/telemetry"
20)
21
22func (s *State) fullyResolvedRepo(r *http.Request) (*FullyResolvedRepo, error) {
23 ctx := r.Context()
24
25 attrs := telemetry.MapAttrs(map[string]string{
26 "repo": chi.URLParam(r, "repo"),
27 "ref": chi.URLParam(r, "ref"),
28 })
29
30 ctx, span := s.t.TraceStart(ctx, "fullyResolvedRepo", attrs...)
31 defer span.End()
32
33 repoName := chi.URLParam(r, "repo")
34 knot, ok := ctx.Value("knot").(string)
35 if !ok {
36 log.Println("malformed middleware")
37 return nil, fmt.Errorf("malformed middleware")
38 }
39
40 span.SetAttributes(attribute.String("knot", knot))
41
42 id, ok := ctx.Value("resolvedId").(identity.Identity)
43 if !ok {
44 log.Println("malformed middleware")
45 return nil, fmt.Errorf("malformed middleware")
46 }
47
48 span.SetAttributes(attribute.String("did", id.DID.String()))
49
50 repoAt, ok := ctx.Value("repoAt").(string)
51 if !ok {
52 log.Println("malformed middleware")
53 return nil, fmt.Errorf("malformed middleware")
54 }
55
56 parsedRepoAt, err := syntax.ParseATURI(repoAt)
57 if err != nil {
58 log.Println("malformed repo at-uri")
59 return nil, fmt.Errorf("malformed middleware")
60 }
61
62 ref := chi.URLParam(r, "ref")
63
64 if ref == "" {
65 us, err := NewUnsignedClient(knot, s.config.Dev)
66 if err != nil {
67 return nil, err
68 }
69
70 defaultBranch, err := us.DefaultBranch(id.DID.String(), repoName)
71 if err != nil {
72 return nil, err
73 }
74
75 ref = defaultBranch.Branch
76
77 span.SetAttributes(attribute.String("default_branch", ref))
78 }
79
80 description, ok := ctx.Value("repoDescription").(string)
81 addedAt, ok := ctx.Value("repoAddedAt").(string)
82
83 return &FullyResolvedRepo{
84 Knot: knot,
85 OwnerId: id,
86 RepoName: repoName,
87 RepoAt: parsedRepoAt,
88 Description: description,
89 CreatedAt: addedAt,
90 Ref: ref,
91 }, nil
92}
93
94func RolesInRepo(s *State, u *auth.User, f *FullyResolvedRepo) repoinfo.RolesInRepo {
95 if u != nil {
96 r := s.enforcer.GetPermissionsInRepo(u.Did, f.Knot, f.DidSlashRepo())
97 return repoinfo.RolesInRepo{r}
98 } else {
99 return repoinfo.RolesInRepo{}
100 }
101}
102
103func uniqueEmails(commits []*object.Commit) []string {
104 emails := make(map[string]struct{})
105 for _, commit := range commits {
106 if commit.Author.Email != "" {
107 emails[commit.Author.Email] = struct{}{}
108 }
109 if commit.Committer.Email != "" {
110 emails[commit.Committer.Email] = struct{}{}
111 }
112 }
113 var uniqueEmails []string
114 for email := range emails {
115 uniqueEmails = append(uniqueEmails, email)
116 }
117 return uniqueEmails
118}
119
120func balanceIndexItems(commitCount, branchCount, tagCount, fileCount int) (commitsTrunc int, branchesTrunc int, tagsTrunc int) {
121 if commitCount == 0 && tagCount == 0 && branchCount == 0 {
122 return
123 }
124
125 // typically 1 item on right side = 2 files in height
126 availableSpace := fileCount / 2
127
128 // clamp tagcount
129 if tagCount > 0 {
130 tagsTrunc = 1
131 availableSpace -= 1 // an extra subtracted for headers etc.
132 }
133
134 // clamp branchcount
135 if branchCount > 0 {
136 branchesTrunc = min(max(branchCount, 1), 2)
137 availableSpace -= branchesTrunc // an extra subtracted for headers etc.
138 }
139
140 // show
141 if commitCount > 0 {
142 commitsTrunc = max(availableSpace, 3)
143 }
144
145 return
146}
147
148func EmailToDidOrHandle(s *State, emails []string) map[string]string {
149 emailToDid, err := db.GetEmailToDid(s.db, emails, true) // only get verified emails for mapping
150 if err != nil {
151 log.Printf("error fetching dids for emails: %v", err)
152 return nil
153 }
154
155 var dids []string
156 for _, v := range emailToDid {
157 dids = append(dids, v)
158 }
159 resolvedIdents := s.resolver.ResolveIdents(context.Background(), dids)
160
161 didHandleMap := make(map[string]string)
162 for _, identity := range resolvedIdents {
163 if !identity.Handle.IsInvalidHandle() {
164 didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
165 } else {
166 didHandleMap[identity.DID.String()] = identity.DID.String()
167 }
168 }
169
170 // Create map of email to didOrHandle for commit display
171 emailToDidOrHandle := make(map[string]string)
172 for email, did := range emailToDid {
173 if didOrHandle, ok := didHandleMap[did]; ok {
174 emailToDidOrHandle[email] = didOrHandle
175 }
176 }
177
178 return emailToDidOrHandle
179}
180
181func randomString(n int) string {
182 const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
183 result := make([]byte, n)
184
185 for i := 0; i < n; i++ {
186 n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
187 result[i] = letters[n.Int64()]
188 }
189
190 return string(result)
191}