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