From aaf07405133eaacfc94bf87a32f4df3ed431a3b9 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Mon, 21 Apr 2025 12:12:11 -0300 Subject: [PATCH] partial docs update. --- khatru/README.md | 43 ++++++++++------------ khatru/docs/cookbook/custom-live-events.md | 3 +- khatru/docs/cookbook/custom-stores.md | 18 ++++----- khatru/docs/cookbook/search.md | 4 +- khatru/docs/core/auth.md | 4 +- khatru/docs/core/eventstore.md | 37 +++++++++---------- khatru/docs/core/management.md | 18 ++++----- khatru/docs/getting-started/index.md | 17 ++++----- sdk/hints/badgerh/db.go | 2 +- 9 files changed, 65 insertions(+), 81 deletions(-) diff --git a/khatru/README.md b/khatru/README.md index abd7351..ebce9c7 100644 --- a/khatru/README.md +++ b/khatru/README.md @@ -1,8 +1,8 @@ -# khatru, a relay framework [![docs badge](https://img.shields.io/badge/docs-reference-blue)](https://pkg.go.dev/github.com/fiatjaf/khatru#Relay) +# khatru, a relay framework [![docs badge](https://img.shields.io/badge/docs-reference-blue)](https://pkg.go.dev/fiatjaf.com/nostr/khatru#Relay) [![Run Tests](https://github.com/fiatjaf/khatru/actions/workflows/test.yml/badge.svg)](https://github.com/fiatjaf/khatru/actions/workflows/test.yml) -[![Go Reference](https://pkg.go.dev/badge/github.com/fiatjaf/khatru.svg)](https://pkg.go.dev/github.com/fiatjaf/khatru) -[![Go Report Card](https://goreportcard.com/badge/github.com/fiatjaf/khatru)](https://goreportcard.com/report/github.com/fiatjaf/khatru) +[![Go Reference](https://pkg.go.dev/badge/fiatjaf.com/nostr/khatru.svg)](https://pkg.go.dev/fiatjaf.com/nostr/khatru) +[![Go Report Card](https://goreportcard.com/badge/fiatjaf.com/nostr/khatru)](https://goreportcard.com/report/fiatjaf.com/nostr/khatru) Khatru makes it easy to write very very custom relays: @@ -22,8 +22,8 @@ import ( "log" "net/http" - "github.com/fiatjaf/khatru" - "github.com/nbd-wtf/go-nostr" + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/khatru" ) func main() { @@ -32,12 +32,12 @@ func main() { // set up some basic properties (will be returned on the NIP-11 endpoint) relay.Info.Name = "my relay" - relay.Info.PubKey = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + relay.Info.PubKey = nostr.MustPubKeyFromHex("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798") relay.Info.Description = "this is my custom relay" relay.Info.Icon = "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fliquipedia.net%2Fcommons%2Fimages%2F3%2F35%2FSCProbe.jpg&f=1&nofb=1&ipt=0cbbfef25bce41da63d910e86c3c343e6c3b9d63194ca9755351bb7c2efa3359&ipo=images" // you must bring your own storage scheme -- if you want to have any - store := make(map[string]*nostr.Event, 120) + store := make(map[nostr.ID]*nostr.Event, 120) // set up the basic relay functions relay.StoreEvent = append(relay.StoreEvent, @@ -47,22 +47,21 @@ func main() { }, ) relay.QueryEvents = append(relay.QueryEvents, - func(ctx context.Context, filter nostr.Filter) (chan *nostr.Event, error) { - ch := make(chan *nostr.Event) - go func() { + func(ctx context.Context, filter nostr.Filter) (iter.Seq[*nostr.Event], error) { + return func(yield func(*nostr.Event) bool) { for _, evt := range store { if filter.Matches(evt) { - ch <- evt + if !yield(evt) { + break + } } } - close(ch) - }() - return ch, nil + }, nil }, ) relay.DeleteEvent = append(relay.DeleteEvent, - func(ctx context.Context, event *nostr.Event) error { - delete(store, event.ID) + func(ctx context.Context, id nostr.ID) error { + delete(store, id) return nil }, ) @@ -75,7 +74,7 @@ func main() { // define your own policies policies.PreventLargeTags(100), func(ctx context.Context, event *nostr.Event) (reject bool, msg string) { - if event.PubKey == "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52" { + if event.PubKey == nostr.MustPubKeyFromHex("fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52") { return true, "we don't allow this person to write here" } return false, "" // anyone else can @@ -89,7 +88,7 @@ func main() { // define your own policies func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) { - if pubkey := khatru.GetAuthed(ctx); pubkey != "" { + if pubkey, isAuthed := khatru.GetAuthed(ctx); isAuthed { log.Printf("request from %s\n", pubkey) return false, "" } @@ -123,16 +122,12 @@ Fear no more. Using the https://fiatjaf.com/nostr/eventstore module you get a bu panic(err) } - relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent) - relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents) - relay.CountEvents = append(relay.CountEvents, db.CountEvents) - relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent) - relay.ReplaceEvent = append(relay.ReplaceEvent, db.ReplaceEvent) + relay.UseEventstore(db) ``` ### But I don't want to write a bunch of custom policies! -Fear no more. We have a bunch of common policies written in the `github.com/fiatjaf/khatru/policies` package and also a handpicked selection of base sane defaults, which you can apply with: +Fear no more. We have a bunch of common policies written in the `fiatjaf.com/nostr/khatru/policies` package and also a handpicked selection of base sane defaults, which you can apply with: ```go policies.ApplySaneDefaults(relay) diff --git a/khatru/docs/cookbook/custom-live-events.md b/khatru/docs/cookbook/custom-live-events.md index adea1e0..4cd4c3b 100644 --- a/khatru/docs/cookbook/custom-live-events.md +++ b/khatru/docs/cookbook/custom-live-events.md @@ -39,7 +39,7 @@ func startPollingGame(relay *khatru.Relay) { Content: "team A has scored!", Tags: nostr.Tags{{"t", "this-game"}} } - evt.Sign(global.RelayPrivateKey) + evt.Sign(global.RelaySecretKey) // calling BroadcastEvent will send the event to everybody who has been listening for tag "t=[this-game]" // there is no need to do any code to keep track of these clients or who is listening to what, khatru // does that already in the background automatically @@ -61,4 +61,3 @@ func startPollingGame(relay *khatru.Relay) { func fetchGameStatus() (GameStatus, error) { // implementation of calling some external API goes here } -``` diff --git a/khatru/docs/cookbook/custom-stores.md b/khatru/docs/cookbook/custom-stores.md index efce73d..3ae9620 100644 --- a/khatru/docs/cookbook/custom-stores.md +++ b/khatru/docs/cookbook/custom-stores.md @@ -21,23 +21,19 @@ func main () { // other stuff here } -func handleWeatherQuery(ctx context.Context, filter nostr.Filter) (ch chan *nostr.Event, err error) { - if filter.Kind != 10774 { +func handleWeatherQuery(ctx context.Context, filter nostr.Filter) iter.Seq[nostr.Event] { + if filter.Kind != nostr.Kind(10774) { // this function only handles kind 10774, if the query is for something else we return // a nil channel, which corresponds to no results - return nil, nil + return nil } file, err := os.Open("weatherdata.xml") if err != nil { - return nil, fmt.Errorf("we have lost our file: %w", err) + return nil } - // QueryEvents functions are expected to return a channel - ch := make(chan *nostr.Event) - - // and they can do their query asynchronously, emitting events to the channel as they come - go func () { + return func(yield func(nostr.Event) bool) { defer file.Close() // we're going to do this for each tag in the filter @@ -74,14 +70,14 @@ func handleWeatherQuery(ctx context.Context, filter nostr.Filter) (ch chan *nost {"condition", record[3]}, } } - evt.Sign(global.RelayPrivateKey) + evt.Sign(global.RelaySecretKey) ch <- evt } } } }() - return ch, nil + } } ``` diff --git a/khatru/docs/cookbook/search.md b/khatru/docs/cookbook/search.md index 2dad7a0..a999202 100644 --- a/khatru/docs/cookbook/search.md +++ b/khatru/docs/cookbook/search.md @@ -4,7 +4,7 @@ outline: deep # Implementing NIP-50 `search` support -The [`nostr.Filter` type](https://pkg.go.dev/github.com/nbd-wtf/go-nostr#Filter) has a `Search` field, so you basically just has to handle that if it's present. +The [`nostr.Filter` type](https://pkg.go.dev/fiatjaf.com/nostr#Filter) has a `Search` field, so you basically just has to handle that if it's present. It can be tricky to implement fulltext search properly though, so some [eventstores](../core/eventstore) implement it natively, such as [Bluge](https://pkg.go.dev/fiatjaf.com/nostr/eventstore/bluge), [OpenSearch](https://pkg.go.dev/fiatjaf.com/nostr/eventstore/opensearch) and [ElasticSearch](https://pkg.go.dev/fiatjaf.com/nostr/eventstore/elasticsearch) (although for the last two you'll need an instance of these database servers running, while with Bluge it's embedded). @@ -39,7 +39,7 @@ Other adapters, like [SQLite](https://pkg.go.dev/fiatjaf.com/nostr/eventstore/sq ```go relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent, search.SaveEvent) - relay.QueryEvents = append(relay.QueryEvents, func (ctx context.Context, filter nostr.Filter) (chan *nostr.Event, error) { + relay.QueryStored = func (ctx context.Context, filter nostr.Filter) iter.Seq[nostr.Event] { if len(filter.Search) > 0 { return search.QueryEvents(ctx, filter) } else { diff --git a/khatru/docs/core/auth.md b/khatru/docs/core/auth.md index 8f53bd7..e8198c4 100644 --- a/khatru/docs/core/auth.md +++ b/khatru/docs/core/auth.md @@ -41,7 +41,7 @@ After a client is authenticated and opens a new subscription with `REQ` or sends ```go relay.RejectFilter = append(relay.RejectFilter, func(ctx context.Context, filter nostr.Filter) (bool, string) { - authenticatedUser := khatru.GetAuthed(ctx) + authenticatedUser, isAuthenticated := khatru.GetAuthed(ctx) }) ``` @@ -77,7 +77,7 @@ You can use that to emulate a listener for these events in case you want to keep case <-ctx.Done(): fmt.Println("connection closed") case <-conn.Authed: - fmt.Println("authenticated as", conn.AuthedPublicKey) + fmt.Println("authenticated as", conn.AuthedPubKey) } }(ctx) }, diff --git a/khatru/docs/core/eventstore.md b/khatru/docs/core/eventstore.md index 04863a9..a5bf20f 100644 --- a/khatru/docs/core/eventstore.md +++ b/khatru/docs/core/eventstore.md @@ -24,7 +24,7 @@ import ( "net/http" "fiatjaf.com/nostr/eventstore/badger" - "github.com/fiatjaf/khatru" + "fiatjaf.com/nostr/khatru" ) func main() { @@ -35,11 +35,7 @@ func main() { panic(err) } - relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent) - relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents) - relay.CountEvents = append(relay.CountEvents, db.CountEvents) - relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent) - relay.ReplaceEvent = append(relay.ReplaceEvent, db.ReplaceEvent) + relay.UseEventstore(db) fmt.Println("running on :3334") http.ListenAndServe(":3334", relay) @@ -58,7 +54,7 @@ If you want to use two different adapters at the same time that's easy. Just add ```go relay.StoreEvent = append(relay.StoreEvent, db1.SaveEvent, db2.SaveEvent) - relay.QueryEvents = append(relay.QueryEvents, db1.QueryEvents, db2.SaveEvent) + relay.QueryEvents = append(relay.QueryEvents, db1.QueryEvents, db2.QueryEvents) ``` But that will duplicate events on both and then return duplicated events on each query. @@ -72,28 +68,29 @@ For example, maybe you want kind 1 events in `db1` and kind 30023 events in `db3 ```go relay.StoreEvent = append(relay.StoreEvent, func (ctx context.Context, evt *nostr.Event) error { switch evt.Kind { - case 1: - return db1.StoreEvent(ctx, evt) - case 30023: - return db30023.StoreEvent(ctx, evt) + case nostr.Kind(1): + return db1.SaveEvent(evt) + case nostr.Kind(30023): + return db30023.SaveEvent(evt) default: return nil } }) - relay.QueryEvents = append(relay.QueryEvents, func (ctx context.Context, filter nostr.Filter) (chan *nostr.Event, error) { + relay.QueryEvents = append(relay.QueryEvents, func (ctx context.Context, filter nostr.Filter) iter.Seq[nostr.Event] { for _, kind := range filter.Kinds { - switch kind { - case 1: + switch nostr.Kind(kind) { + case nostr.Kind(1): filter1 := filter - filter1.Kinds = []int{1} - return db1.QueryEvents(ctx, filter1) - case 30023: + filter1.Kinds = []nostr.Kind{1} + return db1.QueryEvents(filter1) + case nostr.Kind(30023): filter30023 := filter - filter30023.Kinds = []int{30023} - return db30023.QueryEvents(ctx, filter30023) + filter30023.Kinds = []nostr.Kind{30023} + return db30023.QueryEvents(filter30023) default: - return nil, nil + return nil } } + return nil }) ``` diff --git a/khatru/docs/core/management.md b/khatru/docs/core/management.md index 3af9fc2..7f944f8 100644 --- a/khatru/docs/core/management.md +++ b/khatru/docs/core/management.md @@ -6,15 +6,15 @@ outline: deep [NIP-86](https://nips.nostr.com/86) specifies a set of RPC methods for managing the boring aspects of relays, such as whitelisting or banning users, banning individual events, banning IPs and so on. -All [`khatru.Relay`](https://pkg.go.dev/github.com/fiatjaf/khatru#Relay) instances expose a field `ManagementAPI` with a [`RelayManagementAPI`](https://pkg.go.dev/github.com/fiatjaf/khatru#RelayManagementAPI) instance inside, which can be used for creating handlers for each of the RPC methods. +All [`khatru.Relay`](https://pkg.go.dev/fiatjaf.com/nostr/khatru#Relay) instances expose a field `ManagementAPI` with a [`RelayManagementAPI`](https://pkg.go.dev/fiatjaf.com/nostr/khatru#RelayManagementAPI) instance inside, which can be used for creating handlers for each of the RPC methods. There is also a generic `RejectAPICall` which is a slice of functions that will be called before any RPC method, if they exist and, if any of them returns true, the request will be rejected. The most basic implementation of a `RejectAPICall` handler would be one that checks the public key of the caller with a hardcoded public key of the relay owner: ```go -var owner = "" -var allowedPubkeys = make([]string, 0, 10) +var owner = nostr.MustPubKeyFromHex("") +var allowedPubkeys = make([]nostr.PubKey, 0, 10) func main () { relay := khatru.NewRelay() @@ -29,16 +29,17 @@ func main () { } ) - relay.ManagementAPI.AllowPubKey = func(ctx context.Context, pubkey string, reason string) error { + relay.ManagementAPI.AllowPubKey = func(ctx context.Context, pubkey nostr.PubKey, reason string) error { allowedPubkeys = append(allowedPubkeys, pubkey) return nil } - relay.ManagementAPI.BanPubKey = func(ctx context.Context, pubkey string, reason string) error { + relay.ManagementAPI.BanPubKey = func(ctx context.Context, pubkey nostr.PubKey, reason string) error { idx := slices.Index(allowedPubkeys, pubkey) if idx == -1 { return fmt.Errorf("pubkey already not allowed") } allowedPubkeys = slices.Delete(allowedPubkeys, idx, idx+1) + return nil } } ``` @@ -48,12 +49,12 @@ You can also not provide any `RejectAPICall` handler and do the approval specifi In the following example any current member can include any other pubkey, and anyone who was added before is able to remove any pubkey that was added afterwards (not a very good idea, but serves as an example). ```go -var allowedPubkeys = []string{""} +var allowedPubkeys = []nostr.PubKey{nostr.MustPubKeyFromHex("")} func main () { relay := khatru.NewRelay() - relay.ManagementAPI.AllowPubKey = func(ctx context.Context, pubkey string, reason string) error { + relay.ManagementAPI.AllowPubKey = func(ctx context.Context, pubkey nostr.PubKey, reason string) error { caller := khatru.GetAuthed(ctx) if slices.Contains(allowedPubkeys, caller) { @@ -63,7 +64,7 @@ func main () { return fmt.Errorf("you're not authorized") } - relay.ManagementAPI.BanPubKey = func(ctx context.Context, pubkey string, reason string) error { + relay.ManagementAPI.BanPubKey = func(ctx context.Context, pubkey nostr.PubKey, reason string) error { caller := khatru.GetAuthed(ctx) callerIdx := slices.Index(allowedPubkeys, caller) @@ -82,4 +83,3 @@ func main () { return nil } } -``` diff --git a/khatru/docs/getting-started/index.md b/khatru/docs/getting-started/index.md index eb1ea94..7cee8b4 100644 --- a/khatru/docs/getting-started/index.md +++ b/khatru/docs/getting-started/index.md @@ -7,13 +7,13 @@ outline: deep Download the library: ```bash -go get github.com/fiatjaf/khatru +go get fiatjaf.com/nostr/khatru ``` Include the library: ```go -import "github.com/fiatjaf/khatru" +import "fiatjaf.com/nostr/khatru" ``` Then in your `main()` function, instantiate a new `Relay`: @@ -26,7 +26,7 @@ Optionally, set up basic info about the relay that will be returned according to ```go relay.Info.Name = "my relay" -relay.Info.PubKey = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" +relay.Info.PubKey = nostr.MustPubKeyFromHex("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798") relay.Info.Description = "this is my custom relay" relay.Info.Icon = "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fliquipedia.net%2Fcommons%2Fimages%2F3%2F35%2FSCProbe.jpg&f=1&nofb=1&ipt=0cbbfef25bce41da63d910e86c3c343e6c3b9d63194ca9755351bb7c2efa3359&ipo=images" ``` @@ -39,10 +39,7 @@ if err := db.Init(); err != nil { panic(err) } -relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent) -relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents) -relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent) -relay.ReplaceEvent = append(relay.ReplaceEvent, db.ReplaceEvent) +relay.UseEventstore(db) ``` These are lists of functions that will be called in order every time an `EVENT` is received, or a `REQ` query is received. You can add more than one handler there, you can have a function that reads from some other server, but just in some cases, you can do anything. @@ -51,7 +48,7 @@ The next step is adding some protection, because maybe we don't want to allow _a ```go relay.RejectEvent = append(relay.RejectEvent, func (ctx context.Context, event *nostr.Event) (reject bool, msg string) { - firstHexChar := event.PubKey[0:1] + firstHexChar := event.PubKey.Hex()[0:1] if firstHexChar == "a" || firstHexChar == "b" || firstHexChar == "c" { return false, "" // allow } @@ -62,12 +59,12 @@ relay.RejectEvent = append(relay.RejectEvent, func (ctx context.Context, event * We can also make use of some default policies that come bundled with Khatru: ```go -import "github.com/fiatjaf/khatru" // implied +import "fiatjaf.com/nostr/khatru/policies" // implied relay.RejectEvent = append(relay.RejectEvent, policies.PreventLargeTags(120), policies.PreventTimestampsInThePast(time.Hour * 2), policies.PreventTimestampsInTheFuture(time.Minute * 30)) ``` -There are many other ways to customize the relay behavior. Take a look at the [`Relay` struct docs](https://pkg.go.dev/github.com/fiatjaf/khatru#Relay) for more, or read the pages on the sidebar. +There are many other ways to customize the relay behavior. Take a look at the [`Relay` struct docs](https://pkg.go.dev/fiatjaf.com/nostr/khatru#Relay) for more, or read the pages on the sidebar. The last step is actually running the server. Our relay is actually an `http.Handler`, so it can just be ran directly with `http.ListenAndServe()` from the standard library: diff --git a/sdk/hints/badgerh/db.go b/sdk/hints/badgerh/db.go index 809dbfb..7e35797 100644 --- a/sdk/hints/badgerh/db.go +++ b/sdk/hints/badgerh/db.go @@ -21,7 +21,7 @@ func NewBadgerHints(path string) (*BadgerHints, error) { opts.Logger = nil db, err := badger.Open(opts) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to open badger db: %w", err) } return &BadgerHints{db: db}, nil }