diff --git a/.gitignore b/.gitignore index 40c21bc..d301b32 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,4 @@ pkg/database/testrealy /.idea/material_theme_project_new.xml /.idea/orly.iml /.idea/go.imports.xml +/.idea/inspectionProfiles/Project_Default.xml diff --git a/pkg/encoders/event/event.go b/pkg/encoders/event/event.go index 3eabb5d..0c9d694 100644 --- a/pkg/encoders/event/event.go +++ b/pkg/encoders/event/event.go @@ -14,7 +14,6 @@ import ( "orly.dev/pkg/encoders/tags" "orly.dev/pkg/encoders/text" "orly.dev/pkg/encoders/timestamp" - "orly.dev/pkg/encoders/unix" "orly.dev/pkg/interfaces/signer" "orly.dev/pkg/utils/chk" "orly.dev/pkg/utils/errorf" @@ -92,6 +91,9 @@ func (ev *E) Id() []byte { return ev.ID } // CreatedAtInt64 returns the created_at timestamp as a standard int64. func (ev *E) CreatedAtInt64() (i int64) { return ev.CreatedAt.I64() } +// KindInt returns the kind as an int, as is often needed for JSON. +func (ev *E) KindInt() (i int) { return int(ev.Kind.K) } + // KindInt32 returns the kind as an int32, as is often needed for JSON. func (ev *E) KindInt32() (i int32) { return int32(ev.Kind.K) } @@ -109,13 +111,13 @@ func (ev *E) ContentString() (s string) { return string(ev.Content) } // J is an event.E encoded in more basic types than used in this library. type J struct { - Id string `json:"id"` - Pubkey string `json:"pubkey"` - CreatedAt unix.Time `json:"created_at"` - Kind int32 `json:"kind"` - Tags [][]string `json:"tags"` - Content string `json:"content"` - Sig string `json:"sig"` + Id string `json:"id" doc:"event id (SHA256 hash of canonical form of event, 64 characters hex)"` + Pubkey string `json:"pubkey" doc:"public key of author of event, required to verify signature (BIP-340 Schnorr public key, 64 characters hex)"` + CreatedAt int64 `json:"created_at" doc:"unix timestamp of time when event was created"` + Kind int `json:"kind" doc:"kind number of event"` + Tags [][]string `json:"tags" doc:"tags that add metadata to the event"` + Content string `json:"content" doc:"content of event"` + Sig string `json:"sig" doc:"signature of event (BIP-340 schnorr signature, 128 characters hex)"` } // ToEventJ converts an event.E into an event.J. @@ -123,8 +125,8 @@ func (ev *E) ToEventJ() (j *J) { j = &J{} j.Id = ev.IdString() j.Pubkey = ev.PubKeyString() - j.CreatedAt = unix.Time{ev.CreatedAt.Time()} - j.Kind = ev.KindInt32() + j.CreatedAt = ev.CreatedAt.I64() + j.Kind = ev.KindInt() j.Content = ev.ContentString() j.Tags = ev.Tags.ToStringsSlice() j.Sig = ev.SigString() @@ -151,6 +153,13 @@ func (ev *E) KindFromInt32(i int32) { return } +// KindFromInt encodes an int representation of a kind.T into an event.E. +func (ev *E) KindFromInt(i int) { + ev.Kind = &kind.T{} + ev.Kind.K = uint16(i) + return +} + // PubKeyFromString decodes a hex-encoded string into the event.E Pubkey field. func (ev *E) PubKeyFromString(s string) (err error) { if len(s) != 2*schnorr.PubKeyBytesLen { @@ -200,8 +209,8 @@ func (e J) ToEvent() (ev *E, err error) { if err = ev.IdFromString(e.Id); chk.E(err) { return } - ev.CreatedAtFromInt64(e.CreatedAt.Unix()) - ev.KindFromInt32(e.Kind) + ev.CreatedAtFromInt64(e.CreatedAt) + ev.KindFromInt(e.Kind) if err = ev.PubKeyFromString(e.Pubkey); chk.E(err) { return } diff --git a/pkg/protocol/openapi/event.go b/pkg/protocol/openapi/event.go index a8a1601..00c1e42 100644 --- a/pkg/protocol/openapi/event.go +++ b/pkg/protocol/openapi/event.go @@ -19,15 +19,62 @@ import ( "orly.dev/pkg/utils/log" ) +var EventBody = &huma.RequestBody{ + Description: "a signed nostr event", + Content: map[string]*huma.MediaType{ + "application/json": { + Schema: &huma.Schema{ + Type: huma.TypeObject, + Properties: map[string]*huma.Schema{ + "id": { + Type: huma.TypeString, + Description: "SHA256 hash of event in canonical (JSON array with fixed ordering) form, 64 characters hex", + }, + "pubkey": { + Type: huma.TypeString, + Description: "BIP-340 Schnorr public key, 64 characters hex", + }, + "created_at": { + Type: huma.TypeInteger, + Description: "unix timestamp of time of event creation", + }, + "kind": { + Type: huma.TypeInteger, + Description: "kind number of event", + }, + "tags": { + Type: huma.TypeArray, + Description: "array of arrays of strings", + Items: &huma.Schema{ + Type: huma.TypeArray, + Items: &huma.Schema{ + Type: huma.TypeString, + }, + }, + }, + "content": { + Type: huma.TypeString, + Description: "content of event, escaped using NIP-01 standard escapes and UTF-8 encoding", + }, + "sig": { + Type: huma.TypeString, + Description: "BIP-340 Schnorr signature, 128 characters hex", + }, + }, + }, + }, + }, +} + // EventInput is the parameters for the Event HTTP API method. type EventInput struct { - Auth string `header:"Authorization" doc:"nostr nip-98 (and expiring variant)" required:"false"` - Accept string `header:"Accept" default:"application/nostr+json"` - Body string `doc:"event JSON"` + Auth string `header:"Authorization" doc:"nostr nip-98 (and expiring variant)" required:"false"` + Accept string `header:"Accept" default:"application/nostr+json"` + Body *event.J `doc:"event JSON"` } // EventOutput is the return parameters for the HTTP API Event method. -type EventOutput struct{ Body string } +type EventOutput struct{} // RegisterEvent is the implementation of the HTTP API Event method. func (x *Operations) RegisterEvent(api huma.API) { @@ -43,6 +90,7 @@ func (x *Operations) RegisterEvent(api huma.API) { Path: path, Method: method, Tags: []string{"events"}, + RequestBody: EventBody, Description: helpers.GenerateDescription(description, scopes), Security: []map[string][]string{{"auth": scopes}}, }, func(ctx context.T, input *EventInput) ( @@ -63,17 +111,13 @@ func (x *Operations) RegisterEvent(api huma.API) { return } } - ev := &event.E{} - var rem []byte - if rem, err = ev.Unmarshal([]byte(input.Body)); chk.T(err) { + var ev *event.E + if ev, err = input.Body.ToEvent(); chk.E(err) { err = huma.Error422UnprocessableEntity( - "Failed to parse event", err, + "Failed to convert event", err, ) return } - if len(rem) > 0 { - log.D.F("remainder:\n%s", rem) - } // these aliases make it so most of the following code can be copied // verbatim from its counterpart in socketapi.HandleEvent, with the // aid of a different implementation of the openapi.OK type. @@ -393,7 +437,6 @@ func (x *Operations) RegisterEvent(api huma.API) { return } } - output = &EventOutput{"event accepted"} return }, ) diff --git a/pkg/protocol/openapi/events.go b/pkg/protocol/openapi/events.go index 54d044c..1dd4525 100644 --- a/pkg/protocol/openapi/events.go +++ b/pkg/protocol/openapi/events.go @@ -4,25 +4,583 @@ import ( "errors" "github.com/danielgtaylor/huma/v2" "github.com/dgraph-io/badger/v4" + "math" "net/http" "orly.dev/pkg/app/relay/helpers" "orly.dev/pkg/encoders/event" "orly.dev/pkg/encoders/filter" "orly.dev/pkg/encoders/filters" + "orly.dev/pkg/encoders/kind" + "orly.dev/pkg/encoders/tag" + "orly.dev/pkg/encoders/timestamp" "orly.dev/pkg/protocol/auth" "orly.dev/pkg/utils/context" "orly.dev/pkg/utils/log" "orly.dev/pkg/utils/pointers" ) +type Filter struct { + Ids []string `json:"ids,omitempty"` + Kinds []int `json:"kinds,omitempty"` + Authors []string `json:"authors,omitempty"` + Tag_a []string `json:"#a,omitempty"` + Tag_b []string `json:"#b,omitempty"` + Tag_c []string `json:"#c,omitempty"` + Tag_d []string `json:"#d,omitempty"` + Tag_e []string `json:"#e,omitempty"` + Tag_f []string `json:"#f,omitempty"` + Tag_g []string `json:"#g,omitempty"` + Tag_h []string `json:"#h,omitempty"` + Tag_i []string `json:"#i,omitempty"` + Tag_j []string `json:"#j,omitempty"` + Tag_k []string `json:"#k,omitempty"` + Tag_l []string `json:"#l,omitempty"` + Tag_m []string `json:"#m,omitempty"` + Tag_n []string `json:"#n,omitempty"` + Tag_o []string `json:"#o,omitempty"` + Tag_p []string `json:"#p,omitempty"` + Tag_q []string `json:"#q,omitempty"` + Tag_r []string `json:"#r,omitempty"` + Tag_s []string `json:"#s,omitempty"` + Tag_t []string `json:"#t,omitempty"` + Tag_u []string `json:"#u,omitempty"` + Tag_v []string `json:"#v,omitempty"` + Tag_w []string `json:"#w,omitempty"` + Tag_x []string `json:"#x,omitempty"` + Tag_y []string `json:"#y,omitempty"` + Tag_z []string `json:"#z,omitempty"` + Tag_A []string `json:"#A,omitempty"` + Tag_B []string `json:"#B,omitempty"` + Tag_C []string `json:"#C,omitempty"` + Tag_D []string `json:"#D,omitempty"` + Tag_E []string `json:"#E,omitempty"` + Tag_F []string `json:"#F,omitempty"` + Tag_G []string `json:"#G,omitempty"` + Tag_H []string `json:"#H,omitempty"` + Tag_I []string `json:"#I,omitempty"` + Tag_J []string `json:"#J,omitempty"` + Tag_K []string `json:"#K,omitempty"` + Tag_L []string `json:"#L,omitempty"` + Tag_M []string `json:"#M,omitempty"` + Tag_N []string `json:"#N,omitempty"` + Tag_O []string `json:"#O,omitempty"` + Tag_P []string `json:"#P,omitempty"` + Tag_Q []string `json:"#Q,omitempty"` + Tag_R []string `json:"#R,omitempty"` + Tag_S []string `json:"#S,omitempty"` + Tag_T []string `json:"#T,omitempty"` + Tag_U []string `json:"#U,omitempty"` + Tag_V []string `json:"#V,omitempty"` + Tag_W []string `json:"#W,omitempty"` + Tag_X []string `json:"#X,omitempty"` + Tag_Y []string `json:"#Y,omitempty"` + Tag_Z []string `json:"#Z,omitempty"` + Since *int64 `json:"since,omitempty"` + Until *int64 `json:"until,omitempty"` + Search *string `json:"search,omitempty"` + Limit *int `json:"limit,omitempty"` +} + +func (f *Filter) ToFilter() (ff *filter.F) { + ff = filter.New() + + // Convert Ids + if f.Ids != nil && len(f.Ids) > 0 { + for _, id := range f.Ids { + ff.Ids.Append([]byte(id)) + } + } + if f.Kinds != nil && len(f.Kinds) > 0 { + for _, k := range f.Kinds { + ff.Kinds.K = append(ff.Kinds.K, kind.New(uint16(k))) + } + } + if f.Authors != nil && len(f.Authors) > 0 { + for _, author := range f.Authors { + ff.Authors.Append([]byte(author)) + } + } + if f.Since != nil { + ts := timestamp.New(*f.Since) + ff.Since = ts + } + if f.Until != nil { + ts := timestamp.New(*f.Until) + ff.Until = ts + } + if f.Search != nil { + ff.Search = []byte(*f.Search) + } + if f.Limit != nil { + u := uint(*f.Limit) + ff.Limit = &u + } + if f.Tag_a != nil && len(f.Tag_a) > 0 { + t := tag.New("#a") + for _, v := range f.Tag_a { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_b != nil && len(f.Tag_b) > 0 { + t := tag.New("#b") + for _, v := range f.Tag_b { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_c != nil && len(f.Tag_c) > 0 { + t := tag.New("#c") + for _, v := range f.Tag_c { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_d != nil && len(f.Tag_d) > 0 { + t := tag.New("#d") + for _, v := range f.Tag_d { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_e != nil && len(f.Tag_e) > 0 { + t := tag.New("#e") + for _, v := range f.Tag_e { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_f != nil && len(f.Tag_f) > 0 { + t := tag.New("#f") + for _, v := range f.Tag_f { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_g != nil && len(f.Tag_g) > 0 { + t := tag.New("#g") + for _, v := range f.Tag_g { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_h != nil && len(f.Tag_h) > 0 { + t := tag.New("#h") + for _, v := range f.Tag_h { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_i != nil && len(f.Tag_i) > 0 { + t := tag.New("#i") + for _, v := range f.Tag_i { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_j != nil && len(f.Tag_j) > 0 { + t := tag.New("#j") + for _, v := range f.Tag_j { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_k != nil && len(f.Tag_k) > 0 { + t := tag.New("#k") + for _, v := range f.Tag_k { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_l != nil && len(f.Tag_l) > 0 { + t := tag.New("#l") + for _, v := range f.Tag_l { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_m != nil && len(f.Tag_m) > 0 { + t := tag.New("#m") + for _, v := range f.Tag_m { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_n != nil && len(f.Tag_n) > 0 { + t := tag.New("#n") + for _, v := range f.Tag_n { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_o != nil && len(f.Tag_o) > 0 { + t := tag.New("#o") + for _, v := range f.Tag_o { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_p != nil && len(f.Tag_p) > 0 { + t := tag.New("#p") + for _, v := range f.Tag_p { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_q != nil && len(f.Tag_q) > 0 { + t := tag.New("#q") + for _, v := range f.Tag_q { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_r != nil && len(f.Tag_r) > 0 { + t := tag.New("#r") + for _, v := range f.Tag_r { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_s != nil && len(f.Tag_s) > 0 { + t := tag.New("#s") + for _, v := range f.Tag_s { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_t != nil && len(f.Tag_t) > 0 { + t := tag.New("#t") + for _, v := range f.Tag_t { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_u != nil && len(f.Tag_u) > 0 { + t := tag.New("#u") + for _, v := range f.Tag_u { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_v != nil && len(f.Tag_v) > 0 { + t := tag.New("#v") + for _, v := range f.Tag_v { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_w != nil && len(f.Tag_w) > 0 { + t := tag.New("#w") + for _, v := range f.Tag_w { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_x != nil && len(f.Tag_x) > 0 { + t := tag.New("#x") + for _, v := range f.Tag_x { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_y != nil && len(f.Tag_y) > 0 { + t := tag.New("#y") + for _, v := range f.Tag_y { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_z != nil && len(f.Tag_z) > 0 { + t := tag.New("#z") + for _, v := range f.Tag_z { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_A != nil && len(f.Tag_A) > 0 { + t := tag.New("#A") + for _, v := range f.Tag_A { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_B != nil && len(f.Tag_B) > 0 { + t := tag.New("#B") + for _, v := range f.Tag_B { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_C != nil && len(f.Tag_C) > 0 { + t := tag.New("#C") + for _, v := range f.Tag_C { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_D != nil && len(f.Tag_D) > 0 { + t := tag.New("#D") + for _, v := range f.Tag_D { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_E != nil && len(f.Tag_E) > 0 { + t := tag.New("#E") + for _, v := range f.Tag_E { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_F != nil && len(f.Tag_F) > 0 { + t := tag.New("#F") + for _, v := range f.Tag_F { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_G != nil && len(f.Tag_G) > 0 { + t := tag.New("#G") + for _, v := range f.Tag_G { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_H != nil && len(f.Tag_H) > 0 { + t := tag.New("#H") + for _, v := range f.Tag_H { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_I != nil && len(f.Tag_I) > 0 { + t := tag.New("#I") + for _, v := range f.Tag_I { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_J != nil && len(f.Tag_J) > 0 { + t := tag.New("#J") + for _, v := range f.Tag_J { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_K != nil && len(f.Tag_K) > 0 { + t := tag.New("#K") + for _, v := range f.Tag_K { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_L != nil && len(f.Tag_L) > 0 { + t := tag.New("#L") + for _, v := range f.Tag_L { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_M != nil && len(f.Tag_M) > 0 { + t := tag.New("#M") + for _, v := range f.Tag_M { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_N != nil && len(f.Tag_N) > 0 { + t := tag.New("#N") + for _, v := range f.Tag_N { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_O != nil && len(f.Tag_O) > 0 { + t := tag.New("#O") + for _, v := range f.Tag_O { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_P != nil && len(f.Tag_P) > 0 { + t := tag.New("#P") + for _, v := range f.Tag_P { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_Q != nil && len(f.Tag_Q) > 0 { + t := tag.New("#Q") + for _, v := range f.Tag_Q { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_R != nil && len(f.Tag_R) > 0 { + t := tag.New("#R") + for _, v := range f.Tag_R { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_S != nil && len(f.Tag_S) > 0 { + t := tag.New("#S") + for _, v := range f.Tag_S { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_T != nil && len(f.Tag_T) > 0 { + t := tag.New("#T") + for _, v := range f.Tag_T { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_U != nil && len(f.Tag_U) > 0 { + t := tag.New("#U") + for _, v := range f.Tag_U { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_V != nil && len(f.Tag_V) > 0 { + t := tag.New("#V") + for _, v := range f.Tag_V { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_W != nil && len(f.Tag_W) > 0 { + t := tag.New("#W") + for _, v := range f.Tag_W { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_X != nil && len(f.Tag_X) > 0 { + t := tag.New("#X") + for _, v := range f.Tag_X { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_Y != nil && len(f.Tag_Y) > 0 { + t := tag.New("#Y") + for _, v := range f.Tag_Y { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + if f.Tag_Z != nil && len(f.Tag_Z) > 0 { + t := tag.New("#Z") + for _, v := range f.Tag_Z { + t.Append([]byte(v)) + } + ff.Tags.AppendTags(t) + } + return +} + +var exampleSince int64 = 1753432853 +var exampleUntil int64 = 1753462853 +var exampleLimit int = 20 +var created_atMinimum float64 = 0 +var created_atMaximum float64 = float64(math.MaxInt64) +var limitMinimum float64 = 0 +var limitMaximum float64 = float64(math.MaxUint64) + +var EventsBody = &huma.RequestBody{ + Description: "array of nostr events", + Content: map[string]*huma.MediaType{ + "application/json": { + Schema: &huma.Schema{ + Type: huma.TypeObject, + Examples: []any{ + Filter{ + Kinds: []int{0, 1}, + Authors: []string{ + "deadbeefcafe8008deadbeefcafe8008deadbeefcafe8008deadbeefcafe8008", + "deadbeefcafe8008deadbeefcafe8008deadbeefcafe8008deadbeefcafe8008", + }, + Tag_e: []string{ + "deadbeefcafe8008deadbeefcafe8008deadbeefcafe8008deadbeefcafe8008", + "deadbeefcafe8008deadbeefcafe8008deadbeefcafe8008deadbeefcafe8008", + "deadbeefcafe8008deadbeefcafe8008deadbeefcafe8008deadbeefcafe8008", + "deadbeefcafe8008deadbeefcafe8008deadbeefcafe8008deadbeefcafe8008", + }, + Since: &exampleSince, + Until: &exampleUntil, + Limit: &exampleLimit, + }, + Filter{ + Ids: []string{ + "deadbeefcafe8008deadbeefcafe8008deadbeefcafe8008deadbeefcafe8008", + "deadbeefcafe8008deadbeefcafe8008deadbeefcafe8008deadbeefcafe8008", + }, + }, + }, + Properties: map[string]*huma.Schema{ + "ids": { + Type: huma.TypeArray, + Description: "list of event IDs to search for (if present, all other fields are excluded)", + Items: &huma.Schema{ + Type: huma.TypeString, + }, + }, + "kinds": { + Type: huma.TypeArray, + Description: "list of event kinds to search for", + Items: &huma.Schema{ + Type: huma.TypeInteger, + }, + }, + "authors": { + Type: huma.TypeArray, + Description: "list of pubkeys to search for", + Items: &huma.Schema{ + Type: huma.TypeString, + }, + }, + "^#[a-zA-Z]$": { + Type: huma.TypeArray, + Description: "list of tag values to search for", + Items: &huma.Schema{ + Type: huma.TypeString, + }, + }, + "since": { + Type: huma.TypeInteger, + Description: "earliest (smallest, inclusive) created_at value for events", + Minimum: &created_atMinimum, + Maximum: &created_atMaximum, + }, + "until": { + Type: huma.TypeInteger, + Description: "latest (largest, inclusive) created_at value for events", + Minimum: &created_atMinimum, + Maximum: &created_atMaximum, + }, + "limit": { + Type: huma.TypeInteger, + Description: "maximum number of events to return (newest first, reverse chronological order)", + Minimum: &limitMinimum, + Maximum: &limitMaximum, + }, + }, + }, + }, + }, +} + type EventsInput struct { - Auth string `header:"Authorization" doc:"nostr nip-98 (and expiring variant)" required:"false"` - Accept string `header:"Accept" default:"application/nostr+json"` - Body []byte `doc:"filter JSON (standard NIP-01 filter syntax)"` + Auth string `header:"Authorization" doc:"nostr nip-98 (and expiring variant)" required:"false"` + Accept string `header:"Accept" default:"application/nostr+json"` + Body *Filter `doc:"filter JSON (standard NIP-01 filter syntax)"` } type EventsOutput struct { - Body []byte + Body []event.J } // RegisterEvents is the implementation of the HTTP API Events method. @@ -42,6 +600,7 @@ func (x *Operations) RegisterEvents(api huma.API) { Path: path, Method: method, Tags: []string{"events"}, + RequestBody: EventsBody, Description: helpers.GenerateDescription(description, scopes), Security: []map[string][]string{{"auth": scopes}}, }, func(ctx context.T, input *EventsInput) ( diff --git a/pkg/protocol/openapi/export.go b/pkg/protocol/openapi/export.go index d1cd8aa..df5fd8a 100644 --- a/pkg/protocol/openapi/export.go +++ b/pkg/protocol/openapi/export.go @@ -20,7 +20,9 @@ type ExportOutput struct{ RawBody []byte } // RegisterExport implements the Export HTTP API method. func (x *Operations) RegisterExport(api huma.API) { name := "Export" - description := "Export all events (only works with NIP-98 capable client, will not work with UI)" + description := `Export all events (only works with NIP-98 capable client, will not work with UI) + +Returns the events as line structured JSON (JSONL) in the order that they were received by the relay.` path := x.path + "/export" scopes := []string{"admin", "read"} method := http.MethodGet diff --git a/pkg/protocol/openapi/import.go b/pkg/protocol/openapi/import.go index 4a98f8a..50154e0 100644 --- a/pkg/protocol/openapi/import.go +++ b/pkg/protocol/openapi/import.go @@ -16,6 +16,7 @@ import ( // stream of line structured JSON events. type ImportInput struct { Auth string `header:"Authorization" doc:"nostr nip-98 (and expiring variant) token for authentication" required:"true"` + Body string `doc:"events in line structured JSON (JSONL)"` } // ImportOutput is nothing, basically; a 204 or 200 status is expected.