Monorepo for Tangled
tangled.org
1package xrpc
2
3import (
4 "database/sql"
5 "encoding/json"
6 "errors"
7 "log/slog"
8 "net/http"
9 "time"
10
11 "github.com/bluesky-social/indigo/atproto/atclient"
12 "github.com/bluesky-social/indigo/util/ssrf"
13 "github.com/go-chi/chi/v5"
14 "github.com/redis/go-redis/v9"
15 "tangled.org/core/api/tangled"
16 "tangled.org/core/idresolver"
17 "tangled.org/core/knotmirror/config"
18 "tangled.org/core/knotmirror/knotstream"
19 "tangled.org/core/knotmirror/repoindexer"
20 "tangled.org/core/log"
21)
22
23type Xrpc struct {
24 cfg *config.Config
25 db *sql.DB
26 rdb *redis.Client
27 indexer *repoindexer.Indexer
28 resolver *idresolver.Resolver
29 ks *knotstream.KnotStream
30 logger *slog.Logger
31 httpClient *http.Client
32 inflight *inflightTracker
33}
34
35func New(logger *slog.Logger, cfg *config.Config, db *sql.DB, rdb *redis.Client, indexer *repoindexer.Indexer, resolver *idresolver.Resolver, ks *knotstream.KnotStream) *Xrpc {
36 httpClient := &http.Client{
37 Timeout: 30 * time.Second,
38 }
39 if cfg.KnotSSRF {
40 httpClient.Transport = ssrf.PublicOnlyTransport()
41 }
42 return &Xrpc{
43 cfg: cfg,
44 db: db,
45 rdb: rdb,
46 indexer: indexer,
47 resolver: resolver,
48 ks: ks,
49 logger: log.SubLogger(logger, "xrpc"),
50 httpClient: httpClient,
51 inflight: newInflightTracker(),
52 }
53}
54
55func (x *Xrpc) Router() http.Handler {
56 r := chi.NewRouter()
57 r.Use(metricsMiddleware)
58
59 r.Group(func(r chi.Router) {
60 r.Use(x.inflight.middleware)
61
62 r.Get("/"+tangled.GitTempGetArchiveNSID, x.GetArchive)
63 r.Get("/"+tangled.GitTempGetBlobNSID, x.GetBlob)
64 r.Get("/"+tangled.GitTempGetBranchNSID, x.GetBranch)
65 // r.Get("/"+tangled.GitTempGetCommitNSID, x.GetCommit) // todo
66 // r.Get("/"+tangled.GitTempGetDiffNSID, x.GetDiff) // todo
67 r.Get("/"+tangled.GitTempGetEntryNSID, x.GetEntry) // todo
68 // r.Get("/"+tangled.GitTempGetHeadNSID, x.GetHead) // todo
69 r.Get("/"+tangled.GitTempGetTagNSID, x.GetTag) // using types.Response
70 r.Get("/"+tangled.GitTempGetTreeNSID, x.GetTree)
71 r.Get("/"+tangled.GitTempListBranchesNSID, x.ListBranches) // wip, unknown output
72 r.Get("/"+tangled.GitTempListCommitsNSID, x.ListCommits)
73 r.Get("/"+tangled.GitTempListLanguagesNSID, x.ListLanguages)
74 r.Get("/"+tangled.GitTempListTagsNSID, x.ListTags)
75 r.Post("/"+tangled.SyncRequestCrawlNSID, x.RequestCrawl)
76 })
77
78 return r
79}
80
81func writeJson(w http.ResponseWriter, status int, response any) error {
82 w.Header().Set("Content-Type", "application/json")
83 w.WriteHeader(status)
84 if err := json.NewEncoder(w).Encode(response); err != nil {
85 return err
86 }
87 return nil
88}
89
90func writeErr(w http.ResponseWriter, err error) error {
91 var apiErr *atclient.APIError
92 if errors.As(err, &apiErr) {
93 return writeJson(w, apiErr.StatusCode, atclient.ErrorBody{Name: apiErr.Name, Message: apiErr.Message})
94 }
95 return writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "internal server error"})
96}