forked from tangled.org/core
Monorepo for Tangled — https://tangled.org
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 log.Println(result) 255 256 user := s.auth.GetUser(r) 257 s.pages.RepoBranches(w, pages.RepoBranchesParams{ 258 LoggedInUser: user, 259 RepoInfo: pages.RepoInfo{ 260 OwnerDid: f.OwnerDid(), 261 OwnerHandle: f.OwnerHandle(), 262 Name: f.RepoName, 263 SettingsAllowed: settingsAllowed(s, user, f), 264 }, 265 RepoBranchesResponse: result, 266 }) 267 return 268} 269 270func (s *State) RepoBlob(w http.ResponseWriter, r *http.Request) { 271 f, err := fullyResolvedRepo(r) 272 if err != nil { 273 log.Println("failed to get repo and knot", err) 274 return 275 } 276 277 ref := chi.URLParam(r, "ref") 278 filePath := chi.URLParam(r, "*") 279 resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/blob/%s/%s", f.Knot, f.OwnerDid(), f.RepoName, ref, filePath)) 280 if err != nil { 281 log.Println("failed to reach knotserver", err) 282 return 283 } 284 285 body, err := io.ReadAll(resp.Body) 286 if err != nil { 287 log.Fatalf("Error reading response body: %v", err) 288 return 289 } 290 291 var result types.RepoBlobResponse 292 err = json.Unmarshal(body, &result) 293 if err != nil { 294 log.Println("failed to parse response:", err) 295 return 296 } 297 298 user := s.auth.GetUser(r) 299 s.pages.RepoBlob(w, pages.RepoBlobParams{ 300 LoggedInUser: user, 301 RepoInfo: pages.RepoInfo{ 302 OwnerDid: f.OwnerDid(), 303 OwnerHandle: f.OwnerHandle(), 304 Name: f.RepoName, 305 SettingsAllowed: settingsAllowed(s, user, f), 306 }, 307 RepoBlobResponse: result, 308 }) 309 return 310} 311 312func (s *State) AddCollaborator(w http.ResponseWriter, r *http.Request) { 313 f, err := fullyResolvedRepo(r) 314 if err != nil { 315 log.Println("failed to get repo and knot", err) 316 return 317 } 318 319 collaborator := r.FormValue("collaborator") 320 if collaborator == "" { 321 http.Error(w, "malformed form", http.StatusBadRequest) 322 return 323 } 324 325 collaboratorIdent, err := s.resolver.ResolveIdent(r.Context(), collaborator) 326 if err != nil { 327 w.Write([]byte("failed to resolve collaborator did to a handle")) 328 return 329 } 330 log.Printf("adding %s to %s\n", collaboratorIdent.Handle.String(), f.Knot) 331 332 // TODO: create an atproto record for this 333 334 secret, err := s.db.GetRegistrationKey(f.Knot) 335 if err != nil { 336 log.Printf("no key found for domain %s: %s\n", f.Knot, err) 337 return 338 } 339 340 ksClient, err := NewSignedClient(f.Knot, secret) 341 if err != nil { 342 log.Println("failed to create client to ", f.Knot) 343 return 344 } 345 346 ksResp, err := ksClient.AddCollaborator(f.OwnerDid(), f.RepoName, collaboratorIdent.DID.String()) 347 if err != nil { 348 log.Printf("failed to make request to %s: %s", f.Knot, err) 349 return 350 } 351 352 if ksResp.StatusCode != http.StatusNoContent { 353 w.Write([]byte(fmt.Sprint("knotserver failed to add collaborator: ", err))) 354 return 355 } 356 357 err = s.enforcer.AddCollaborator(collaboratorIdent.DID.String(), f.Knot, f.OwnerSlashRepo()) 358 if err != nil { 359 w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err))) 360 return 361 } 362 363 w.Write([]byte(fmt.Sprint("added collaborator: ", collaboratorIdent.Handle.String()))) 364 365} 366 367func (s *State) RepoSettings(w http.ResponseWriter, r *http.Request) { 368 f, err := fullyResolvedRepo(r) 369 if err != nil { 370 log.Println("failed to get repo and knot", err) 371 return 372 } 373 374 switch r.Method { 375 case http.MethodGet: 376 // for now, this is just pubkeys 377 user := s.auth.GetUser(r) 378 repoCollaborators, err := s.enforcer.E.GetImplicitUsersForResourceByDomain(f.OwnerSlashRepo(), f.Knot) 379 if err != nil { 380 log.Println("failed to get collaborators", err) 381 } 382 log.Println(repoCollaborators) 383 384 isCollaboratorInviteAllowed := false 385 if user != nil { 386 ok, err := s.enforcer.IsCollaboratorInviteAllowed(user.Did, f.Knot, f.OwnerSlashRepo()) 387 if err == nil && ok { 388 isCollaboratorInviteAllowed = true 389 } 390 } 391 392 s.pages.RepoSettings(w, pages.RepoSettingsParams{ 393 LoggedInUser: user, 394 RepoInfo: pages.RepoInfo{ 395 OwnerDid: f.OwnerDid(), 396 OwnerHandle: f.OwnerHandle(), 397 Name: f.RepoName, 398 SettingsAllowed: settingsAllowed(s, user, f), 399 }, 400 Collaborators: repoCollaborators, 401 IsCollaboratorInviteAllowed: isCollaboratorInviteAllowed, 402 }) 403 } 404} 405 406type FullyResolvedRepo struct { 407 Knot string 408 OwnerId identity.Identity 409 RepoName string 410} 411 412func (f *FullyResolvedRepo) OwnerDid() string { 413 return f.OwnerId.DID.String() 414} 415 416func (f *FullyResolvedRepo) OwnerHandle() string { 417 return f.OwnerId.Handle.String() 418} 419 420func (f *FullyResolvedRepo) OwnerSlashRepo() string { 421 return filepath.Join(f.OwnerDid(), f.RepoName) 422} 423 424func fullyResolvedRepo(r *http.Request) (*FullyResolvedRepo, error) { 425 repoName := chi.URLParam(r, "repo") 426 knot, ok := r.Context().Value("knot").(string) 427 if !ok { 428 log.Println("malformed middleware") 429 return nil, fmt.Errorf("malformed middleware") 430 } 431 id, ok := r.Context().Value("resolvedId").(identity.Identity) 432 if !ok { 433 log.Println("malformed middleware") 434 return nil, fmt.Errorf("malformed middleware") 435 } 436 437 return &FullyResolvedRepo{ 438 Knot: knot, 439 OwnerId: id, 440 RepoName: repoName, 441 }, nil 442} 443 444func settingsAllowed(s *State, u *auth.User, f *FullyResolvedRepo) bool { 445 settingsAllowed := false 446 if u != nil { 447 ok, err := s.enforcer.IsSettingsAllowed(u.Did, f.Knot, f.OwnerSlashRepo()) 448 if err == nil && ok { 449 settingsAllowed = true 450 } 451 } 452 453 return settingsAllowed 454}