this repo has no description

knotserver: /init and /user endpoints

When the knot is initialized, the h.init channel is closed signalling
the Jetstream watcher to begin, with the owner DID in filter. There's no
null filter so starting it without any DIDs will give us events for all
DIDs. /init also checks if h.knotInitialized is true, and will 409
Conflict if /init is called again.

PUT /user is similar, but will only trigger a call to UpdateDids.

Changed files
+118 -10
knotserver
+1 -2
flake.nix
··· 45 nativeBuildInputs = [ 46 pkgs.go 47 pkgs.air 48 - pkgs.templ 49 pkgs.gopls 50 pkgs.httpie 51 pkgs.indigo-lexgen ··· 54 ]; 55 }; 56 }); 57 - apps = forAllSystems (system: 58 let 59 pkgs = nixpkgsFor."${system}"; 60 air-watcher = name: pkgs.writeShellScriptBin "run"
··· 45 nativeBuildInputs = [ 46 pkgs.go 47 pkgs.air 48 pkgs.gopls 49 pkgs.httpie 50 pkgs.indigo-lexgen ··· 53 ]; 54 }; 55 }); 56 + apps = forAllSystems (system: 57 let 58 pkgs = nixpkgsFor."${system}"; 59 air-watcher = name: pkgs.writeShellScriptBin "run"
+6
knotserver/db/init.go
··· 25 created timestamp default current_timestamp, 26 unique(did, name, key) 27 ); 28 create table if not exists repos ( 29 id integer primary key autoincrement, 30 did text not null,
··· 25 created timestamp default current_timestamp, 26 unique(did, name, key) 27 ); 28 + create table if not exists users ( 29 + id integer primary key autoincrement, 30 + did text not null, 31 + unique(did), 32 + foreign key (did) references public_keys(did) on delete cascade 33 + ); 34 create table if not exists repos ( 35 id integer primary key autoincrement, 36 did text not null,
+2 -2
knotserver/db/pubkeys.go
··· 11 tangled.PublicKey 12 } 13 14 - func (d *DB) AddPublicKeyFromRecord(recordIface map[string]interface{}) error { 15 record := make(map[string]string) 16 for k, v := range recordIface { 17 if str, ok := v.(string); ok { ··· 20 } 21 22 pk := PublicKey{ 23 - Did: record["did"], 24 } 25 pk.Name = record["name"] 26 pk.Key = record["key"]
··· 11 tangled.PublicKey 12 } 13 14 + func (d *DB) AddPublicKeyFromRecord(did string, recordIface map[string]interface{}) error { 15 record := make(map[string]string) 16 for k, v := range recordIface { 17 if str, ok := v.(string); ok { ··· 20 } 21 22 pk := PublicKey{ 23 + Did: did, 24 } 25 pk.Name = record["name"] 26 pk.Key = record["key"]
+11
knotserver/db/users.go
···
··· 1 + package db 2 + 3 + func (d *DB) AddUser(did string) error { 4 + _, err := d.db.Exec(`insert into users (did) values (?)`, did) 5 + return err 6 + } 7 + 8 + func (d *DB) RemoveUser(did string) error { 9 + _, err := d.db.Exec(`delete from users where did = ?`, did) 10 + return err 11 + }
+21 -4
knotserver/handler.go
··· 18 c *config.Config 19 db *db.DB 20 js *jsclient.JetstreamClient 21 } 22 23 func Setup(ctx context.Context, c *config.Config, db *db.DB) (http.Handler, error) { 24 r := chi.NewRouter() 25 26 h := Handle{ 27 - c: c, 28 - db: db, 29 } 30 31 err := h.StartJetstream(ctx) ··· 33 return nil, fmt.Errorf("failed to start jetstream: %w", err) 34 } 35 36 r.Get("/", h.Index) 37 r.Route("/{did}", func(r chi.Router) { 38 // Repo routes ··· 63 }) 64 65 // Add a new user to the knot 66 - // r.With(h.VerifySignature).Put("/user", h.AddUser) 67 68 // Health check. Used for two-way verification with appview. 69 r.With(h.VerifySignature).Get("/health", h.Health) ··· 85 } 86 87 go func() { 88 for msg := range messages { 89 var data map[string]interface{} 90 if err := json.Unmarshal(msg, &data); err != nil { ··· 97 98 switch commit["collection"].(string) { 99 case tangled.PublicKeyNSID: 100 record := commit["record"].(map[string]interface{}) 101 - if err := h.db.AddPublicKeyFromRecord(record); err != nil { 102 log.Printf("failed to add public key: %v", err) 103 } 104 log.Printf("added public key from firehose: %s", data["did"])
··· 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 28 func 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) ··· 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 ··· 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) ··· 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 { ··· 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"])
-2
knotserver/jsclient/jetstream.go
··· 100 queryParams := j.buildQueryParams(cursor) 101 u := j.buildWebsocketURL(queryParams) 102 103 - log.Printf("connecting to jetstream at: %s", u.String()) 104 - 105 dialer := websocket.Dialer{ 106 HandshakeTimeout: 10 * time.Second, 107 }
··· 100 queryParams := j.buildQueryParams(cursor) 101 u := j.buildWebsocketURL(queryParams) 102 103 dialer := websocket.Dialer{ 104 HandshakeTimeout: 10 * time.Second, 105 }
+77
knotserver/routes.go
··· 391 w.WriteHeader(http.StatusNoContent) 392 } 393 394 func (h *Handle) Health(w http.ResponseWriter, r *http.Request) { 395 log.Println("got health check") 396 mac := hmac.New(sha256.New, []byte(h.c.Server.Secret))
··· 391 w.WriteHeader(http.StatusNoContent) 392 } 393 394 + func (h *Handle) AddUser(w http.ResponseWriter, r *http.Request) { 395 + data := struct { 396 + DID string `json:"did"` 397 + PublicKey string `json:"pubkey"` 398 + }{} 399 + 400 + if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 401 + writeError(w, "invalid request body", http.StatusBadRequest) 402 + return 403 + } 404 + 405 + did := data.DID 406 + key := data.PublicKey 407 + 408 + if err := h.db.AddUser(did); err == nil { 409 + pk := db.PublicKey{ 410 + Did: did, 411 + } 412 + pk.Key = key 413 + pk.Name = "default" 414 + err := h.db.AddPublicKey(pk) 415 + if err != nil { 416 + writeError(w, err.Error(), http.StatusInternalServerError) 417 + return 418 + } 419 + } else { 420 + writeError(w, err.Error(), http.StatusInternalServerError) 421 + return 422 + } 423 + 424 + h.js.UpdateDids([]string{did}) 425 + 426 + w.WriteHeader(http.StatusNoContent) 427 + } 428 + 429 + // TODO: make this set the initial user as the owner 430 + func (h *Handle) Init(w http.ResponseWriter, r *http.Request) { 431 + if h.knotInitialized { 432 + writeError(w, "knot already initialized", http.StatusConflict) 433 + return 434 + } 435 + 436 + data := struct { 437 + DID string `json:"did"` 438 + PublicKey string `json:"pubkey"` 439 + }{} 440 + 441 + if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 442 + writeError(w, "invalid request body", http.StatusBadRequest) 443 + return 444 + } 445 + 446 + did := data.DID 447 + key := data.PublicKey 448 + 449 + if err := h.db.AddUser(did); err == nil { 450 + pk := db.PublicKey{ 451 + Did: did, 452 + } 453 + pk.Key = key 454 + pk.Name = "default" 455 + err := h.db.AddPublicKey(pk) 456 + if err != nil { 457 + writeError(w, err.Error(), http.StatusInternalServerError) 458 + return 459 + } 460 + } else { 461 + writeError(w, err.Error(), http.StatusInternalServerError) 462 + return 463 + } 464 + 465 + h.js.UpdateDids([]string{did}) 466 + // Signal that the knot is ready 467 + close(h.init) 468 + w.WriteHeader(http.StatusNoContent) 469 + } 470 + 471 func (h *Handle) Health(w http.ResponseWriter, r *http.Request) { 472 log.Println("got health check") 473 mac := hmac.New(sha256.New, []byte(h.c.Server.Secret))