this repo has no description
1package state
2
3import (
4 "encoding/json"
5 "fmt"
6 "io"
7 "log"
8 "net/http"
9
10 "github.com/bluesky-social/indigo/atproto/identity"
11 securejoin "github.com/cyphar/filepath-securejoin"
12 "github.com/go-chi/chi/v5"
13 "github.com/sotangled/tangled/appview/auth"
14 "github.com/sotangled/tangled/appview/pages"
15 "github.com/sotangled/tangled/types"
16)
17
18func (s *State) RepoIndex(w http.ResponseWriter, r *http.Request) {
19 f, err := fullyResolvedRepo(r)
20 if err != nil {
21 log.Println("failed to fully resolve repo", err)
22 return
23 }
24
25 resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s", f.Knot, f.OwnerDid(), f.RepoName))
26 if err != nil {
27 s.pages.Error503(w)
28 log.Println("failed to reach knotserver", err)
29 return
30 }
31 defer resp.Body.Close()
32
33 body, err := io.ReadAll(resp.Body)
34 if err != nil {
35 log.Fatalf("Error reading response body: %v", err)
36 return
37 }
38
39 var result types.RepoIndexResponse
40 err = json.Unmarshal(body, &result)
41 if err != nil {
42 log.Fatalf("Error unmarshalling response body: %v", err)
43 return
44 }
45
46 log.Println(resp.Status, result)
47
48 user := s.auth.GetUser(r)
49 s.pages.RepoIndexPage(w, pages.RepoIndexParams{
50 LoggedInUser: user,
51 RepoInfo: pages.RepoInfo{
52 OwnerDid: f.OwnerDid(),
53 OwnerHandle: f.OwnerHandle(),
54 Name: f.RepoName,
55 SettingsAllowed: settingsAllowed(s, user, f),
56 },
57 RepoIndexResponse: result,
58 })
59
60 return
61}
62
63func (s *State) RepoLog(w http.ResponseWriter, r *http.Request) {
64 f, err := fullyResolvedRepo(r)
65 if err != nil {
66 log.Println("failed to fully resolve repo", err)
67 return
68 }
69
70 ref := chi.URLParam(r, "ref")
71 resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/log/%s", f.Knot, f.OwnerDid(), f.RepoName, ref))
72 if err != nil {
73 log.Println("failed to reach knotserver", err)
74 return
75 }
76
77 body, err := io.ReadAll(resp.Body)
78 if err != nil {
79 log.Fatalf("Error reading response body: %v", err)
80 return
81 }
82
83 var result types.RepoLogResponse
84 err = json.Unmarshal(body, &result)
85 if err != nil {
86 log.Println("failed to parse json response", err)
87 return
88 }
89
90 user := s.auth.GetUser(r)
91 s.pages.RepoLog(w, pages.RepoLogParams{
92 LoggedInUser: user,
93 RepoInfo: pages.RepoInfo{
94 OwnerDid: f.OwnerDid(),
95 OwnerHandle: f.OwnerHandle(),
96 Name: f.RepoName,
97 SettingsAllowed: settingsAllowed(s, user, f),
98 },
99 RepoLogResponse: result,
100 })
101 return
102}
103
104func (s *State) RepoCommit(w http.ResponseWriter, r *http.Request) {
105 f, err := fullyResolvedRepo(r)
106 if err != nil {
107 log.Println("failed to fully resolve repo", err)
108 return
109 }
110
111 ref := chi.URLParam(r, "ref")
112 resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/commit/%s", f.Knot, f.OwnerDid(), f.RepoName, ref))
113 if err != nil {
114 log.Println("failed to reach knotserver", err)
115 return
116 }
117
118 body, err := io.ReadAll(resp.Body)
119 if err != nil {
120 log.Fatalf("Error reading response body: %v", err)
121 return
122 }
123
124 var result types.RepoCommitResponse
125 err = json.Unmarshal(body, &result)
126 if err != nil {
127 log.Println("failed to parse response:", err)
128 return
129 }
130
131 user := s.auth.GetUser(r)
132 s.pages.RepoCommit(w, pages.RepoCommitParams{
133 LoggedInUser: user,
134 RepoInfo: pages.RepoInfo{
135 OwnerDid: f.OwnerDid(),
136 OwnerHandle: f.OwnerHandle(),
137 Name: f.RepoName,
138 SettingsAllowed: settingsAllowed(s, user, f),
139 },
140 RepoCommitResponse: result,
141 })
142 return
143}
144
145func (s *State) RepoTree(w http.ResponseWriter, r *http.Request) {
146 f, err := fullyResolvedRepo(r)
147 if err != nil {
148 log.Println("failed to fully resolve repo", err)
149 return
150 }
151
152 ref := chi.URLParam(r, "ref")
153 treePath := chi.URLParam(r, "*")
154 resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/tree/%s/%s", f.Knot, f.OwnerDid(), f.RepoName, ref, treePath))
155 if err != nil {
156 log.Println("failed to reach knotserver", err)
157 return
158 }
159
160 body, err := io.ReadAll(resp.Body)
161 if err != nil {
162 log.Fatalf("Error reading response body: %v", err)
163 return
164 }
165
166 var result types.RepoTreeResponse
167 err = json.Unmarshal(body, &result)
168 if err != nil {
169 log.Println("failed to parse response:", err)
170 return
171 }
172
173 log.Println(result)
174
175 user := s.auth.GetUser(r)
176 s.pages.RepoTree(w, pages.RepoTreeParams{
177 LoggedInUser: user,
178 RepoInfo: pages.RepoInfo{
179 OwnerDid: f.OwnerDid(),
180 OwnerHandle: f.OwnerHandle(),
181 Name: f.RepoName,
182 SettingsAllowed: settingsAllowed(s, user, f),
183 },
184 RepoTreeResponse: result,
185 })
186 return
187}
188
189func (s *State) RepoTags(w http.ResponseWriter, r *http.Request) {
190 f, err := fullyResolvedRepo(r)
191 if err != nil {
192 log.Println("failed to get repo and knot", err)
193 return
194 }
195
196 resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/tags", f.Knot, f.OwnerDid(), f.RepoName))
197 if err != nil {
198 log.Println("failed to reach knotserver", err)
199 return
200 }
201
202 body, err := io.ReadAll(resp.Body)
203 if err != nil {
204 log.Fatalf("Error reading response body: %v", err)
205 return
206 }
207
208 var result types.RepoTagsResponse
209 err = json.Unmarshal(body, &result)
210 if err != nil {
211 log.Println("failed to parse response:", err)
212 return
213 }
214
215 user := s.auth.GetUser(r)
216 s.pages.RepoTags(w, pages.RepoTagsParams{
217 LoggedInUser: user,
218 RepoInfo: pages.RepoInfo{
219 OwnerDid: f.OwnerDid(),
220 OwnerHandle: f.OwnerHandle(),
221 Name: f.RepoName,
222 SettingsAllowed: settingsAllowed(s, user, f),
223 },
224 RepoTagsResponse: result,
225 })
226 return
227}
228
229func (s *State) RepoBranches(w http.ResponseWriter, r *http.Request) {
230 f, err := fullyResolvedRepo(r)
231 if err != nil {
232 log.Println("failed to get repo and knot", err)
233 return
234 }
235
236 resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/branches", f.Knot, f.OwnerDid(), f.RepoName))
237 if err != nil {
238 log.Println("failed to reach knotserver", err)
239 return
240 }
241
242 body, err := io.ReadAll(resp.Body)
243 if err != nil {
244 log.Fatalf("Error reading response body: %v", err)
245 return
246 }
247
248 var result types.RepoBranchesResponse
249 err = json.Unmarshal(body, &result)
250 if err != nil {
251 log.Println("failed to parse response:", err)
252 return
253 }
254
255 log.Println(result)
256
257 user := s.auth.GetUser(r)
258 s.pages.RepoBranches(w, pages.RepoBranchesParams{
259 LoggedInUser: user,
260 RepoInfo: pages.RepoInfo{
261 OwnerDid: f.OwnerDid(),
262 OwnerHandle: f.OwnerHandle(),
263 Name: f.RepoName,
264 SettingsAllowed: settingsAllowed(s, user, f),
265 },
266 RepoBranchesResponse: result,
267 })
268 return
269}
270
271func (s *State) RepoBlob(w http.ResponseWriter, r *http.Request) {
272 f, err := fullyResolvedRepo(r)
273 if err != nil {
274 log.Println("failed to get repo and knot", err)
275 return
276 }
277
278 ref := chi.URLParam(r, "ref")
279 filePath := chi.URLParam(r, "*")
280 resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/blob/%s/%s", f.Knot, f.OwnerDid(), f.RepoName, ref, filePath))
281 if err != nil {
282 log.Println("failed to reach knotserver", err)
283 return
284 }
285
286 body, err := io.ReadAll(resp.Body)
287 if err != nil {
288 log.Fatalf("Error reading response body: %v", err)
289 return
290 }
291
292 var result types.RepoBlobResponse
293 err = json.Unmarshal(body, &result)
294 if err != nil {
295 log.Println("failed to parse response:", err)
296 return
297 }
298
299 user := s.auth.GetUser(r)
300 s.pages.RepoBlob(w, pages.RepoBlobParams{
301 LoggedInUser: user,
302 RepoInfo: pages.RepoInfo{
303 OwnerDid: f.OwnerDid(),
304 OwnerHandle: f.OwnerHandle(),
305 Name: f.RepoName,
306 SettingsAllowed: settingsAllowed(s, user, f),
307 },
308 RepoBlobResponse: result,
309 })
310 return
311}
312
313func (s *State) AddCollaborator(w http.ResponseWriter, r *http.Request) {
314 f, err := fullyResolvedRepo(r)
315 if err != nil {
316 log.Println("failed to get repo and knot", err)
317 return
318 }
319
320 collaborator := r.FormValue("collaborator")
321 if collaborator == "" {
322 http.Error(w, "malformed form", http.StatusBadRequest)
323 return
324 }
325
326 collaboratorIdent, err := s.resolver.ResolveIdent(r.Context(), collaborator)
327 if err != nil {
328 w.Write([]byte("failed to resolve collaborator did to a handle"))
329 return
330 }
331 log.Printf("adding %s to %s\n", collaboratorIdent.Handle.String(), f.Knot)
332
333 // TODO: create an atproto record for this
334
335 secret, err := s.db.GetRegistrationKey(f.Knot)
336 if err != nil {
337 log.Printf("no key found for domain %s: %s\n", f.Knot, err)
338 return
339 }
340
341 ksClient, err := NewSignedClient(f.Knot, secret)
342 if err != nil {
343 log.Println("failed to create client to ", f.Knot)
344 return
345 }
346
347 ksResp, err := ksClient.AddCollaborator(f.OwnerDid(), f.RepoName, collaboratorIdent.DID.String())
348 if err != nil {
349 log.Printf("failed to make request to %s: %s", f.Knot, err)
350 return
351 }
352
353 if ksResp.StatusCode != http.StatusNoContent {
354 w.Write([]byte(fmt.Sprint("knotserver failed to add collaborator: ", err)))
355 return
356 }
357
358 err = s.enforcer.AddCollaborator(collaboratorIdent.DID.String(), f.Knot, f.OwnerSlashRepo())
359 if err != nil {
360 w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err)))
361 return
362 }
363
364 w.Write([]byte(fmt.Sprint("added collaborator: ", collaboratorIdent.Handle.String())))
365
366}
367
368func (s *State) RepoSettings(w http.ResponseWriter, r *http.Request) {
369 f, err := fullyResolvedRepo(r)
370 if err != nil {
371 log.Println("failed to get repo and knot", err)
372 return
373 }
374
375 switch r.Method {
376 case http.MethodGet:
377 // for now, this is just pubkeys
378 user := s.auth.GetUser(r)
379 repoCollaborators, err := s.enforcer.E.GetImplicitUsersForResourceByDomain(f.OwnerSlashRepo(), f.Knot)
380 if err != nil {
381 log.Println("failed to get collaborators", err)
382 }
383 log.Println(repoCollaborators)
384
385 isCollaboratorInviteAllowed := false
386 if user != nil {
387 ok, err := s.enforcer.IsCollaboratorInviteAllowed(user.Did, f.Knot, f.OwnerSlashRepo())
388 if err == nil && ok {
389 isCollaboratorInviteAllowed = true
390 }
391 }
392
393 s.pages.RepoSettings(w, pages.RepoSettingsParams{
394 LoggedInUser: user,
395 RepoInfo: pages.RepoInfo{
396 OwnerDid: f.OwnerDid(),
397 OwnerHandle: f.OwnerHandle(),
398 Name: f.RepoName,
399 SettingsAllowed: settingsAllowed(s, user, f),
400 },
401 Collaborators: repoCollaborators,
402 IsCollaboratorInviteAllowed: isCollaboratorInviteAllowed,
403 })
404 }
405}
406
407type FullyResolvedRepo struct {
408 Knot string
409 OwnerId identity.Identity
410 RepoName string
411}
412
413func (f *FullyResolvedRepo) OwnerDid() string {
414 return f.OwnerId.DID.String()
415}
416
417func (f *FullyResolvedRepo) OwnerHandle() string {
418 return f.OwnerId.Handle.String()
419}
420
421func (f *FullyResolvedRepo) OwnerSlashRepo() string {
422 p, _ := securejoin.SecureJoin(f.OwnerDid(), f.RepoName)
423 return p
424}
425
426func fullyResolvedRepo(r *http.Request) (*FullyResolvedRepo, error) {
427 repoName := chi.URLParam(r, "repo")
428 knot, ok := r.Context().Value("knot").(string)
429 if !ok {
430 log.Println("malformed middleware")
431 return nil, fmt.Errorf("malformed middleware")
432 }
433 id, ok := r.Context().Value("resolvedId").(identity.Identity)
434 if !ok {
435 log.Println("malformed middleware")
436 return nil, fmt.Errorf("malformed middleware")
437 }
438
439 return &FullyResolvedRepo{
440 Knot: knot,
441 OwnerId: id,
442 RepoName: repoName,
443 }, nil
444}
445
446func settingsAllowed(s *State, u *auth.User, f *FullyResolvedRepo) bool {
447 settingsAllowed := false
448 if u != nil {
449 ok, err := s.enforcer.IsSettingsAllowed(u.Did, f.Knot, f.OwnerSlashRepo())
450 if err == nil && ok {
451 settingsAllowed = true
452 }
453 }
454
455 return settingsAllowed
456}