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}