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}