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