forked from tangled.org/core
this repo has no description

Compare changes

Choose any two refs to compare.

+4 -8
appview/db/issues.go
··· 359 359 repoMap[string(repos[i].RepoAt())] = &repos[i] 360 360 } 361 361 362 - for issueAt, i := range issueMap { 363 - if r, ok := repoMap[string(i.RepoAt)]; ok { 364 - i.Repo = r 365 - } else { 366 - // do not show up the issue if the repo is deleted 367 - // TODO: foreign key where? 368 - delete(issueMap, issueAt) 369 - } 362 + for issueAt := range issueMap { 363 + i := issueMap[issueAt] 364 + r := repoMap[string(i.RepoAt)] 365 + i.Repo = r 370 366 } 371 367 372 368 // collect comments
+3 -3
appview/pages/templates/repo/tree.html
··· 25 25 <div class="flex flex-col md:flex-row md:justify-between gap-2"> 26 26 <div id="breadcrumbs" class="overflow-x-auto whitespace-nowrap text-gray-400 dark:text-gray-500"> 27 27 {{ range .BreadCrumbs }} 28 - <a href="{{ index . 1}}" class="text-bold text-gray-500 dark:text-gray-400 {{ $linkstyle }}">{{ pathUnescape (index . 0) }}</a> / 28 + <a href="{{ index . 1 }}" class="text-bold text-gray-500 dark:text-gray-400 {{ $linkstyle }}">{{ pathUnescape (index . 0) }}</a> / 29 29 {{ end }} 30 30 </div> 31 31 <div id="dir-info" class="text-gray-500 dark:text-gray-400 text-xs md:text-sm flex flex-wrap items-center gap-1 md:gap-0"> 32 32 {{ $stats := .TreeStats }} 33 33 34 - <span>at <a href="/{{ $.RepoInfo.FullName }}/tree/{{ $.Ref }}">{{ $.Ref }}</a></span> 34 + <span>at <a href="/{{ $.RepoInfo.FullName }}/tree/{{ pathEscape $.Ref }}">{{ $.Ref }}</a></span> 35 35 {{ if eq $stats.NumFolders 1 }} 36 36 <span class="select-none px-1 md:px-2 [&:before]:content-['ยท']"></span> 37 37 <span>{{ $stats.NumFolders }} folder</span> ··· 55 55 {{ range .Files }} 56 56 <div class="grid grid-cols-12 gap-4 items-center py-1"> 57 57 <div class="col-span-8 md:col-span-4"> 58 - {{ $link := printf "/%s/%s/%s/%s/%s" $.RepoInfo.FullName "tree" (urlquery $.Ref) $.TreePath .Name }} 58 + {{ $link := printf "/%s/%s/%s/%s/%s" $.RepoInfo.FullName "tree" (pathEscape $.Ref) $.TreePath .Name }} 59 59 {{ $icon := "folder" }} 60 60 {{ $iconStyle := "size-4 fill-current" }} 61 61
+16 -17
appview/repo/index.go
··· 5 5 "fmt" 6 6 "log" 7 7 "net/http" 8 + "net/url" 8 9 "slices" 9 10 "sort" 10 11 "strings" ··· 31 32 32 33 func (rp *Repo) RepoIndex(w http.ResponseWriter, r *http.Request) { 33 34 ref := chi.URLParam(r, "ref") 35 + ref, _ = url.PathUnescape(ref) 34 36 35 37 f, err := rp.repoResolver.Resolve(r) 36 38 if err != nil { ··· 61 63 RepoInfo: repoInfo, 62 64 }) 63 65 return 64 - } else { 65 - rp.pages.Error503(w) 66 - log.Println("failed to build index response", err) 67 - return 68 66 } 67 + 68 + rp.pages.Error503(w) 69 + log.Println("failed to build index response", err) 70 + return 69 71 } 70 72 71 73 tagMap := make(map[string][]string) ··· 245 247 // first get branches to determine the ref if not specified 246 248 branchesBytes, err := tangled.RepoBranches(ctx, xrpcc, "", 0, repo) 247 249 if err != nil { 248 - return nil, err 250 + return nil, fmt.Errorf("failed to call repoBranches: %w", err) 249 251 } 250 252 251 253 var branchesResp types.RepoBranchesResponse 252 254 if err := json.Unmarshal(branchesBytes, &branchesResp); err != nil { 253 - return nil, err 255 + return nil, fmt.Errorf("failed to unmarshal branches response: %w", err) 254 256 } 255 257 256 258 // if no ref specified, use default branch or first available 257 - if ref == "" && len(branchesResp.Branches) > 0 { 259 + if ref == "" { 258 260 for _, branch := range branchesResp.Branches { 259 261 if branch.IsDefault { 260 262 ref = branch.Name 261 263 break 262 264 } 263 265 } 264 - if ref == "" { 265 - ref = branchesResp.Branches[0].Name 266 - } 267 266 } 268 267 269 - // check if repo is empty 270 - if len(branchesResp.Branches) == 0 { 268 + // if ref is still empty, this means the default branch is not set 269 + if ref == "" { 271 270 return &types.RepoIndexResponse{ 272 271 IsEmpty: true, 273 272 Branches: branchesResp.Branches, ··· 292 291 defer wg.Done() 293 292 tagsBytes, err := tangled.RepoTags(ctx, xrpcc, "", 0, repo) 294 293 if err != nil { 295 - errs = errors.Join(errs, err) 294 + errs = errors.Join(errs, fmt.Errorf("failed to call repoTags: %w", err)) 296 295 return 297 296 } 298 297 299 298 if err := json.Unmarshal(tagsBytes, &tagsResp); err != nil { 300 - errs = errors.Join(errs, err) 299 + errs = errors.Join(errs, fmt.Errorf("failed to unmarshal repoTags: %w", err)) 301 300 } 302 301 }() 303 302 ··· 307 306 defer wg.Done() 308 307 resp, err := tangled.RepoTree(ctx, xrpcc, "", ref, repo) 309 308 if err != nil { 310 - errs = errors.Join(errs, err) 309 + errs = errors.Join(errs, fmt.Errorf("failed to call repoTree: %w", err)) 311 310 return 312 311 } 313 312 treeResp = resp ··· 319 318 defer wg.Done() 320 319 logBytes, err := tangled.RepoLog(ctx, xrpcc, "", 50, "", ref, repo) 321 320 if err != nil { 322 - errs = errors.Join(errs, err) 321 + errs = errors.Join(errs, fmt.Errorf("failed to call repoLog: %w", err)) 323 322 return 324 323 } 325 324 326 325 if err := json.Unmarshal(logBytes, &logResp); err != nil { 327 - errs = errors.Join(errs, err) 326 + errs = errors.Join(errs, fmt.Errorf("failed to unmarshal repoLog: %w", err)) 328 327 } 329 328 }() 330 329
+97 -118
appview/repo/repo.go
··· 85 85 } 86 86 87 87 func (rp *Repo) DownloadArchive(w http.ResponseWriter, r *http.Request) { 88 - refParam := chi.URLParam(r, "ref") 88 + ref := chi.URLParam(r, "ref") 89 + ref, _ = url.PathUnescape(ref) 90 + 89 91 f, err := rp.repoResolver.Resolve(r) 90 92 if err != nil { 91 93 log.Println("failed to get repo and knot", err) ··· 102 104 } 103 105 104 106 repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 105 - archiveBytes, err := tangled.RepoArchive(r.Context(), xrpcc, "tar.gz", "", refParam, repo) 106 - if err != nil { 107 - if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 108 - log.Println("failed to call XRPC repo.archive", xrpcerr) 109 - rp.pages.Error503(w) 110 - return 111 - } 112 - rp.pages.Error404(w) 107 + archiveBytes, err := tangled.RepoArchive(r.Context(), xrpcc, "tar.gz", "", ref, repo) 108 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 109 + log.Println("failed to call XRPC repo.archive", xrpcerr) 110 + rp.pages.Error503(w) 113 111 return 114 112 } 115 113 116 - // Set headers for file download 117 - filename := fmt.Sprintf("%s-%s.tar.gz", f.Name, refParam) 114 + // Set headers for file download, just pass along whatever the knot specifies 115 + safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-") 116 + filename := fmt.Sprintf("%s-%s.tar.gz", f.Name, safeRefFilename) 118 117 w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) 119 118 w.Header().Set("Content-Type", "application/gzip") 120 119 w.Header().Set("Content-Length", fmt.Sprintf("%d", len(archiveBytes))) ··· 139 138 } 140 139 141 140 ref := chi.URLParam(r, "ref") 141 + ref, _ = url.PathUnescape(ref) 142 142 143 143 scheme := "http" 144 144 if !rp.config.Core.Dev { ··· 159 159 160 160 repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 161 161 xrpcBytes, err := tangled.RepoLog(r.Context(), xrpcc, cursor, limit, "", ref, repo) 162 - if err != nil { 163 - if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 164 - log.Println("failed to call XRPC repo.log", xrpcerr) 165 - rp.pages.Error503(w) 166 - return 167 - } 168 - rp.pages.Error404(w) 162 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 163 + log.Println("failed to call XRPC repo.log", xrpcerr) 164 + rp.pages.Error503(w) 169 165 return 170 166 } 171 167 ··· 177 173 } 178 174 179 175 tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo) 180 - if err != nil { 181 - if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 182 - log.Println("failed to call XRPC repo.tags", xrpcerr) 183 - rp.pages.Error503(w) 184 - return 185 - } 176 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 177 + log.Println("failed to call XRPC repo.tags", xrpcerr) 178 + rp.pages.Error503(w) 179 + return 186 180 } 187 181 188 182 tagMap := make(map[string][]string) ··· 196 190 } 197 191 198 192 branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo) 199 - if err != nil { 200 - if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 201 - log.Println("failed to call XRPC repo.branches", xrpcerr) 202 - rp.pages.Error503(w) 203 - return 204 - } 193 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 194 + log.Println("failed to call XRPC repo.branches", xrpcerr) 195 + rp.pages.Error503(w) 196 + return 205 197 } 206 198 207 199 if branchBytes != nil { ··· 353 345 return 354 346 } 355 347 ref := chi.URLParam(r, "ref") 348 + ref, _ = url.PathUnescape(ref) 356 349 357 350 var diffOpts types.DiffOpts 358 351 if d := r.URL.Query().Get("diff"); d == "split" { ··· 375 368 376 369 repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 377 370 xrpcBytes, err := tangled.RepoDiff(r.Context(), xrpcc, ref, repo) 378 - if err != nil { 379 - if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 380 - log.Println("failed to call XRPC repo.diff", xrpcerr) 381 - rp.pages.Error503(w) 382 - return 383 - } 384 - rp.pages.Error404(w) 371 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 372 + log.Println("failed to call XRPC repo.diff", xrpcerr) 373 + rp.pages.Error503(w) 385 374 return 386 375 } 387 376 ··· 433 422 } 434 423 435 424 ref := chi.URLParam(r, "ref") 436 - treePath := chi.URLParam(r, "*") 425 + ref, _ = url.PathUnescape(ref) 437 426 438 427 // if the tree path has a trailing slash, let's strip it 439 428 // so we don't 404 429 + treePath := chi.URLParam(r, "*") 430 + treePath, _ = url.PathUnescape(treePath) 440 431 treePath = strings.TrimSuffix(treePath, "/") 441 432 442 433 scheme := "http" ··· 450 441 451 442 repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 452 443 xrpcResp, err := tangled.RepoTree(r.Context(), xrpcc, treePath, ref, repo) 453 - if err != nil { 454 - if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 455 - log.Println("failed to call XRPC repo.tree", xrpcerr) 456 - rp.pages.Error503(w) 457 - return 458 - } 459 - rp.pages.Error404(w) 444 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 445 + log.Println("failed to call XRPC repo.tree", xrpcerr) 446 + rp.pages.Error503(w) 460 447 return 461 448 } 462 449 ··· 498 485 499 486 // redirects tree paths trying to access a blob; in this case the result.Files is unpopulated, 500 487 // so we can safely redirect to the "parent" (which is the same file). 501 - unescapedTreePath, _ := url.PathUnescape(treePath) 502 - if len(result.Files) == 0 && result.Parent == unescapedTreePath { 503 - http.Redirect(w, r, fmt.Sprintf("/%s/blob/%s/%s", f.OwnerSlashRepo(), ref, result.Parent), http.StatusFound) 488 + if len(result.Files) == 0 && result.Parent == treePath { 489 + redirectTo := fmt.Sprintf("/%s/blob/%s/%s", f.OwnerSlashRepo(), url.PathEscape(ref), result.Parent) 490 + http.Redirect(w, r, redirectTo, http.StatusFound) 504 491 return 505 492 } 506 493 507 494 user := rp.oauth.GetUser(r) 508 495 509 496 var breadcrumbs [][]string 510 - breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), ref)}) 497 + breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), url.PathEscape(ref))}) 511 498 if treePath != "" { 512 499 for idx, elem := range strings.Split(treePath, "/") { 513 - breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], elem)}) 500 + breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], url.PathEscape(elem))}) 514 501 } 515 502 } 516 503 ··· 543 530 544 531 repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 545 532 xrpcBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo) 546 - if err != nil { 547 - if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 548 - log.Println("failed to call XRPC repo.tags", xrpcerr) 549 - rp.pages.Error503(w) 550 - return 551 - } 552 - rp.pages.Error404(w) 533 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 534 + log.Println("failed to call XRPC repo.tags", xrpcerr) 535 + rp.pages.Error503(w) 553 536 return 554 537 } 555 538 ··· 616 599 617 600 repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 618 601 xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo) 619 - if err != nil { 620 - if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 621 - log.Println("failed to call XRPC repo.branches", xrpcerr) 622 - rp.pages.Error503(w) 623 - return 624 - } 625 - rp.pages.Error404(w) 602 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 603 + log.Println("failed to call XRPC repo.branches", xrpcerr) 604 + rp.pages.Error503(w) 626 605 return 627 606 } 628 607 ··· 651 630 } 652 631 653 632 ref := chi.URLParam(r, "ref") 633 + ref, _ = url.PathUnescape(ref) 634 + 654 635 filePath := chi.URLParam(r, "*") 636 + filePath, _ = url.PathUnescape(filePath) 655 637 656 638 scheme := "http" 657 639 if !rp.config.Core.Dev { ··· 664 646 665 647 repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Repo.Name) 666 648 resp, err := tangled.RepoBlob(r.Context(), xrpcc, filePath, false, ref, repo) 667 - if err != nil { 668 - if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 669 - log.Println("failed to call XRPC repo.blob", xrpcerr) 670 - rp.pages.Error503(w) 671 - return 672 - } 673 - rp.pages.Error404(w) 649 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 650 + log.Println("failed to call XRPC repo.blob", xrpcerr) 651 + rp.pages.Error503(w) 674 652 return 675 653 } 676 654 677 655 // Use XRPC response directly instead of converting to internal types 678 656 679 657 var breadcrumbs [][]string 680 - breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), ref)}) 658 + breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), url.PathEscape(ref))}) 681 659 if filePath != "" { 682 660 for idx, elem := range strings.Split(filePath, "/") { 683 - breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], elem)}) 661 + breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], url.PathEscape(elem))}) 684 662 } 685 663 } 686 664 ··· 710 688 711 689 // fetch the raw binary content using sh.tangled.repo.blob xrpc 712 690 repoName := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 713 - blobURL := fmt.Sprintf("%s://%s/xrpc/sh.tangled.repo.blob?repo=%s&ref=%s&path=%s&raw=true", 714 - scheme, f.Knot, url.QueryEscape(repoName), url.QueryEscape(ref), url.QueryEscape(filePath)) 691 + 692 + baseURL := &url.URL{ 693 + Scheme: scheme, 694 + Host: f.Knot, 695 + Path: "/xrpc/sh.tangled.repo.blob", 696 + } 697 + query := baseURL.Query() 698 + query.Set("repo", repoName) 699 + query.Set("ref", ref) 700 + query.Set("path", filePath) 701 + query.Set("raw", "true") 702 + baseURL.RawQuery = query.Encode() 703 + blobURL := baseURL.String() 715 704 716 705 contentSrc = blobURL 717 706 if !rp.config.Core.Dev { ··· 766 755 } 767 756 768 757 ref := chi.URLParam(r, "ref") 758 + ref, _ = url.PathUnescape(ref) 759 + 769 760 filePath := chi.URLParam(r, "*") 761 + filePath, _ = url.PathUnescape(filePath) 770 762 771 763 scheme := "http" 772 764 if !rp.config.Core.Dev { ··· 774 766 } 775 767 776 768 repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Repo.Name) 777 - blobURL := fmt.Sprintf("%s://%s/xrpc/sh.tangled.repo.blob?repo=%s&ref=%s&path=%s&raw=true", 778 - scheme, f.Knot, url.QueryEscape(repo), url.QueryEscape(ref), url.QueryEscape(filePath)) 769 + baseURL := &url.URL{ 770 + Scheme: scheme, 771 + Host: f.Knot, 772 + Path: "/xrpc/sh.tangled.repo.blob", 773 + } 774 + query := baseURL.Query() 775 + query.Set("repo", repo) 776 + query.Set("ref", ref) 777 + query.Set("path", filePath) 778 + query.Set("raw", "true") 779 + baseURL.RawQuery = query.Encode() 780 + blobURL := baseURL.String() 779 781 780 782 req, err := http.NewRequest("GET", blobURL, nil) 781 783 if err != nil { ··· 1364 1366 1365 1367 repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 1366 1368 xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo) 1367 - if err != nil { 1368 - if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1369 - log.Println("failed to call XRPC repo.branches", xrpcerr) 1370 - rp.pages.Error503(w) 1371 - return 1372 - } 1369 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1370 + log.Println("failed to call XRPC repo.branches", xrpcerr) 1373 1371 rp.pages.Error503(w) 1374 1372 return 1375 1373 } ··· 1471 1469 1472 1470 func (rp *Repo) SyncRepoFork(w http.ResponseWriter, r *http.Request) { 1473 1471 ref := chi.URLParam(r, "ref") 1472 + ref, _ = url.PathUnescape(ref) 1474 1473 1475 1474 user := rp.oauth.GetUser(r) 1476 1475 f, err := rp.repoResolver.Resolve(r) ··· 1759 1758 1760 1759 repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 1761 1760 branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo) 1762 - if err != nil { 1763 - if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1764 - log.Println("failed to call XRPC repo.branches", xrpcerr) 1765 - rp.pages.Error503(w) 1766 - return 1767 - } 1768 - rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.") 1761 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1762 + log.Println("failed to call XRPC repo.branches", xrpcerr) 1763 + rp.pages.Error503(w) 1769 1764 return 1770 1765 } 1771 1766 ··· 1800 1795 } 1801 1796 1802 1797 tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo) 1803 - if err != nil { 1804 - if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1805 - log.Println("failed to call XRPC repo.tags", xrpcerr) 1806 - rp.pages.Error503(w) 1807 - return 1808 - } 1809 - rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.") 1798 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1799 + log.Println("failed to call XRPC repo.tags", xrpcerr) 1800 + rp.pages.Error503(w) 1810 1801 return 1811 1802 } 1812 1803 ··· 1877 1868 repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 1878 1869 1879 1870 branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo) 1880 - if err != nil { 1881 - if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1882 - log.Println("failed to call XRPC repo.branches", xrpcerr) 1883 - rp.pages.Error503(w) 1884 - return 1885 - } 1886 - rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.") 1871 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1872 + log.Println("failed to call XRPC repo.branches", xrpcerr) 1873 + rp.pages.Error503(w) 1887 1874 return 1888 1875 } 1889 1876 ··· 1895 1882 } 1896 1883 1897 1884 tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo) 1898 - if err != nil { 1899 - if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1900 - log.Println("failed to call XRPC repo.tags", xrpcerr) 1901 - rp.pages.Error503(w) 1902 - return 1903 - } 1904 - rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.") 1885 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1886 + log.Println("failed to call XRPC repo.tags", xrpcerr) 1887 + rp.pages.Error503(w) 1905 1888 return 1906 1889 } 1907 1890 ··· 1913 1896 } 1914 1897 1915 1898 compareBytes, err := tangled.RepoCompare(r.Context(), xrpcc, repo, base, head) 1916 - if err != nil { 1917 - if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1918 - log.Println("failed to call XRPC repo.compare", xrpcerr) 1919 - rp.pages.Error503(w) 1920 - return 1921 - } 1922 - rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.") 1899 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1900 + log.Println("failed to call XRPC repo.compare", xrpcerr) 1901 + rp.pages.Error503(w) 1923 1902 return 1924 1903 } 1925 1904
+1 -10
knotserver/xrpc/list_keys.go
··· 1 1 package xrpc 2 2 3 3 import ( 4 - "encoding/json" 5 4 "net/http" 6 5 "strconv" 7 6 ··· 46 45 response.Cursor = &nextCursor 47 46 } 48 47 49 - w.Header().Set("Content-Type", "application/json") 50 - if err := json.NewEncoder(w).Encode(response); err != nil { 51 - x.Logger.Error("failed to encode response", "error", err) 52 - writeError(w, xrpcerr.NewXrpcError( 53 - xrpcerr.WithTag("InternalServerError"), 54 - xrpcerr.WithMessage("failed to encode response"), 55 - ), http.StatusInternalServerError) 56 - return 57 - } 48 + writeJson(w, response) 58 49 }
+1 -10
knotserver/xrpc/owner.go
··· 1 1 package xrpc 2 2 3 3 import ( 4 - "encoding/json" 5 4 "net/http" 6 5 7 6 "tangled.sh/tangled.sh/core/api/tangled" ··· 19 18 Owner: owner, 20 19 } 21 20 22 - w.Header().Set("Content-Type", "application/json") 23 - if err := json.NewEncoder(w).Encode(response); err != nil { 24 - x.Logger.Error("failed to encode response", "error", err) 25 - writeError(w, xrpcerr.NewXrpcError( 26 - xrpcerr.WithTag("InternalServerError"), 27 - xrpcerr.WithMessage("failed to encode response"), 28 - ), http.StatusInternalServerError) 29 - return 30 - } 21 + writeJson(w, response) 31 22 }
+8 -7
knotserver/xrpc/repo_archive.go
··· 13 13 ) 14 14 15 15 func (x *Xrpc) RepoArchive(w http.ResponseWriter, r *http.Request) { 16 - repo, repoPath, unescapedRef, err := x.parseStandardParams(r) 16 + repo := r.URL.Query().Get("repo") 17 + repoPath, err := x.parseRepoParam(repo) 17 18 if err != nil { 18 19 writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest) 19 20 return 20 21 } 22 + 23 + ref := r.URL.Query().Get("ref") 24 + // ref can be empty (git.Open handles this) 21 25 22 26 format := r.URL.Query().Get("format") 23 27 if format == "" { ··· 34 38 return 35 39 } 36 40 37 - gr, err := git.Open(repoPath, unescapedRef) 41 + gr, err := git.Open(repoPath, ref) 38 42 if err != nil { 39 - writeError(w, xrpcerr.NewXrpcError( 40 - xrpcerr.WithTag("RefNotFound"), 41 - xrpcerr.WithMessage("repository or ref not found"), 42 - ), http.StatusNotFound) 43 + writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound) 43 44 return 44 45 } 45 46 46 47 repoParts := strings.Split(repo, "/") 47 48 repoName := repoParts[len(repoParts)-1] 48 49 49 - safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(unescapedRef).Short(), "/", "-") 50 + safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-") 50 51 51 52 var archivePrefix string 52 53 if prefix != "" {
+7 -15
knotserver/xrpc/repo_blob.go
··· 3 3 import ( 4 4 "crypto/sha256" 5 5 "encoding/base64" 6 - "encoding/json" 7 6 "fmt" 8 7 "net/http" 9 8 "path/filepath" ··· 16 15 ) 17 16 18 17 func (x *Xrpc) RepoBlob(w http.ResponseWriter, r *http.Request) { 19 - _, repoPath, ref, err := x.parseStandardParams(r) 18 + repo := r.URL.Query().Get("repo") 19 + repoPath, err := x.parseRepoParam(repo) 20 20 if err != nil { 21 21 writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest) 22 22 return 23 23 } 24 24 25 + ref := r.URL.Query().Get("ref") 26 + // ref can be empty (git.Open handles this) 27 + 25 28 treePath := r.URL.Query().Get("path") 26 29 if treePath == "" { 27 30 writeError(w, xrpcerr.NewXrpcError( ··· 35 38 36 39 gr, err := git.Open(repoPath, ref) 37 40 if err != nil { 38 - writeError(w, xrpcerr.NewXrpcError( 39 - xrpcerr.WithTag("RefNotFound"), 40 - xrpcerr.WithMessage("repository or ref not found"), 41 - ), http.StatusNotFound) 41 + writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound) 42 42 return 43 43 } 44 44 ··· 123 123 response.MimeType = &mimeType 124 124 } 125 125 126 - w.Header().Set("Content-Type", "application/json") 127 - if err := json.NewEncoder(w).Encode(response); err != nil { 128 - x.Logger.Error("failed to encode response", "error", err) 129 - writeError(w, xrpcerr.NewXrpcError( 130 - xrpcerr.WithTag("InternalServerError"), 131 - xrpcerr.WithMessage("failed to encode response"), 132 - ), http.StatusInternalServerError) 133 - return 134 - } 126 + writeJson(w, response) 135 127 } 136 128 137 129 // isTextualMimeType returns true if the MIME type represents textual content
+5 -16
knotserver/xrpc/repo_branch.go
··· 1 1 package xrpc 2 2 3 3 import ( 4 - "encoding/json" 5 4 "net/http" 6 5 "net/url" 6 + "time" 7 7 8 8 "tangled.sh/tangled.sh/core/api/tangled" 9 9 "tangled.sh/tangled.sh/core/knotserver/git" ··· 31 31 32 32 gr, err := git.PlainOpen(repoPath) 33 33 if err != nil { 34 - writeError(w, xrpcerr.NewXrpcError( 35 - xrpcerr.WithTag("RepoNotFound"), 36 - xrpcerr.WithMessage("repository not found"), 37 - ), http.StatusNotFound) 34 + writeError(w, xrpcerr.RepoNotFoundError, http.StatusNoContent) 38 35 return 39 36 } 40 37 ··· 70 67 Name: ref.Name().Short(), 71 68 Hash: ref.Hash().String(), 72 69 ShortHash: &[]string{ref.Hash().String()[:7]}[0], 73 - When: commit.Author.When.Format("2006-01-02T15:04:05.000Z"), 70 + When: commit.Author.When.Format(time.RFC3339), 74 71 IsDefault: &isDefault, 75 72 } 76 73 ··· 81 78 response.Author = &tangled.RepoBranch_Signature{ 82 79 Name: commit.Author.Name, 83 80 Email: commit.Author.Email, 84 - When: commit.Author.When.Format("2006-01-02T15:04:05.000Z"), 81 + When: commit.Author.When.Format(time.RFC3339), 85 82 } 86 83 87 - w.Header().Set("Content-Type", "application/json") 88 - if err := json.NewEncoder(w).Encode(response); err != nil { 89 - x.Logger.Error("failed to encode response", "error", err) 90 - writeError(w, xrpcerr.NewXrpcError( 91 - xrpcerr.WithTag("InternalServerError"), 92 - xrpcerr.WithMessage("failed to encode response"), 93 - ), http.StatusInternalServerError) 94 - return 95 - } 84 + writeJson(w, response) 96 85 }
+3 -19
knotserver/xrpc/repo_branches.go
··· 1 1 package xrpc 2 2 3 3 import ( 4 - "encoding/json" 5 4 "net/http" 6 5 "strconv" 7 6 ··· 31 30 32 31 gr, err := git.PlainOpen(repoPath) 33 32 if err != nil { 34 - writeError(w, xrpcerr.NewXrpcError( 35 - xrpcerr.WithTag("RepoNotFound"), 36 - xrpcerr.WithMessage("repository not found"), 37 - ), http.StatusNotFound) 33 + writeError(w, xrpcerr.RepoNotFoundError, http.StatusNoContent) 38 34 return 39 35 } 40 36 ··· 47 43 } 48 44 } 49 45 50 - end := offset + limit 51 - if end > len(branches) { 52 - end = len(branches) 53 - } 46 + end := min(offset+limit, len(branches)) 54 47 55 48 paginatedBranches := branches[offset:end] 56 49 ··· 59 52 Branches: paginatedBranches, 60 53 } 61 54 62 - // Write JSON response directly 63 - w.Header().Set("Content-Type", "application/json") 64 - if err := json.NewEncoder(w).Encode(response); err != nil { 65 - x.Logger.Error("failed to encode response", "error", err) 66 - writeError(w, xrpcerr.NewXrpcError( 67 - xrpcerr.WithTag("InternalServerError"), 68 - xrpcerr.WithMessage("failed to encode response"), 69 - ), http.StatusInternalServerError) 70 - return 71 - } 55 + writeJson(w, response) 72 56 }
+7 -23
knotserver/xrpc/repo_compare.go
··· 1 1 package xrpc 2 2 3 3 import ( 4 - "encoding/json" 5 4 "fmt" 6 5 "net/http" 7 - "net/url" 8 6 9 7 "tangled.sh/tangled.sh/core/knotserver/git" 10 8 "tangled.sh/tangled.sh/core/types" ··· 19 17 return 20 18 } 21 19 22 - rev1Param := r.URL.Query().Get("rev1") 23 - if rev1Param == "" { 20 + rev1 := r.URL.Query().Get("rev1") 21 + if rev1 == "" { 24 22 writeError(w, xrpcerr.NewXrpcError( 25 23 xrpcerr.WithTag("InvalidRequest"), 26 24 xrpcerr.WithMessage("missing rev1 parameter"), ··· 28 26 return 29 27 } 30 28 31 - rev2Param := r.URL.Query().Get("rev2") 32 - if rev2Param == "" { 29 + rev2 := r.URL.Query().Get("rev2") 30 + if rev2 == "" { 33 31 writeError(w, xrpcerr.NewXrpcError( 34 32 xrpcerr.WithTag("InvalidRequest"), 35 33 xrpcerr.WithMessage("missing rev2 parameter"), ··· 37 35 return 38 36 } 39 37 40 - rev1, _ := url.PathUnescape(rev1Param) 41 - rev2, _ := url.PathUnescape(rev2Param) 42 - 43 38 gr, err := git.PlainOpen(repoPath) 44 39 if err != nil { 45 - writeError(w, xrpcerr.NewXrpcError( 46 - xrpcerr.WithTag("RepoNotFound"), 47 - xrpcerr.WithMessage("repository not found"), 48 - ), http.StatusNotFound) 40 + writeError(w, xrpcerr.RepoNotFoundError, http.StatusNoContent) 49 41 return 50 42 } 51 43 ··· 79 71 return 80 72 } 81 73 82 - resp := types.RepoFormatPatchResponse{ 74 + response := types.RepoFormatPatchResponse{ 83 75 Rev1: commit1.Hash.String(), 84 76 Rev2: commit2.Hash.String(), 85 77 FormatPatch: formatPatch, 86 78 Patch: rawPatch, 87 79 } 88 80 89 - w.Header().Set("Content-Type", "application/json") 90 - if err := json.NewEncoder(w).Encode(resp); err != nil { 91 - x.Logger.Error("failed to encode response", "error", err) 92 - writeError(w, xrpcerr.NewXrpcError( 93 - xrpcerr.WithTag("InternalServerError"), 94 - xrpcerr.WithMessage("failed to encode response"), 95 - ), http.StatusInternalServerError) 96 - return 97 - } 81 + writeJson(w, response) 98 82 }
+6 -30
knotserver/xrpc/repo_diff.go
··· 1 1 package xrpc 2 2 3 3 import ( 4 - "encoding/json" 5 4 "net/http" 6 - "net/url" 7 5 8 6 "tangled.sh/tangled.sh/core/knotserver/git" 9 7 "tangled.sh/tangled.sh/core/types" ··· 18 16 return 19 17 } 20 18 21 - refParam := r.URL.Query().Get("ref") 22 - if refParam == "" { 23 - writeError(w, xrpcerr.NewXrpcError( 24 - xrpcerr.WithTag("InvalidRequest"), 25 - xrpcerr.WithMessage("missing ref parameter"), 26 - ), http.StatusBadRequest) 27 - return 28 - } 29 - 30 - ref, _ := url.QueryUnescape(refParam) 19 + ref := r.URL.Query().Get("ref") 20 + // ref can be empty (git.Open handles this) 31 21 32 22 gr, err := git.Open(repoPath, ref) 33 23 if err != nil { 34 - writeError(w, xrpcerr.NewXrpcError( 35 - xrpcerr.WithTag("RefNotFound"), 36 - xrpcerr.WithMessage("repository or ref not found"), 37 - ), http.StatusNotFound) 24 + writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound) 38 25 return 39 26 } 40 27 41 28 diff, err := gr.Diff() 42 29 if err != nil { 43 30 x.Logger.Error("getting diff", "error", err.Error()) 44 - writeError(w, xrpcerr.NewXrpcError( 45 - xrpcerr.WithTag("RefNotFound"), 46 - xrpcerr.WithMessage("failed to generate diff"), 47 - ), http.StatusInternalServerError) 31 + writeError(w, xrpcerr.RefNotFoundError, http.StatusInternalServerError) 48 32 return 49 33 } 50 34 51 - resp := types.RepoCommitResponse{ 35 + response := types.RepoCommitResponse{ 52 36 Ref: ref, 53 37 Diff: diff, 54 38 } 55 39 56 - w.Header().Set("Content-Type", "application/json") 57 - if err := json.NewEncoder(w).Encode(resp); err != nil { 58 - x.Logger.Error("failed to encode response", "error", err) 59 - writeError(w, xrpcerr.NewXrpcError( 60 - xrpcerr.WithTag("InternalServerError"), 61 - xrpcerr.WithMessage("failed to encode response"), 62 - ), http.StatusInternalServerError) 63 - return 64 - } 40 + writeJson(w, response) 65 41 }
+4 -19
knotserver/xrpc/repo_get_default_branch.go
··· 1 1 package xrpc 2 2 3 3 import ( 4 - "encoding/json" 5 4 "net/http" 5 + "time" 6 6 7 7 "tangled.sh/tangled.sh/core/api/tangled" 8 8 "tangled.sh/tangled.sh/core/knotserver/git" ··· 17 17 return 18 18 } 19 19 20 - gr, err := git.Open(repoPath, "") 21 - if err != nil { 22 - writeError(w, xrpcerr.NewXrpcError( 23 - xrpcerr.WithTag("RepoNotFound"), 24 - xrpcerr.WithMessage("repository not found"), 25 - ), http.StatusNotFound) 26 - return 27 - } 20 + gr, err := git.PlainOpen(repoPath) 28 21 29 22 branch, err := gr.FindMainBranch() 30 23 if err != nil { ··· 39 32 response := tangled.RepoGetDefaultBranch_Output{ 40 33 Name: branch, 41 34 Hash: "", 42 - When: "1970-01-01T00:00:00.000Z", 35 + When: time.UnixMicro(0).Format(time.RFC3339), 43 36 } 44 37 45 - w.Header().Set("Content-Type", "application/json") 46 - if err := json.NewEncoder(w).Encode(response); err != nil { 47 - x.Logger.Error("failed to encode response", "error", err) 48 - writeError(w, xrpcerr.NewXrpcError( 49 - xrpcerr.WithTag("InternalServerError"), 50 - xrpcerr.WithMessage("failed to encode response"), 51 - ), http.StatusInternalServerError) 52 - return 53 - } 38 + writeJson(w, response) 54 39 }
+4 -21
knotserver/xrpc/repo_languages.go
··· 2 2 3 3 import ( 4 4 "context" 5 - "encoding/json" 6 5 "math" 7 6 "net/http" 8 - "net/url" 9 7 "time" 10 8 11 9 "tangled.sh/tangled.sh/core/api/tangled" ··· 14 12 ) 15 13 16 14 func (x *Xrpc) RepoLanguages(w http.ResponseWriter, r *http.Request) { 17 - refParam := r.URL.Query().Get("ref") 18 - if refParam == "" { 19 - refParam = "HEAD" // default 20 - } 21 - ref, _ := url.PathUnescape(refParam) 22 - 23 15 repo := r.URL.Query().Get("repo") 24 16 repoPath, err := x.parseRepoParam(repo) 25 17 if err != nil { ··· 27 19 return 28 20 } 29 21 22 + ref := r.URL.Query().Get("ref") 23 + 30 24 gr, err := git.Open(repoPath, ref) 31 25 if err != nil { 32 26 x.Logger.Error("opening repo", "error", err.Error()) 33 - writeError(w, xrpcerr.NewXrpcError( 34 - xrpcerr.WithTag("RefNotFound"), 35 - xrpcerr.WithMessage("repository or ref not found"), 36 - ), http.StatusNotFound) 27 + writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound) 37 28 return 38 29 } 39 30 ··· 81 72 response.TotalFiles = &totalFiles 82 73 } 83 74 84 - w.Header().Set("Content-Type", "application/json") 85 - if err := json.NewEncoder(w).Encode(response); err != nil { 86 - x.Logger.Error("failed to encode response", "error", err) 87 - writeError(w, xrpcerr.NewXrpcError( 88 - xrpcerr.WithTag("InternalServerError"), 89 - xrpcerr.WithMessage("failed to encode response"), 90 - ), http.StatusInternalServerError) 91 - return 92 - } 75 + writeJson(w, response) 93 76 }
+3 -33
knotserver/xrpc/repo_log.go
··· 1 1 package xrpc 2 2 3 3 import ( 4 - "encoding/json" 5 4 "net/http" 6 - "net/url" 7 5 "strconv" 8 6 9 7 "tangled.sh/tangled.sh/core/knotserver/git" ··· 19 17 return 20 18 } 21 19 22 - refParam := r.URL.Query().Get("ref") 23 - if refParam == "" { 24 - writeError(w, xrpcerr.NewXrpcError( 25 - xrpcerr.WithTag("InvalidRequest"), 26 - xrpcerr.WithMessage("missing ref parameter"), 27 - ), http.StatusBadRequest) 28 - return 29 - } 20 + ref := r.URL.Query().Get("ref") 30 21 31 22 path := r.URL.Query().Get("path") 32 23 cursor := r.URL.Query().Get("cursor") ··· 38 29 } 39 30 } 40 31 41 - ref, err := url.QueryUnescape(refParam) 42 - if err != nil { 43 - writeError(w, xrpcerr.NewXrpcError( 44 - xrpcerr.WithTag("InvalidRequest"), 45 - xrpcerr.WithMessage("invalid ref parameter"), 46 - ), http.StatusBadRequest) 47 - return 48 - } 49 - 50 32 gr, err := git.Open(repoPath, ref) 51 33 if err != nil { 52 - writeError(w, xrpcerr.NewXrpcError( 53 - xrpcerr.WithTag("RefNotFound"), 54 - xrpcerr.WithMessage("repository or ref not found"), 55 - ), http.StatusNotFound) 34 + writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound) 56 35 return 57 36 } 58 37 ··· 98 77 99 78 response.Log = true 100 79 101 - // Write JSON response directly 102 - w.Header().Set("Content-Type", "application/json") 103 - if err := json.NewEncoder(w).Encode(response); err != nil { 104 - x.Logger.Error("failed to encode response", "error", err) 105 - writeError(w, xrpcerr.NewXrpcError( 106 - xrpcerr.WithTag("InternalServerError"), 107 - xrpcerr.WithMessage("failed to encode response"), 108 - ), http.StatusInternalServerError) 109 - return 110 - } 80 + writeJson(w, response) 111 81 }
+3 -16
knotserver/xrpc/repo_tags.go
··· 1 1 package xrpc 2 2 3 3 import ( 4 - "encoding/json" 5 4 "net/http" 6 5 "strconv" 7 6 ··· 30 29 } 31 30 } 32 31 33 - gr, err := git.Open(repoPath, "") 32 + gr, err := git.PlainOpen(repoPath) 34 33 if err != nil { 35 34 x.Logger.Error("failed to open", "error", err) 36 - writeError(w, xrpcerr.NewXrpcError( 37 - xrpcerr.WithTag("RepoNotFound"), 38 - xrpcerr.WithMessage("repository not found"), 39 - ), http.StatusNotFound) 35 + writeError(w, xrpcerr.RepoNotFoundError, http.StatusNoContent) 40 36 return 41 37 } 42 38 ··· 86 82 Tags: paginatedTags, 87 83 } 88 84 89 - // Write JSON response directly 90 - w.Header().Set("Content-Type", "application/json") 91 - if err := json.NewEncoder(w).Encode(response); err != nil { 92 - x.Logger.Error("failed to encode response", "error", err) 93 - writeError(w, xrpcerr.NewXrpcError( 94 - xrpcerr.WithTag("InternalServerError"), 95 - xrpcerr.WithMessage("failed to encode response"), 96 - ), http.StatusInternalServerError) 97 - return 98 - } 85 + writeJson(w, response) 99 86 }
+6 -33
knotserver/xrpc/repo_tree.go
··· 1 1 package xrpc 2 2 3 3 import ( 4 - "encoding/json" 5 4 "net/http" 6 - "net/url" 7 5 "path/filepath" 6 + "time" 8 7 9 8 "tangled.sh/tangled.sh/core/api/tangled" 10 9 "tangled.sh/tangled.sh/core/knotserver/git" ··· 21 20 return 22 21 } 23 22 24 - refParam := r.URL.Query().Get("ref") 25 - if refParam == "" { 26 - writeError(w, xrpcerr.NewXrpcError( 27 - xrpcerr.WithTag("InvalidRequest"), 28 - xrpcerr.WithMessage("missing ref parameter"), 29 - ), http.StatusBadRequest) 30 - return 31 - } 23 + ref := r.URL.Query().Get("ref") 24 + // ref can be empty (git.Open handles this) 32 25 33 26 path := r.URL.Query().Get("path") 34 27 // path can be empty (defaults to root) 35 28 36 - ref, err := url.QueryUnescape(refParam) 37 - if err != nil { 38 - writeError(w, xrpcerr.NewXrpcError( 39 - xrpcerr.WithTag("InvalidRequest"), 40 - xrpcerr.WithMessage("invalid ref parameter"), 41 - ), http.StatusBadRequest) 42 - return 43 - } 44 - 45 29 gr, err := git.Open(repoPath, ref) 46 30 if err != nil { 47 31 x.Logger.Error("failed to open git repository", "error", err, "path", repoPath, "ref", ref) 48 - writeError(w, xrpcerr.NewXrpcError( 49 - xrpcerr.WithTag("RefNotFound"), 50 - xrpcerr.WithMessage("repository or ref not found"), 51 - ), http.StatusNotFound) 32 + writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound) 52 33 return 53 34 } 54 35 ··· 77 58 entry.Last_commit = &tangled.RepoTree_LastCommit{ 78 59 Hash: file.LastCommit.Hash.String(), 79 60 Message: file.LastCommit.Message, 80 - When: file.LastCommit.When.Format("2006-01-02T15:04:05.000Z"), 61 + When: file.LastCommit.When.Format(time.RFC3339), 81 62 } 82 63 } 83 64 ··· 104 85 Files: treeEntries, 105 86 } 106 87 107 - w.Header().Set("Content-Type", "application/json") 108 - if err := json.NewEncoder(w).Encode(response); err != nil { 109 - x.Logger.Error("failed to encode response", "error", err) 110 - writeError(w, xrpcerr.NewXrpcError( 111 - xrpcerr.WithTag("InternalServerError"), 112 - xrpcerr.WithMessage("failed to encode response"), 113 - ), http.StatusInternalServerError) 114 - return 115 - } 88 + writeJson(w, response) 116 89 }
+1 -11
knotserver/xrpc/version.go
··· 1 1 package xrpc 2 2 3 3 import ( 4 - "encoding/json" 5 4 "fmt" 6 5 "net/http" 7 6 "runtime/debug" 8 7 9 8 "tangled.sh/tangled.sh/core/api/tangled" 10 - xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors" 11 9 ) 12 10 13 11 // version is set during build time. ··· 58 56 Version: version, 59 57 } 60 58 61 - w.Header().Set("Content-Type", "application/json") 62 - if err := json.NewEncoder(w).Encode(response); err != nil { 63 - x.Logger.Error("failed to encode response", "error", err) 64 - writeError(w, xrpcerr.NewXrpcError( 65 - xrpcerr.WithTag("InternalServerError"), 66 - xrpcerr.WithMessage("failed to encode response"), 67 - ), http.StatusInternalServerError) 68 - return 69 - } 59 + writeJson(w, response) 70 60 }
+14 -35
knotserver/xrpc/xrpc.go
··· 4 4 "encoding/json" 5 5 "log/slog" 6 6 "net/http" 7 - "net/url" 8 7 "strings" 9 8 10 9 securejoin "github.com/cyphar/filepath-securejoin" ··· 88 87 } 89 88 90 89 // Parse repo string (did/repoName format) 91 - parts := strings.Split(repo, "/") 92 - if len(parts) < 2 { 90 + parts := strings.SplitN(repo, "/", 2) 91 + if len(parts) != 2 { 93 92 return "", xrpcerr.NewXrpcError( 94 93 xrpcerr.WithTag("InvalidRequest"), 95 94 xrpcerr.WithMessage("invalid repo format, expected 'did/repoName'"), 96 95 ) 97 96 } 98 97 99 - did := strings.Join(parts[:len(parts)-1], "/") 100 - repoName := parts[len(parts)-1] 98 + did := parts[0] 99 + repoName := parts[1] 101 100 102 101 // Construct repository path using the same logic as didPath 103 102 didRepoPath, err := securejoin.SecureJoin(did, repoName) 104 103 if err != nil { 105 - return "", xrpcerr.NewXrpcError( 106 - xrpcerr.WithTag("RepoNotFound"), 107 - xrpcerr.WithMessage("failed to access repository"), 108 - ) 104 + return "", xrpcerr.RepoNotFoundError 109 105 } 110 106 111 107 repoPath, err := securejoin.SecureJoin(x.Config.Repo.ScanPath, didRepoPath) 112 108 if err != nil { 113 - return "", xrpcerr.NewXrpcError( 114 - xrpcerr.WithTag("RepoNotFound"), 115 - xrpcerr.WithMessage("failed to access repository"), 116 - ) 109 + return "", xrpcerr.RepoNotFoundError 117 110 } 118 111 119 112 return repoPath, nil 120 113 } 121 114 122 - // parseStandardParams parses common query parameters used by most handlers 123 - func (x *Xrpc) parseStandardParams(r *http.Request) (repo, repoPath, ref string, err error) { 124 - // Parse repo parameter 125 - repo = r.URL.Query().Get("repo") 126 - repoPath, err = x.parseRepoParam(repo) 127 - if err != nil { 128 - return "", "", "", err 129 - } 130 - 131 - // Parse and unescape ref parameter 132 - refParam := r.URL.Query().Get("ref") 133 - if refParam == "" { 134 - return "", "", "", xrpcerr.NewXrpcError( 135 - xrpcerr.WithTag("InvalidRequest"), 136 - xrpcerr.WithMessage("missing ref parameter"), 137 - ) 138 - } 139 - 140 - ref, _ = url.QueryUnescape(refParam) 141 - return repo, repoPath, ref, nil 142 - } 143 - 144 115 func writeError(w http.ResponseWriter, e xrpcerr.XrpcError, status int) { 145 116 w.Header().Set("Content-Type", "application/json") 146 117 w.WriteHeader(status) 147 118 json.NewEncoder(w).Encode(e) 148 119 } 120 + 121 + func writeJson(w http.ResponseWriter, response any) { 122 + w.Header().Set("Content-Type", "application/json") 123 + if err := json.NewEncoder(w).Encode(response); err != nil { 124 + writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError) 125 + return 126 + } 127 + }
+10
xrpc/errors/errors.go
··· 56 56 WithMessage("owner not set for this service"), 57 57 ) 58 58 59 + var RepoNotFoundError = NewXrpcError( 60 + WithTag("RepoNotFound"), 61 + WithMessage("failed to access repository"), 62 + ) 63 + 64 + var RefNotFoundError = NewXrpcError( 65 + WithTag("RefNotFound"), 66 + WithMessage("failed to access ref"), 67 + ) 68 + 59 69 var AuthError = func(err error) XrpcError { 60 70 return NewXrpcError( 61 71 WithTag("Auth"),