1package knotserver
2
3import (
4 "context"
5 "fmt"
6 "log/slog"
7 "net/http"
8
9 "github.com/go-chi/chi/v5"
10 "github.com/sotangled/tangled/jetstream"
11 "github.com/sotangled/tangled/knotserver/config"
12 "github.com/sotangled/tangled/knotserver/db"
13 "github.com/sotangled/tangled/rbac"
14)
15
16const (
17 ThisServer = "thisserver" // resource identifier for rbac enforcement
18)
19
20type Handle struct {
21 c *config.Config
22 db *db.DB
23 jc *jetstream.JetstreamClient
24 e *rbac.Enforcer
25 l *slog.Logger
26
27 // init is a channel that is closed when the knot has been initailized
28 // i.e. when the first user (knot owner) has been added.
29 init chan struct{}
30 knotInitialized bool
31}
32
33func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, jc *jetstream.JetstreamClient, l *slog.Logger) (http.Handler, error) {
34 r := chi.NewRouter()
35
36 h := Handle{
37 c: c,
38 db: db,
39 e: e,
40 l: l,
41 jc: jc,
42 init: make(chan struct{}),
43 }
44
45 err := e.AddDomain(ThisServer)
46 if err != nil {
47 return nil, fmt.Errorf("failed to setup enforcer: %w", err)
48 }
49
50 err = h.jc.StartJetstream(ctx, h.processMessages)
51 if err != nil {
52 return nil, fmt.Errorf("failed to start jetstream: %w", err)
53 }
54
55 // Check if the knot knows about any Dids;
56 // if it does, it is already initialized and we can repopulate the
57 // Jetstream subscriptions.
58 dids, err := db.GetAllDids()
59 if err != nil {
60 return nil, fmt.Errorf("failed to get all Dids: %w", err)
61 }
62 if len(dids) > 0 {
63 h.knotInitialized = true
64 close(h.init)
65 h.jc.UpdateDids(dids)
66 }
67
68 r.Get("/", h.Index)
69 r.Route("/{did}", func(r chi.Router) {
70 // Repo routes
71 r.Route("/{name}", func(r chi.Router) {
72 r.Post("/collaborator/add", h.AddRepoCollaborator)
73
74 r.Get("/", h.RepoIndex)
75 r.Get("/info/refs", h.InfoRefs)
76 r.Post("/git-upload-pack", h.UploadPack)
77
78 r.Route("/merge", func(r chi.Router) {
79 r.Post("/", h.Merge)
80 r.Post("/check", h.MergeCheck)
81 })
82
83 r.Route("/tree/{ref}", func(r chi.Router) {
84 r.Get("/", h.RepoIndex)
85 r.Get("/*", h.RepoTree)
86 })
87
88 r.Route("/blob/{ref}", func(r chi.Router) {
89 r.Get("/*", h.Blob)
90 })
91
92 r.Get("/log/{ref}", h.Log)
93 r.Get("/archive/{file}", h.Archive)
94 r.Get("/commit/{ref}", h.Diff)
95 r.Get("/tags", h.Tags)
96 r.Get("/branches", h.Branches)
97 })
98 })
99
100 // Create a new repository.
101 r.Route("/repo", func(r chi.Router) {
102 r.Use(h.VerifySignature)
103 r.Put("/new", h.NewRepo)
104 r.Delete("/", h.RemoveRepo)
105 })
106
107 r.Route("/member", func(r chi.Router) {
108 r.Use(h.VerifySignature)
109 r.Put("/add", h.AddMember)
110 })
111
112 // Initialize the knot with an owner and public key.
113 r.With(h.VerifySignature).Post("/init", h.Init)
114
115 // Health check. Used for two-way verification with appview.
116 r.With(h.VerifySignature).Get("/health", h.Health)
117
118 // All public keys on the knot.
119 r.Get("/keys", h.Keys)
120
121 return r, nil
122}