Monorepo for Tangled

[WIP] appview: repo, pulls, issues, and pages handlers for repo DID

+290 -157
+3 -2
appview/issues/issues.go
··· 306 306 return 307 307 } 308 308 309 - roles := repoinfo.RolesInRepo{Roles: rp.enforcer.GetPermissionsInRepo(user.Active.Did, f.Knot, f.DidSlashRepo())} 309 + roles := repoinfo.RolesInRepo{Roles: rp.enforcer.GetPermissionsInRepo(user.Active.Did, f.Knot, f.RepoIdentifier())} 310 310 isRepoOwner := roles.IsOwner() 311 311 isCollaborator := roles.IsCollaborator() 312 312 isIssueOwner := user.Active.Did == issue.Did ··· 354 354 return 355 355 } 356 356 357 - roles := repoinfo.RolesInRepo{Roles: rp.enforcer.GetPermissionsInRepo(user.Active.Did, f.Knot, f.DidSlashRepo())} 357 + roles := repoinfo.RolesInRepo{Roles: rp.enforcer.GetPermissionsInRepo(user.Active.Did, f.Knot, f.RepoIdentifier())} 358 358 isRepoOwner := roles.IsOwner() 359 359 isCollaborator := roles.IsCollaborator() 360 360 isIssueOwner := user.Active.Did == issue.Did ··· 1011 1011 1012 1012 issue := &models.Issue{ 1013 1013 RepoAt: f.RepoAt(), 1014 + RepoDid: f.RepoDid, 1014 1015 Rkey: tid.TID(), 1015 1016 Title: r.FormValue("title"), 1016 1017 Body: body,
+5 -2
appview/pages/funcmap.go
··· 78 78 return identity.Handle.String() 79 79 }, 80 80 "ownerSlashRepo": func(repo *models.Repo) string { 81 + if repo.RepoDid != "" { 82 + return repo.RepoDid 83 + } 81 84 ownerId, err := p.resolver.ResolveIdent(context.Background(), repo.Did) 82 85 if err != nil { 83 - return repo.DidSlashRepo() 86 + return repo.RepoIdentifier() 84 87 } 85 88 handle := ownerId.Handle 86 89 if handle != "" && !handle.IsInvalidHandle() { 87 90 return string(handle) + "/" + repo.Name 88 91 } 89 - return repo.DidSlashRepo() 92 + return repo.RepoIdentifier() 90 93 }, 91 94 "truncateAt30": func(s string) string { 92 95 if len(s) <= 30 {
+7
appview/pages/repoinfo/repoinfo.go
··· 20 20 } 21 21 22 22 func (r RepoInfo) FullName() string { 23 + if r.RepoDid != "" { 24 + return r.RepoDid 25 + } 23 26 return path.Join(r.owner(), r.Name) 24 27 } 25 28 ··· 32 35 } 33 36 34 37 func (r RepoInfo) FullNameWithoutAt() string { 38 + if r.RepoDid != "" { 39 + return userutil.FlattenDid(r.RepoDid) 40 + } 35 41 return path.Join(r.ownerWithoutAt(), r.Name) 36 42 } 37 43 ··· 59 65 Rkey string 60 66 OwnerDid string 61 67 OwnerHandle string 68 + RepoDid string 62 69 Description string 63 70 Website string 64 71 Topics []string
+21
appview/pages/templates/repo/fork.html
··· 37 37 <p class="text-sm text-gray-500 dark:text-gray-400">A knot hosts repository data. <a href="/settings/knots" class="underline">Learn how to register your own knot.</a></p> 38 38 </fieldset> 39 39 40 + <fieldset class="space-y-3"> 41 + <details> 42 + <summary class="dark:text-white cursor-pointer select-none">Bring your own DID</summary> 43 + <div class="mt-2"> 44 + <input 45 + type="text" 46 + id="repo_did" 47 + name="repo_did" 48 + class="w-full p-2 border rounded bg-gray-100 dark:bg-gray-700 dark:text-white dark:border-gray-600" 49 + placeholder="did:web:example.com" 50 + /> 51 + <p class="text-sm text-gray-500 dark:text-gray-400 mt-1"> 52 + Provide a <code>did:web</code> you control to use as this fork's identity. 53 + You must serve a DID doc on your domain with an <code>atproto_pds</code> service 54 + endpoint pointing to the selected knot. If left empty, a <code>did:plc</code> will be 55 + automatically created for you! 56 + </p> 57 + </div> 58 + </details> 59 + </fieldset> 60 + 40 61 <div class="space-y-2"> 41 62 <button type="submit" class="btn-create flex items-center gap-2"> 42 63 {{ i "git-fork" "w-4 h-4" }}
+26
appview/pages/templates/repo/new.html
··· 70 70 <div class="space-y-2"> 71 71 {{ template "defaultBranch" . }} 72 72 {{ template "knot" . }} 73 + {{ template "repoDid" . }} 73 74 </div> 74 75 </div> 75 76 </div> ··· 168 169 A knot hosts repository data and handles Git operations. 169 170 You can also <a href="/settings/knots" class="underline">register your own knot</a>. 170 171 </p> 172 + </div> 173 + {{ end }} 174 + 175 + {{ define "repoDid" }} 176 + <div> 177 + <details> 178 + <summary class="text-sm font-bold uppercase dark:text-white mb-1 cursor-pointer select-none"> 179 + Bring your own DID 180 + </summary> 181 + <div class="mt-2"> 182 + <input 183 + type="text" 184 + id="repo_did" 185 + name="repo_did" 186 + class="w-full dark:bg-gray-700 dark:text-white dark:border-gray-600 border border-gray-300 rounded px-3 py-2" 187 + placeholder="did:web:example.com" 188 + /> 189 + <p class="text-sm text-gray-500 dark:text-gray-400 mt-1"> 190 + Provide a <code>did:web</code> you control to use as this repo's identity. 191 + You must serve a DID doc on your domain with an <code>atproto_pds</code> service 192 + endpoint pointing to the selected knot. If left empty, a <code>did:plc</code> will be 193 + automatically created for you! 194 + </p> 195 + </div> 196 + </details> 171 197 </div> 172 198 {{ end }} 173 199
+44 -45
appview/pulls/pulls.go
··· 406 406 } 407 407 408 408 // user can only delete branch if they are a collaborator in the repo that the branch belongs to 409 - perms := s.enforcer.GetPermissionsInRepo(user.Active.Did, repo.Knot, repo.DidSlashRepo()) 409 + perms := s.enforcer.GetPermissionsInRepo(user.Active.Did, repo.Knot, repo.RepoIdentifier()) 410 410 if !slices.Contains(perms, "repo:push") { 411 411 return nil 412 412 } ··· 420 420 Host: host, 421 421 } 422 422 423 - resp, err := tangled.RepoBranch(r.Context(), xrpcc, branch, fmt.Sprintf("%s/%s", repo.Did, repo.Name)) 423 + resp, err := tangled.RepoBranch(r.Context(), xrpcc, branch, repo.RepoIdentifier()) 424 424 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 425 425 return nil 426 426 } ··· 436 436 return pages.Unknown 437 437 } 438 438 439 - var knot, ownerDid, repoName string 440 - 439 + var sourceRepo *models.Repo 441 440 if pull.PullSource.RepoAt != nil { 442 - // fork-based pulls 443 - sourceRepo, err := db.GetRepoByAtUri(s.db, pull.PullSource.RepoAt.String()) 441 + var err error 442 + sourceRepo, err = db.GetRepoByAtUri(s.db, pull.PullSource.RepoAt.String()) 444 443 if err != nil { 445 444 log.Println("failed to get source repo", err) 446 445 return pages.Unknown 447 446 } 448 - 449 - knot = sourceRepo.Knot 450 - ownerDid = sourceRepo.Did 451 - repoName = sourceRepo.Name 452 447 } else { 453 - // pulls within the same repo 454 - knot = repo.Knot 455 - ownerDid = repo.Did 456 - repoName = repo.Name 448 + sourceRepo = repo 457 449 } 458 450 459 451 scheme := "http" 460 452 if !s.config.Core.Dev { 461 453 scheme = "https" 462 454 } 463 - host := fmt.Sprintf("%s://%s", scheme, knot) 455 + host := fmt.Sprintf("%s://%s", scheme, sourceRepo.Knot) 464 456 xrpcc := &indigoxrpc.Client{ 465 457 Host: host, 466 458 } 467 459 468 - didSlashName := fmt.Sprintf("%s/%s", ownerDid, repoName) 469 - branchResp, err := tangled.RepoBranch(r.Context(), xrpcc, pull.PullSource.Branch, didSlashName) 460 + branchResp, err := tangled.RepoBranch(r.Context(), xrpcc, pull.PullSource.Branch, sourceRepo.RepoIdentifier()) 470 461 if err != nil { 471 462 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 472 463 log.Println("failed to call XRPC repo.branches", xrpcerr) ··· 863 854 comment := &models.PullComment{ 864 855 OwnerDid: user.Active.Did, 865 856 RepoAt: f.RepoAt().String(), 857 + RepoDid: f.RepoDid, 866 858 PullId: pull.PullId, 867 859 Body: body, 868 860 CommentAt: atResp.Uri, ··· 913 905 Host: host, 914 906 } 915 907 916 - repo := fmt.Sprintf("%s/%s", f.Did, f.Name) 917 - xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo) 908 + xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, f.RepoIdentifier()) 918 909 if err != nil { 919 910 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 920 911 log.Println("failed to call XRPC repo.branches", xrpcerr) ··· 963 954 } 964 955 965 956 // Determine PR type based on input parameters 966 - roles := repoinfo.RolesInRepo{Roles: s.enforcer.GetPermissionsInRepo(user.Active.Did, f.Knot, f.DidSlashRepo())} 957 + roles := repoinfo.RolesInRepo{Roles: s.enforcer.GetPermissionsInRepo(user.Active.Did, f.Knot, f.RepoIdentifier())} 967 958 isPushAllowed := roles.IsPushAllowed() 968 959 isBranchBased := isPushAllowed && sourceBranch != "" && fromFork == "" 969 960 isForkBased := fromFork != "" && sourceBranch != "" ··· 1079 1070 Host: host, 1080 1071 } 1081 1072 1082 - didSlashRepo := fmt.Sprintf("%s/%s", repo.Did, repo.Name) 1083 - xrpcBytes, err := tangled.RepoCompare(r.Context(), xrpcc, didSlashRepo, targetBranch, sourceBranch) 1073 + xrpcBytes, err := tangled.RepoCompare(r.Context(), xrpcc, repo.RepoIdentifier(), targetBranch, sourceBranch) 1084 1074 if err != nil { 1085 1075 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1086 1076 log.Println("failed to call XRPC repo.compare", xrpcerr) ··· 1189 1179 Host: forkHost, 1190 1180 } 1191 1181 1192 - forkRepoId := fmt.Sprintf("%s/%s", fork.Did, fork.Name) 1193 - forkXrpcBytes, err := tangled.RepoCompare(r.Context(), forkXrpcc, forkRepoId, hiddenRef, sourceBranch) 1182 + forkXrpcBytes, err := tangled.RepoCompare(r.Context(), forkXrpcc, fork.RepoIdentifier(), hiddenRef, sourceBranch) 1194 1183 if err != nil { 1195 1184 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1196 1185 log.Println("failed to call XRPC repo.compare for fork", xrpcerr) ··· 1223 1212 forkAtUriStr := forkAtUri.String() 1224 1213 1225 1214 pullSource := &models.PullSource{ 1226 - Branch: sourceBranch, 1227 - RepoAt: &forkAtUri, 1215 + Branch: sourceBranch, 1216 + RepoAt: &forkAtUri, 1217 + RepoDid: fork.RepoDid, 1228 1218 } 1229 1219 recordPullSource := &tangled.RepoPull_Source{ 1230 1220 Branch: sourceBranch, 1231 - Repo: &forkAtUriStr, 1232 1221 Sha: sourceRev, 1233 1222 } 1223 + if fork.RepoDid != "" { 1224 + recordPullSource.RepoDid = &fork.RepoDid 1225 + } else { 1226 + recordPullSource.Repo = &forkAtUriStr 1227 + } 1234 1228 1235 1229 s.createPullRequest(w, r, repo, user, title, body, targetBranch, patch, combined, sourceRev, pullSource, recordPullSource, isStacked) 1236 1230 } ··· 1313 1307 TargetBranch: targetBranch, 1314 1308 OwnerDid: user.Active.Did, 1315 1309 RepoAt: repo.RepoAt(), 1310 + RepoDid: repo.RepoDid, 1316 1311 Rkey: rkey, 1317 1312 Mentions: mentions, 1318 1313 References: references, ··· 1347 1342 Rkey: rkey, 1348 1343 Record: &lexutil.LexiconTypeDecoder{ 1349 1344 Val: &tangled.RepoPull{ 1350 - Title: title, 1351 - Target: &tangled.RepoPull_Target{ 1352 - Repo: string(repo.RepoAt()), 1353 - Branch: targetBranch, 1354 - }, 1345 + Title: title, 1346 + Target: repoPullTarget(repo, targetBranch), 1355 1347 PatchBlob: blob.Blob, 1356 1348 Source: recordPullSource, 1357 1349 CreatedAt: time.Now().Format(time.RFC3339), ··· 1544 1536 Host: host, 1545 1537 } 1546 1538 1547 - repo := fmt.Sprintf("%s/%s", f.Did, f.Name) 1548 - xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo) 1539 + xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, f.RepoIdentifier()) 1549 1540 if err != nil { 1550 1541 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1551 1542 log.Println("failed to call XRPC repo.branches", xrpcerr) ··· 1631 1622 Host: sourceHost, 1632 1623 } 1633 1624 1634 - sourceRepo := fmt.Sprintf("%s/%s", forkOwnerDid, repo.Name) 1635 - sourceXrpcBytes, err := tangled.RepoBranches(r.Context(), sourceXrpcc, "", 0, sourceRepo) 1625 + sourceXrpcBytes, err := tangled.RepoBranches(r.Context(), sourceXrpcc, "", 0, repo.RepoIdentifier()) 1636 1626 if err != nil { 1637 1627 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1638 1628 log.Println("failed to call XRPC repo.branches for source", xrpcerr) ··· 1660 1650 Host: targetHost, 1661 1651 } 1662 1652 1663 - targetRepo := fmt.Sprintf("%s/%s", f.Did, f.Name) 1664 - targetXrpcBytes, err := tangled.RepoBranches(r.Context(), targetXrpcc, "", 0, targetRepo) 1653 + targetXrpcBytes, err := tangled.RepoBranches(r.Context(), targetXrpcc, "", 0, f.RepoIdentifier()) 1665 1654 if err != nil { 1666 1655 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1667 1656 log.Println("failed to call XRPC repo.branches for target", xrpcerr) ··· 1771 1760 return 1772 1761 } 1773 1762 1774 - roles := repoinfo.RolesInRepo{Roles: s.enforcer.GetPermissionsInRepo(user.Active.Did, f.Knot, f.DidSlashRepo())} 1763 + roles := repoinfo.RolesInRepo{Roles: s.enforcer.GetPermissionsInRepo(user.Active.Did, f.Knot, f.RepoIdentifier())} 1775 1764 if !roles.IsPushAllowed() { 1776 1765 log.Println("unauthorized user") 1777 1766 w.WriteHeader(http.StatusUnauthorized) ··· 1787 1776 Host: host, 1788 1777 } 1789 1778 1790 - repo := fmt.Sprintf("%s/%s", f.Did, f.Name) 1791 - xrpcBytes, err := tangled.RepoCompare(r.Context(), xrpcc, repo, pull.TargetBranch, pull.PullSource.Branch) 1779 + xrpcBytes, err := tangled.RepoCompare(r.Context(), xrpcc, f.RepoIdentifier(), pull.TargetBranch, pull.PullSource.Branch) 1792 1780 if err != nil { 1793 1781 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1794 1782 log.Println("failed to call XRPC repo.compare", xrpcerr) ··· 1881 1869 forkScheme = "https" 1882 1870 } 1883 1871 forkHost := fmt.Sprintf("%s://%s", forkScheme, forkRepo.Knot) 1884 - forkRepoId := fmt.Sprintf("%s/%s", forkRepo.Did, forkRepo.Name) 1885 - forkXrpcBytes, err := tangled.RepoCompare(r.Context(), &indigoxrpc.Client{Host: forkHost}, forkRepoId, hiddenRef, pull.PullSource.Branch) 1872 + forkXrpcBytes, err := tangled.RepoCompare(r.Context(), &indigoxrpc.Client{Host: forkHost}, forkRepo.RepoIdentifier(), hiddenRef, pull.PullSource.Branch) 1886 1873 if err != nil { 1887 1874 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1888 1875 log.Println("failed to call XRPC repo.compare for fork", xrpcerr) ··· 2360 2347 } 2361 2348 2362 2349 // auth filter: only owner or collaborators can close 2363 - roles := repoinfo.RolesInRepo{Roles: s.enforcer.GetPermissionsInRepo(user.Active.Did, f.Knot, f.DidSlashRepo())} 2350 + roles := repoinfo.RolesInRepo{Roles: s.enforcer.GetPermissionsInRepo(user.Active.Did, f.Knot, f.RepoIdentifier())} 2364 2351 isOwner := roles.IsOwner() 2365 2352 isCollaborator := roles.IsCollaborator() 2366 2353 isPullAuthor := user.Active.Did == pull.OwnerDid ··· 2434 2421 } 2435 2422 2436 2423 // auth filter: only owner or collaborators can close 2437 - roles := repoinfo.RolesInRepo{Roles: s.enforcer.GetPermissionsInRepo(user.Active.Did, f.Knot, f.DidSlashRepo())} 2424 + roles := repoinfo.RolesInRepo{Roles: s.enforcer.GetPermissionsInRepo(user.Active.Did, f.Knot, f.RepoIdentifier())} 2438 2425 isOwner := roles.IsOwner() 2439 2426 isCollaborator := roles.IsCollaborator() 2440 2427 isPullAuthor := user.Active.Did == pull.OwnerDid ··· 2528 2515 TargetBranch: targetBranch, 2529 2516 OwnerDid: user.Active.Did, 2530 2517 RepoAt: repo.RepoAt(), 2518 + RepoDid: repo.RepoDid, 2531 2519 Rkey: rkey, 2532 2520 Mentions: mentions, 2533 2521 References: references, ··· 2559 2547 } 2560 2548 2561 2549 func ptrPullState(s models.PullState) *models.PullState { return &s } 2550 + 2551 + func repoPullTarget(repo *models.Repo, branch string) *tangled.RepoPull_Target { 2552 + target := &tangled.RepoPull_Target{Branch: branch} 2553 + if repo.RepoDid != "" { 2554 + target.RepoDid = &repo.RepoDid 2555 + } else { 2556 + s := string(repo.RepoAt()) 2557 + target.Repo = &s 2558 + } 2559 + return target 2560 + }
+2 -2
appview/repo/archive.go
··· 25 25 scheme = "https" 26 26 } 27 27 host := fmt.Sprintf("%s://%s", scheme, f.Knot) 28 - didSlashRepo := f.DidSlashRepo() 28 + didSlashRepo := f.RepoIdentifier() 29 29 30 30 // build the xrpc url 31 31 u, err := url.Parse(host) ··· 66 66 if link := resp.Header.Get("Link"); link != "" { 67 67 if resolvedRef, err := extractImmutableLink(link); err == nil { 68 68 newLink := fmt.Sprintf("<%s/%s/archive/%s.tar.gz>; rel=\"immutable\"", 69 - rp.config.Core.BaseUrl(), f.DidSlashRepo(), resolvedRef) 69 + rp.config.Core.BaseUrl(), f.RepoIdentifier(), resolvedRef) 70 70 w.Header().Set("Link", newLink) 71 71 } 72 72 }
+19 -9
appview/repo/artifact.go
··· 80 80 Repo: user.Active.Did, 81 81 Rkey: rkey, 82 82 Record: &lexutil.LexiconTypeDecoder{ 83 - Val: &tangled.RepoArtifact{ 84 - Artifact: uploadBlobResp.Blob, 85 - CreatedAt: createdAt.Format(time.RFC3339), 86 - Name: header.Filename, 87 - Repo: f.RepoAt().String(), 88 - Tag: tag.Tag.Hash[:], 89 - }, 83 + Val: repoArtifactRecord(f, uploadBlobResp.Blob, createdAt, header.Filename, tag.Tag.Hash[:]), 90 84 }, 91 85 }) 92 86 if err != nil { ··· 109 103 Did: user.Active.Did, 110 104 Rkey: rkey, 111 105 RepoAt: f.RepoAt(), 106 + RepoDid: f.RepoDid, 112 107 Tag: tag.Tag.Hash, 113 108 CreatedAt: createdAt, 114 109 BlobCid: cid.Cid(uploadBlobResp.Blob.Ref), ··· 322 317 Host: host, 323 318 } 324 319 325 - repo := fmt.Sprintf("%s/%s", f.Did, f.Name) 326 - xrpcBytes, err := tangled.RepoTags(ctx, xrpcc, "", 0, repo) 320 + xrpcBytes, err := tangled.RepoTags(ctx, xrpcc, "", 0, f.RepoIdentifier()) 327 321 if err != nil { 328 322 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 329 323 l.Error("failed to call XRPC repo.tags", "err", xrpcerr) ··· 358 352 359 353 return tag, nil 360 354 } 355 + 356 + func repoArtifactRecord(f *models.Repo, blob *lexutil.LexBlob, createdAt time.Time, name string, tag []byte) *tangled.RepoArtifact { 357 + rec := &tangled.RepoArtifact{ 358 + Artifact: blob, 359 + CreatedAt: createdAt.Format(time.RFC3339), 360 + Name: name, 361 + Tag: tag, 362 + } 363 + if f.RepoDid != "" { 364 + rec.RepoDid = &f.RepoDid 365 + } else { 366 + s := f.RepoAt().String() 367 + rec.Repo = &s 368 + } 369 + return rec 370 + }
+3 -4
appview/repo/blob.go
··· 58 58 xrpcc := &indigoxrpc.Client{ 59 59 Host: host, 60 60 } 61 - repo := fmt.Sprintf("%s/%s", f.Did, f.Name) 62 - resp, err := tangled.RepoBlob(r.Context(), xrpcc, filePath, false, ref, repo) 61 + resp, err := tangled.RepoBlob(r.Context(), xrpcc, filePath, false, ref, f.RepoIdentifier()) 63 62 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 64 63 l.Error("failed to call XRPC repo.blob", "err", xrpcerr) 65 64 rp.pages.Error503(w) ··· 139 138 if !rp.config.Core.Dev { 140 139 scheme = "https" 141 140 } 142 - repo := f.DidSlashRepo() 141 + repo := f.RepoIdentifier() 143 142 baseURL := &url.URL{ 144 143 Scheme: scheme, 145 144 Host: f.Knot, ··· 290 289 scheme = "https" 291 290 } 292 291 293 - repoName := fmt.Sprintf("%s/%s", repo.Did, repo.Name) 292 + repoName := repo.RepoIdentifier() 294 293 baseURL := &url.URL{ 295 294 Scheme: scheme, 296 295 Host: repo.Knot,
+1 -2
appview/repo/branches.go
··· 29 29 xrpcc := &indigoxrpc.Client{ 30 30 Host: host, 31 31 } 32 - repo := fmt.Sprintf("%s/%s", f.Did, f.Name) 33 - xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo) 32 + xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, f.RepoIdentifier()) 34 33 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 35 34 l.Error("failed to call XRPC repo.branches", "err", xrpcerr) 36 35 rp.pages.Error503(w)
+7 -7
appview/repo/compare.go
··· 36 36 Host: host, 37 37 } 38 38 39 - repo := fmt.Sprintf("%s/%s", f.Did, f.Name) 40 - branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo) 39 + repoId := f.RepoIdentifier() 40 + branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repoId) 41 41 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 42 42 l.Error("failed to call XRPC repo.branches", "err", xrpcerr) 43 43 rp.pages.Error503(w) ··· 74 74 head = queryHead 75 75 } 76 76 77 - tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo) 77 + tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repoId) 78 78 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 79 79 l.Error("failed to call XRPC repo.tags", "err", xrpcerr) 80 80 rp.pages.Error503(w) ··· 149 149 Host: host, 150 150 } 151 151 152 - repo := fmt.Sprintf("%s/%s", f.Did, f.Name) 152 + repoId := f.RepoIdentifier() 153 153 154 - branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo) 154 + branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repoId) 155 155 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 156 156 l.Error("failed to call XRPC repo.branches", "err", xrpcerr) 157 157 rp.pages.Error503(w) ··· 165 165 return 166 166 } 167 167 168 - tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo) 168 + tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repoId) 169 169 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 170 170 l.Error("failed to call XRPC repo.tags", "err", xrpcerr) 171 171 rp.pages.Error503(w) ··· 179 179 return 180 180 } 181 181 182 - compareBytes, err := tangled.RepoCompare(r.Context(), xrpcc, repo, base, head) 182 + compareBytes, err := tangled.RepoCompare(r.Context(), xrpcc, repoId, base, head) 183 183 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 184 184 l.Error("failed to call XRPC repo.compare", "err", xrpcerr) 185 185 rp.pages.Error503(w)
+7 -8
appview/repo/index.go
··· 182 182 183 183 if err != nil || langs == nil { 184 184 // non-fatal, fetch langs from ks via XRPC 185 - didSlashRepo := fmt.Sprintf("%s/%s", repo.Did, repo.Name) 186 - ls, err := tangled.RepoLanguages(ctx, xrpcc, currentRef, didSlashRepo) 185 + ls, err := tangled.RepoLanguages(ctx, xrpcc, currentRef, repo.RepoIdentifier()) 187 186 if err != nil { 188 187 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 189 188 l.Error("failed to call XRPC repo.languages", "err", xrpcerr) ··· 199 198 for _, lang := range ls.Languages { 200 199 langs = append(langs, models.RepoLanguage{ 201 200 RepoAt: repo.RepoAt(), 201 + RepoDid: repo.RepoDid, 202 202 Ref: currentRef, 203 203 IsDefaultRef: isDefaultRef, 204 204 Language: lang.Name, ··· 259 259 260 260 // buildIndexResponse creates a RepoIndexResponse by combining multiple xrpc calls in parallel 261 261 func (rp *Repo) buildIndexResponse(ctx context.Context, xrpcc *indigoxrpc.Client, repo *models.Repo, ref string) (*types.RepoIndexResponse, error) { 262 - didSlashRepo := fmt.Sprintf("%s/%s", repo.Did, repo.Name) 262 + repoId := repo.RepoIdentifier() 263 263 264 - // first get branches to determine the ref if not specified 265 - branchesBytes, err := tangled.RepoBranches(ctx, xrpcc, "", 0, didSlashRepo) 264 + branchesBytes, err := tangled.RepoBranches(ctx, xrpcc, "", 0, repoId) 266 265 if err != nil { 267 266 return nil, fmt.Errorf("failed to call repoBranches: %w", err) 268 267 } ··· 304 303 305 304 // tags 306 305 wg.Go(func() { 307 - tagsBytes, err := tangled.RepoTags(ctx, xrpcc, "", 0, didSlashRepo) 306 + tagsBytes, err := tangled.RepoTags(ctx, xrpcc, "", 0, repoId) 308 307 if err != nil { 309 308 errs = errors.Join(errs, fmt.Errorf("failed to call repoTags: %w", err)) 310 309 return ··· 317 316 318 317 // tree/files 319 318 wg.Go(func() { 320 - resp, err := tangled.RepoTree(ctx, xrpcc, "", ref, didSlashRepo) 319 + resp, err := tangled.RepoTree(ctx, xrpcc, "", ref, repoId) 321 320 if err != nil { 322 321 errs = errors.Join(errs, fmt.Errorf("failed to call repoTree: %w", err)) 323 322 return ··· 327 326 328 327 // commits 329 328 wg.Go(func() { 330 - logBytes, err := tangled.RepoLog(ctx, xrpcc, "", 50, "", ref, didSlashRepo) 329 + logBytes, err := tangled.RepoLog(ctx, xrpcc, "", 50, "", ref, repoId) 331 330 if err != nil { 332 331 errs = errors.Join(errs, fmt.Errorf("failed to call repoLog: %w", err)) 333 332 return
+5 -6
appview/repo/log.go
··· 57 57 cursor = strconv.Itoa(offset) 58 58 } 59 59 60 - repo := fmt.Sprintf("%s/%s", f.Did, f.Name) 61 - xrpcBytes, err := tangled.RepoLog(r.Context(), xrpcc, cursor, limit, "", ref, repo) 60 + xrpcBytes, err := tangled.RepoLog(r.Context(), xrpcc, cursor, limit, "", ref, f.RepoIdentifier()) 62 61 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 63 62 l.Error("failed to call XRPC repo.log", "err", xrpcerr) 64 63 rp.pages.Error503(w) ··· 72 71 return 73 72 } 74 73 75 - tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo) 74 + repoId := f.RepoIdentifier() 75 + tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repoId) 76 76 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 77 77 l.Error("failed to call XRPC repo.tags", "err", xrpcerr) 78 78 rp.pages.Error503(w) ··· 93 93 } 94 94 } 95 95 96 - branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo) 96 + branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repoId) 97 97 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 98 98 l.Error("failed to call XRPC repo.branches", "err", xrpcerr) 99 99 rp.pages.Error503(w) ··· 172 172 Host: host, 173 173 } 174 174 175 - repo := fmt.Sprintf("%s/%s", f.Did, f.Name) 176 - xrpcBytes, err := tangled.RepoDiff(r.Context(), xrpcc, ref, repo) 175 + xrpcBytes, err := tangled.RepoDiff(r.Context(), xrpcc, ref, f.RepoIdentifier()) 177 176 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 178 177 l.Error("failed to call XRPC repo.diff", "err", xrpcerr) 179 178 rp.pages.Error503(w)
+128 -55
appview/repo/repo.go
··· 33 33 atpclient "github.com/bluesky-social/indigo/atproto/client" 34 34 "github.com/bluesky-social/indigo/atproto/syntax" 35 35 lexutil "github.com/bluesky-social/indigo/lex/util" 36 - securejoin "github.com/cyphar/filepath-securejoin" 36 + 37 37 "github.com/go-chi/chi/v5" 38 38 ) 39 39 ··· 309 309 return 310 310 } 311 311 312 - err = db.SubscribeLabel(tx, &models.RepoLabel{ 312 + if err = db.SubscribeLabel(tx, &models.RepoLabel{ 313 313 RepoAt: f.RepoAt(), 314 314 LabelAt: label.AtUri(), 315 - }) 315 + RepoDid: f.RepoDid, 316 + }); err != nil { 317 + fail("Failed to subscribe to label.", err) 318 + return 319 + } 316 320 317 321 err = tx.Commit() 318 322 if err != nil { ··· 504 508 err = db.SubscribeLabel(tx, &models.RepoLabel{ 505 509 RepoAt: f.RepoAt(), 506 510 LabelAt: syntax.ATURI(l), 511 + RepoDid: f.RepoDid, 507 512 }) 508 513 if err != nil { 509 514 fail("Failed to subscribe to label.", err) ··· 746 751 Repo: currentUser.Active.Did, 747 752 Rkey: rkey, 748 753 Record: &lexutil.LexiconTypeDecoder{ 749 - Val: &tangled.RepoCollaborator{ 750 - Subject: collaboratorIdent.DID.String(), 751 - Repo: string(f.RepoAt()), 752 - CreatedAt: createdAt.Format(time.RFC3339), 753 - }}, 754 + Val: repoCollaboratorRecord(f, collaboratorIdent.DID.String(), createdAt), 755 + }, 754 756 }) 755 757 // invalid record 756 758 if err != nil { ··· 785 787 } 786 788 defer rollback() 787 789 788 - err = rp.enforcer.AddCollaborator(collaboratorIdent.DID.String(), f.Knot, f.DidSlashRepo()) 790 + err = rp.enforcer.AddCollaborator(collaboratorIdent.DID.String(), f.Knot, f.RepoIdentifier()) 789 791 if err != nil { 790 792 fail("Failed to add collaborator permissions.", err) 791 793 return ··· 796 798 Rkey: rkey, 797 799 SubjectDid: collaboratorIdent.DID, 798 800 RepoAt: f.RepoAt(), 801 + RepoDid: f.RepoDid, 799 802 Created: createdAt, 800 803 }) 801 804 if err != nil { ··· 891 894 }() 892 895 893 896 // remove collaborator RBAC 894 - repoCollaborators, err := rp.enforcer.E.GetImplicitUsersForResourceByDomain(f.DidSlashRepo(), f.Knot) 897 + repoCollaborators, err := rp.enforcer.E.GetImplicitUsersForResourceByDomain(f.RepoIdentifier(), f.Knot) 895 898 if err != nil { 896 899 rp.pages.Notice(w, noticeId, "Failed to remove collaborators") 897 900 return 898 901 } 899 902 for _, c := range repoCollaborators { 900 903 did := c[0] 901 - rp.enforcer.RemoveCollaborator(did, f.Knot, f.DidSlashRepo()) 904 + rp.enforcer.RemoveCollaborator(did, f.Knot, f.RepoIdentifier()) 902 905 } 903 906 l.Info("removed collaborators") 904 907 905 908 // remove repo RBAC 906 - err = rp.enforcer.RemoveRepo(f.Did, f.Knot, f.DidSlashRepo()) 909 + err = rp.enforcer.RemoveRepo(f.Did, f.Knot, f.RepoIdentifier()) 907 910 if err != nil { 908 911 rp.pages.Notice(w, noticeId, "Failed to update RBAC rules") 909 912 return ··· 1058 1061 uri = "http" 1059 1062 } 1060 1063 1061 - forkSourceUrl := fmt.Sprintf("%s://%s/%s/%s", uri, f.Knot, f.Did, f.Name) 1064 + forkSourceUrl := fmt.Sprintf("%s://%s/%s", uri, f.Knot, f.RepoIdentifier()) 1062 1065 l = l.With("cloneUrl", forkSourceUrl) 1063 1066 1064 - sourceAt := f.RepoAt().String() 1067 + rkey := tid.TID() 1065 1068 1066 - // create an atproto record for this fork 1067 - rkey := tid.TID() 1069 + // TODO: this could coordinate better with the knot to recieve a clone status 1070 + client, err := rp.oauth.ServiceClient( 1071 + r, 1072 + oauth.WithService(targetKnot), 1073 + oauth.WithLxm(tangled.RepoCreateNSID), 1074 + oauth.WithDev(rp.config.Core.Dev), 1075 + oauth.WithTimeout(time.Second*20), 1076 + ) 1077 + if err != nil { 1078 + l.Error("could not create service client", "err", err) 1079 + rp.pages.Notice(w, "repo", "Failed to connect to knot server.") 1080 + return 1081 + } 1082 + 1083 + forkInput := &tangled.RepoCreate_Input{ 1084 + Rkey: rkey, 1085 + Name: forkName, 1086 + Source: &forkSourceUrl, 1087 + } 1088 + if rd := strings.TrimSpace(r.FormValue("repo_did")); rd != "" { 1089 + forkInput.RepoDid = &rd 1090 + } 1091 + 1092 + createResp, createErr := tangled.RepoCreate( 1093 + r.Context(), 1094 + client, 1095 + forkInput, 1096 + ) 1097 + if err := xrpcclient.HandleXrpcErr(createErr); err != nil { 1098 + rp.pages.Notice(w, "repo", err.Error()) 1099 + return 1100 + } 1101 + 1102 + var repoDid string 1103 + if createResp != nil && createResp.RepoDid != nil { 1104 + repoDid = *createResp.RepoDid 1105 + } 1106 + if repoDid == "" { 1107 + l.Error("knot returned empty repo DID for fork") 1108 + rp.pages.Notice(w, "repo", "Knot failed to mint a repo DID. The knot may need to be upgraded.") 1109 + return 1110 + } 1111 + 1112 + forkSource := f.RepoAt().String() 1113 + if f.RepoDid != "" { 1114 + forkSource = f.RepoDid 1115 + } 1116 + 1068 1117 repo := &models.Repo{ 1069 1118 Did: user.Active.Did, 1070 1119 Name: forkName, 1071 1120 Knot: targetKnot, 1072 1121 Rkey: rkey, 1073 - Source: sourceAt, 1122 + Source: forkSource, 1074 1123 Description: f.Description, 1075 1124 Created: time.Now(), 1076 1125 Labels: rp.config.Label.DefaultLabelDefs, 1126 + RepoDid: repoDid, 1077 1127 } 1078 1128 record := repo.AsRecord() 1079 1129 1130 + cleanupKnot := func() { 1131 + go func() { 1132 + delays := []time.Duration{0, 2 * time.Second, 5 * time.Second} 1133 + for attempt, delay := range delays { 1134 + time.Sleep(delay) 1135 + deleteClient, dErr := rp.oauth.ServiceClient( 1136 + r, 1137 + oauth.WithService(targetKnot), 1138 + oauth.WithLxm(tangled.RepoDeleteNSID), 1139 + oauth.WithDev(rp.config.Core.Dev), 1140 + ) 1141 + if dErr != nil { 1142 + l.Error("failed to create delete client for knot cleanup", "attempt", attempt+1, "err", dErr) 1143 + continue 1144 + } 1145 + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 1146 + if dErr := tangled.RepoDelete(ctx, deleteClient, &tangled.RepoDelete_Input{ 1147 + Did: user.Active.Did, 1148 + Name: forkName, 1149 + Rkey: rkey, 1150 + }); dErr != nil { 1151 + cancel() 1152 + l.Error("failed to clean up fork on knot after rollback", "attempt", attempt+1, "err", dErr) 1153 + continue 1154 + } 1155 + cancel() 1156 + l.Info("successfully cleaned up fork on knot after rollback", "attempt", attempt+1) 1157 + return 1158 + } 1159 + l.Error("exhausted retries for knot cleanup, fork may be orphaned", 1160 + "did", user.Active.Did, "fork", forkName, "knot", targetKnot) 1161 + }() 1162 + } 1163 + 1080 1164 atpClient, err := rp.oauth.AuthorizedClient(r) 1081 1165 if err != nil { 1082 1166 l.Error("failed to create xrpcclient", "err", err) 1167 + cleanupKnot() 1083 1168 rp.pages.Notice(w, "repo", "Failed to fork repository.") 1084 1169 return 1085 1170 } ··· 1094 1179 }) 1095 1180 if err != nil { 1096 1181 l.Error("failed to write to PDS", "err", err) 1182 + cleanupKnot() 1097 1183 rp.pages.Notice(w, "repo", "Failed to announce repository creation.") 1098 1184 return 1099 1185 } ··· 1109 1195 return 1110 1196 } 1111 1197 1112 - // The rollback function reverts a few things on failure: 1113 - // - the pending txn 1114 - // - the ACLs 1115 - // - the atproto record created 1116 1198 rollback := func() { 1117 1199 err1 := tx.Rollback() 1118 1200 err2 := rp.enforcer.E.LoadPolicy() 1119 1201 err3 := rollbackRecord(context.Background(), aturi, atpClient) 1120 1202 1121 - // ignore txn complete errors, this is okay 1122 1203 if errors.Is(err1, sql.ErrTxDone) { 1123 1204 err1 = nil 1124 1205 } 1125 1206 1126 1207 if errs := errors.Join(err1, err2, err3); errs != nil { 1127 1208 l.Error("failed to rollback changes", "errs", errs) 1128 - return 1209 + } 1210 + 1211 + if aturi != "" { 1212 + cleanupKnot() 1129 1213 } 1130 1214 } 1131 1215 defer rollback() 1132 1216 1133 - // TODO: this could coordinate better with the knot to recieve a clone status 1134 - client, err := rp.oauth.ServiceClient( 1135 - r, 1136 - oauth.WithService(targetKnot), 1137 - oauth.WithLxm(tangled.RepoCreateNSID), 1138 - oauth.WithDev(rp.config.Core.Dev), 1139 - oauth.WithTimeout(time.Second*20), // big repos take time to clone 1140 - ) 1141 - if err != nil { 1142 - l.Error("could not create service client", "err", err) 1143 - rp.pages.Notice(w, "repo", "Failed to connect to knot server.") 1144 - return 1145 - } 1146 - 1147 - err = tangled.RepoCreate( 1148 - r.Context(), 1149 - client, 1150 - &tangled.RepoCreate_Input{ 1151 - Rkey: rkey, 1152 - Source: &forkSourceUrl, 1153 - }, 1154 - ) 1155 - if err := xrpcclient.HandleXrpcErr(err); err != nil { 1156 - rp.pages.Notice(w, "repo", err.Error()) 1157 - return 1158 - } 1159 - 1160 1217 err = db.AddRepo(tx, repo) 1161 1218 if err != nil { 1162 1219 l.Error("failed to AddRepo", "err", err) ··· 1164 1221 return 1165 1222 } 1166 1223 1167 - // acls 1168 - p, _ := securejoin.SecureJoin(user.Active.Did, forkName) 1169 - err = rp.enforcer.AddRepo(user.Active.Did, targetKnot, p) 1224 + rbacPath := repo.RepoIdentifier() 1225 + err = rp.enforcer.AddRepo(user.Active.Did, targetKnot, rbacPath) 1170 1226 if err != nil { 1171 1227 l.Error("failed to add ACLs", "err", err) 1172 1228 rp.pages.Notice(w, "repo", "Failed to set up repository permissions.") ··· 1187 1243 return 1188 1244 } 1189 1245 1190 - // reset the ATURI because the transaction completed successfully 1191 1246 aturi = "" 1192 1247 1193 1248 rp.notifier.NewRepo(r.Context(), repo) 1194 - rp.pages.HxLocation(w, fmt.Sprintf("/%s/%s", user.Active.Did, forkName)) 1249 + if repoDid != "" { 1250 + rp.pages.HxLocation(w, fmt.Sprintf("/%s", repoDid)) 1251 + } else { 1252 + rp.pages.HxLocation(w, fmt.Sprintf("/%s/%s", user.Active.Did, forkName)) 1253 + } 1195 1254 } 1196 1255 } 1197 1256 ··· 1216 1275 }) 1217 1276 return err 1218 1277 } 1278 + 1279 + func repoCollaboratorRecord(f *models.Repo, subject string, createdAt time.Time) *tangled.RepoCollaborator { 1280 + rec := &tangled.RepoCollaborator{ 1281 + Subject: subject, 1282 + CreatedAt: createdAt.Format(time.RFC3339), 1283 + } 1284 + if f.RepoDid != "" { 1285 + rec.RepoDid = &f.RepoDid 1286 + } else { 1287 + s := string(f.RepoAt()) 1288 + rec.Repo = &s 1289 + } 1290 + return rec 1291 + }
+2 -3
appview/repo/settings.go
··· 188 188 Host: host, 189 189 } 190 190 191 - repo := fmt.Sprintf("%s/%s", f.Did, f.Name) 192 - xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo) 191 + xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, f.RepoIdentifier()) 193 192 var result types.RepoBranchesResponse 194 193 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 195 194 l.Error("failed to call XRPC repo.branches", "err", xrpcerr) ··· 260 259 user := rp.oauth.GetMultiAccountUser(r) 261 260 262 261 collaborators, err := func(repo *models.Repo) ([]pages.Collaborator, error) { 263 - repoCollaborators, err := rp.enforcer.E.GetImplicitUsersForResourceByDomain(repo.DidSlashRepo(), repo.Knot) 262 + repoCollaborators, err := rp.enforcer.E.GetImplicitUsersForResourceByDomain(repo.RepoIdentifier(), repo.Knot) 264 263 if err != nil { 265 264 return nil, err 266 265 }
+3 -5
appview/repo/tags.go
··· 35 35 xrpcc := &indigoxrpc.Client{ 36 36 Host: host, 37 37 } 38 - repo := fmt.Sprintf("%s/%s", f.Did, f.Name) 39 - xrpcBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo) 38 + xrpcBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, f.RepoIdentifier()) 40 39 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 41 40 l.Error("failed to call XRPC repo.tags", "err", xrpcerr) 42 41 rp.pages.Error503(w) ··· 98 97 xrpcc := &indigoxrpc.Client{ 99 98 Host: host, 100 99 } 101 - repo := fmt.Sprintf("%s/%s", f.Did, f.Name) 102 100 tag := chi.URLParam(r, "tag") 103 101 104 - xrpcBytes, err := tangled.RepoTag(r.Context(), xrpcc, repo, tag) 102 + xrpcBytes, err := tangled.RepoTag(r.Context(), xrpcc, f.RepoIdentifier(), tag) 105 103 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 106 104 // if we don't match an existing tag, and the tag we're trying 107 105 // to match is "latest", resolve to the most recent tag 108 106 if tag == "latest" { 109 - tagsBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 1, repo) 107 + tagsBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 1, f.RepoIdentifier()) 110 108 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 111 109 l.Error("failed to call XRPC repo.tags for latest", "err", xrpcerr) 112 110 rp.pages.Error503(w)
+1 -2
appview/repo/tree.go
··· 41 41 xrpcc := &indigoxrpc.Client{ 42 42 Host: host, 43 43 } 44 - repo := fmt.Sprintf("%s/%s", f.Did, f.Name) 45 - xrpcResp, err := tangled.RepoTree(r.Context(), xrpcc, treePath, ref, repo) 44 + xrpcResp, err := tangled.RepoTree(r.Context(), xrpcc, treePath, ref, f.RepoIdentifier()) 46 45 if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 47 46 l.Error("failed to call XRPC repo.tree", "err", xrpcerr) 48 47 rp.pages.Error503(w)
+6 -5
appview/repo/webhooks.go
··· 89 89 } 90 90 91 91 webhook := &models.Webhook{ 92 - RepoAt: f.RepoAt(), 93 - Url: url, 94 - Secret: secret, 95 - Active: active, 96 - Events: events, 92 + RepoAt: f.RepoAt(), 93 + RepoDid: f.RepoDid, 94 + Url: url, 95 + Secret: secret, 96 + Active: active, 97 + Events: events, 97 98 } 98 99 99 100 tx, err := rp.db.Begin()