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}