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