Monorepo for Tangled
at push-pkuzytwlwptp 127 lines 3.9 kB view raw
1package xrpc 2 3import ( 4 "encoding/json" 5 "log/slog" 6 "net/http" 7 "strings" 8 9 "tangled.org/core/api/tangled" 10 "tangled.org/core/idresolver" 11 "tangled.org/core/jetstream" 12 "tangled.org/core/knotserver/config" 13 "tangled.org/core/knotserver/db" 14 "tangled.org/core/notifier" 15 "tangled.org/core/rbac" 16 xrpcerr "tangled.org/core/xrpc/errors" 17 "tangled.org/core/xrpc/serviceauth" 18 19 "github.com/go-chi/chi/v5" 20) 21 22type Xrpc struct { 23 Config *config.Config 24 Db *db.DB 25 Ingester *jetstream.JetstreamClient 26 Enforcer *rbac.Enforcer 27 Logger *slog.Logger 28 Notifier *notifier.Notifier 29 Resolver *idresolver.Resolver 30 ServiceAuth *serviceauth.ServiceAuth 31} 32 33func (x *Xrpc) Router() http.Handler { 34 r := chi.NewRouter() 35 36 r.Group(func(r chi.Router) { 37 r.Use(x.ServiceAuth.VerifyServiceAuth) 38 39 r.Post("/"+tangled.RepoSetDefaultBranchNSID, x.SetDefaultBranch) 40 r.Post("/"+tangled.RepoDeleteBranchNSID, x.DeleteBranch) 41 r.Post("/"+tangled.RepoCreateNSID, x.CreateRepo) 42 r.Post("/"+tangled.RepoDeleteNSID, x.DeleteRepo) 43 r.Post("/"+tangled.RepoForkStatusNSID, x.ForkStatus) 44 r.Post("/"+tangled.RepoForkSyncNSID, x.ForkSync) 45 r.Post("/"+tangled.RepoHiddenRefNSID, x.HiddenRef) 46 r.Post("/"+tangled.RepoMergeNSID, x.Merge) 47 }) 48 49 // merge check is an open endpoint 50 // 51 // TODO: should we constrain this more? 52 // - we can calculate on PR submit/resubmit/gitRefUpdate etc. 53 // - use ETags on clients to keep requests to a minimum 54 r.Post("/"+tangled.RepoMergeCheckNSID, x.MergeCheck) 55 56 // repo query endpoints (no auth required) 57 r.Get("/"+tangled.RepoTreeNSID, x.RepoTree) 58 r.Get("/"+tangled.RepoLogNSID, x.RepoLog) 59 r.Get("/"+tangled.RepoBranchesNSID, x.RepoBranches) 60 r.Get("/"+tangled.RepoTagsNSID, x.RepoTags) 61 r.Get("/"+tangled.RepoTagNSID, x.RepoTag) 62 r.Get("/"+tangled.RepoBlobNSID, x.RepoBlob) 63 r.Get("/"+tangled.RepoDiffNSID, x.RepoDiff) 64 r.Get("/"+tangled.RepoCompareNSID, x.RepoCompare) 65 r.Get("/"+tangled.RepoGetDefaultBranchNSID, x.RepoGetDefaultBranch) 66 r.Get("/"+tangled.RepoBranchNSID, x.RepoBranch) 67 r.Get("/"+tangled.RepoArchiveNSID, x.RepoArchive) 68 r.Get("/"+tangled.RepoLanguagesNSID, x.RepoLanguages) 69 70 r.Get("/"+tangled.RepoResolveAtUriNSID, x.ResolveAtUri) 71 72 // knot query endpoints (no auth required) 73 r.Get("/"+tangled.KnotListKeysNSID, x.ListKeys) 74 r.Get("/"+tangled.KnotVersionNSID, x.Version) 75 76 // service query endpoints (no auth required) 77 r.Get("/"+tangled.OwnerNSID, x.Owner) 78 79 return r 80} 81 82// parseRepoParam parses a repo parameter in 'did/repoName' or bare 'did:...' format 83// and returns the full repository path on disk 84func (x *Xrpc) parseRepoParam(repo string) (string, error) { 85 if repo == "" { 86 return "", xrpcerr.NewXrpcError( 87 xrpcerr.WithTag("InvalidRequest"), 88 xrpcerr.WithMessage("missing repo parameter"), 89 ) 90 } 91 92 if strings.HasPrefix(repo, "did:") && !strings.Contains(repo, "/") { 93 repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repo) 94 if err != nil { 95 return "", xrpcerr.RepoNotFoundError 96 } 97 return repoPath, nil 98 } 99 100 parts := strings.SplitN(repo, "/", 2) 101 if len(parts) != 2 || parts[0] == "" || parts[1] == "" { 102 return "", xrpcerr.NewXrpcError( 103 xrpcerr.WithTag("InvalidRequest"), 104 xrpcerr.WithMessage("invalid repo format, expected 'did/repoName'"), 105 ) 106 } 107 108 repoPath, _, err := x.Db.ResolveRepoOnDisk(x.Config.Repo.ScanPath, parts[0], parts[1]) 109 if err != nil { 110 return "", xrpcerr.RepoNotFoundError 111 } 112 return repoPath, nil 113} 114 115func writeError(w http.ResponseWriter, e xrpcerr.XrpcError, status int) { 116 w.Header().Set("Content-Type", "application/json") 117 w.WriteHeader(status) 118 json.NewEncoder(w).Encode(e) 119} 120 121func 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}