forked from
tangled.org/core
fork
Configure Feed
Select the types of activity you want to include in your feed.
this repo has no description
fork
Configure Feed
Select the types of activity you want to include in your feed.
1package knotserver
2
3import (
4 "context"
5 "fmt"
6 "log/slog"
7 "net/http"
8 "runtime/debug"
9
10 "github.com/go-chi/chi/v5"
11 "tangled.sh/tangled.sh/core/jetstream"
12 "tangled.sh/tangled.sh/core/knotserver/config"
13 "tangled.sh/tangled.sh/core/knotserver/db"
14 "tangled.sh/tangled.sh/core/rbac"
15)
16
17const (
18 ThisServer = "thisserver" // resource identifier for rbac enforcement
19)
20
21type Handle struct {
22 c *config.Config
23 db *db.DB
24 jc *jetstream.JetstreamClient
25 e *rbac.Enforcer
26 l *slog.Logger
27
28 // init is a channel that is closed when the knot has been initailized
29 // i.e. when the first user (knot owner) has been added.
30 init chan struct{}
31 knotInitialized bool
32}
33
34func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, jc *jetstream.JetstreamClient, l *slog.Logger) (http.Handler, error) {
35 r := chi.NewRouter()
36
37 h := Handle{
38 c: c,
39 db: db,
40 e: e,
41 l: l,
42 jc: jc,
43 init: make(chan struct{}),
44 }
45
46 err := e.AddDomain(ThisServer)
47 if err != nil {
48 return nil, fmt.Errorf("failed to setup enforcer: %w", err)
49 }
50
51 err = h.jc.StartJetstream(ctx, h.processMessages)
52 if err != nil {
53 return nil, fmt.Errorf("failed to start jetstream: %w", err)
54 }
55
56 // Check if the knot knows about any Dids;
57 // if it does, it is already initialized and we can repopulate the
58 // Jetstream subscriptions.
59 dids, err := db.GetAllDids()
60 if err != nil {
61 return nil, fmt.Errorf("failed to get all Dids: %w", err)
62 }
63
64 if len(dids) > 0 {
65 h.knotInitialized = true
66 close(h.init)
67 for _, d := range dids {
68 h.jc.AddDid(d)
69 }
70 }
71
72 r.Get("/", h.Index)
73 r.Get("/capabilities", h.Capabilities)
74 r.Get("/version", h.Version)
75 r.Route("/{did}", func(r chi.Router) {
76 // Repo routes
77 r.Route("/{name}", func(r chi.Router) {
78 r.Route("/collaborator", func(r chi.Router) {
79 r.Use(h.VerifySignature)
80 r.Post("/add", h.AddRepoCollaborator)
81 })
82
83 r.Get("/", h.RepoIndex)
84 r.Get("/info/refs", h.InfoRefs)
85 r.Post("/git-upload-pack", h.UploadPack)
86 r.Get("/compare/{rev1}/{rev2}", h.Compare) // git diff-tree compare of two objects
87
88 r.With(h.VerifySignature).Post("/hidden-ref/{forkRef}/{remoteRef}", h.NewHiddenRef)
89
90 r.Route("/merge", func(r chi.Router) {
91 r.With(h.VerifySignature)
92 r.Post("/", h.Merge)
93 r.Post("/check", h.MergeCheck)
94 })
95
96 r.Route("/tree/{ref}", func(r chi.Router) {
97 r.Get("/", h.RepoIndex)
98 r.Get("/*", h.RepoTree)
99 })
100
101 r.Route("/blob/{ref}", func(r chi.Router) {
102 r.Get("/*", h.Blob)
103 })
104
105 r.Route("/raw/{ref}", func(r chi.Router) {
106 r.Get("/*", h.BlobRaw)
107 })
108
109 r.Get("/log/{ref}", h.Log)
110 r.Get("/archive/{file}", h.Archive)
111 r.Get("/commit/{ref}", h.Diff)
112 r.Get("/tags", h.Tags)
113 r.Route("/branches", func(r chi.Router) {
114 r.Get("/", h.Branches)
115 r.Get("/{branch}", h.Branch)
116 r.Route("/default", func(r chi.Router) {
117 r.Get("/", h.DefaultBranch)
118 r.With(h.VerifySignature).Put("/", h.SetDefaultBranch)
119 })
120 })
121 })
122 })
123
124 // Create a new repository.
125 r.Route("/repo", func(r chi.Router) {
126 r.Use(h.VerifySignature)
127 r.Put("/new", h.NewRepo)
128 r.Delete("/", h.RemoveRepo)
129 r.Post("/fork", h.RepoFork)
130 })
131
132 r.Route("/member", func(r chi.Router) {
133 r.Use(h.VerifySignature)
134 r.Put("/add", h.AddMember)
135 })
136
137 // Initialize the knot with an owner and public key.
138 r.With(h.VerifySignature).Post("/init", h.Init)
139
140 // Health check. Used for two-way verification with appview.
141 r.With(h.VerifySignature).Get("/health", h.Health)
142
143 // All public keys on the knot.
144 r.Get("/keys", h.Keys)
145
146 return r, nil
147}
148
149// version is set during build time.
150var version string
151
152func (h *Handle) Version(w http.ResponseWriter, r *http.Request) {
153 if version == "" {
154 info, ok := debug.ReadBuildInfo()
155 if !ok {
156 http.Error(w, "failed to read build info", http.StatusInternalServerError)
157 return
158 }
159
160 var modVer string
161 for _, mod := range info.Deps {
162 if mod.Path == "tangled.sh/tangled.sh/knotserver" {
163 version = mod.Version
164 break
165 }
166 }
167
168 if modVer == "" {
169 version = "unknown"
170 }
171 }
172
173 w.Header().Set("Content-Type", "text/plain")
174 fmt.Fprintf(w, "knotserver/%s", version)
175}