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