this repo has no description
1package knotserver
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "log"
8 "net/http"
9
10 "github.com/go-chi/chi/v5"
11 tangled "github.com/sotangled/tangled/api/tangled"
12 "github.com/sotangled/tangled/knotserver/config"
13 "github.com/sotangled/tangled/knotserver/db"
14 "github.com/sotangled/tangled/knotserver/jsclient"
15)
16
17type Handle struct {
18 c *config.Config
19 db *db.DB
20 js *jsclient.JetstreamClient
21
22 // init is a channel that is closed when the knot has been initailized
23 // i.e. when the first user (knot owner) has been added.
24 init chan struct{}
25 knotInitialized bool
26}
27
28func Setup(ctx context.Context, c *config.Config, db *db.DB) (http.Handler, error) {
29 r := chi.NewRouter()
30
31 h := Handle{
32 c: c,
33 db: db,
34 init: make(chan struct{}),
35 }
36
37 err := h.StartJetstream(ctx)
38 if err != nil {
39 return nil, fmt.Errorf("failed to start jetstream: %w", err)
40 }
41
42 // TODO: close this channel and set h.knotInitialized *only after*
43 // checking if we have an owner.
44 close(h.init)
45 h.knotInitialized = true
46
47 r.Get("/", h.Index)
48 r.Route("/{did}", func(r chi.Router) {
49 // Repo routes
50 r.Route("/{name}", func(r chi.Router) {
51 r.Get("/", h.RepoIndex)
52 r.Get("/info/refs", h.InfoRefs)
53 r.Post("/git-upload-pack", h.UploadPack)
54
55 r.Route("/tree/{ref}", func(r chi.Router) {
56 r.Get("/*", h.RepoTree)
57 })
58
59 r.Route("/blob/{ref}", func(r chi.Router) {
60 r.Get("/*", h.FileContent)
61 })
62
63 r.Get("/log/{ref}", h.Log)
64 r.Get("/archive/{file}", h.Archive)
65 r.Get("/commit/{ref}", h.Diff)
66 r.Get("/refs/", h.Refs)
67 })
68 })
69
70 // Create a new repository
71 r.Route("/repo", func(r chi.Router) {
72 r.Use(h.VerifySignature)
73 r.Put("/new", h.NewRepo)
74 })
75
76 // Add a new user to the knot
77 r.With(h.VerifySignature).Put("/user", h.AddUser)
78 r.With(h.VerifySignature).Post("/init", h.Init)
79
80 // Health check. Used for two-way verification with appview.
81 r.With(h.VerifySignature).Get("/health", h.Health)
82
83 // All public keys on the knot
84 r.Get("/keys", h.Keys)
85
86 return r, nil
87}
88
89func (h *Handle) StartJetstream(ctx context.Context) error {
90 colections := []string{tangled.PublicKeyNSID}
91 dids := []string{}
92
93 h.js = jsclient.NewJetstreamClient(colections, dids)
94 messages, err := h.js.ReadJetstream(ctx)
95 if err != nil {
96 return fmt.Errorf("failed to read from jetstream: %w", err)
97 }
98
99 go func() {
100 log.Println("waiting for knot to be initialized")
101 <-h.init
102 log.Println("initalized jetstream watcher")
103
104 for msg := range messages {
105 var data map[string]interface{}
106 if err := json.Unmarshal(msg, &data); err != nil {
107 log.Printf("error unmarshaling message: %v", err)
108 continue
109 }
110
111 if kind, ok := data["kind"].(string); ok && kind == "commit" {
112 commit := data["commit"].(map[string]interface{})
113
114 switch commit["collection"].(string) {
115 case tangled.PublicKeyNSID:
116 did := data["did"].(string)
117 record := commit["record"].(map[string]interface{})
118 if err := h.db.AddPublicKeyFromRecord(did, record); err != nil {
119 log.Printf("failed to add public key: %v", err)
120 }
121 log.Printf("added public key from firehose: %s", data["did"])
122 default:
123 }
124 }
125
126 }
127 }()
128
129 return nil
130}