Files
orly/pkg/protocol/openapi/events.go
mleku dda39de5a5
Some checks failed
Go / build (push) Has been cancelled
Go / release (push) Has been cancelled
refactor logging to use closures for intensive tasks
2025-08-15 22:27:16 +01:00

687 lines
17 KiB
Go

package openapi
import (
"errors"
"github.com/danielgtaylor/huma/v2"
"github.com/davecgh/go-spew/spew"
"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 *Filter `doc:"filter JSON (standard NIP-01 filter syntax)"`
}
type EventsOutput struct {
Body []*event.J
}
// RegisterEvents is the implementation of the HTTP API Events method.
//
// This method returns the results of a single filter query, filtered by
// privilege.
func (x *Operations) RegisterEvents(api huma.API) {
name := "Events"
description := `Query for events using a standard NIP-01 filter (only allows one filter)
Returns events as a JSON array of event objects.`
path := x.path + "/events"
scopes := []string{"user", "read"}
method := http.MethodPost
huma.Register(
api, huma.Operation{
OperationID: name,
Summary: name,
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) (
output *EventsOutput, err error,
) {
r := ctx.Value("http-request").(*http.Request)
remote := helpers.GetRemoteFromReq(r)
var authed, super bool
var pubkey []byte
// if auth is required and not public readable, the request is not
// authorized.
if x.I.AuthRequired() && !x.I.PublicReadable() {
authed, pubkey, super = x.UserAuth(r, remote)
if !authed {
err = huma.Error401Unauthorized("Not Authorized")
return
}
}
f := filter.New()
var rem []byte
log.T.C(func() string { return spew.Sdump(input) })
if len(rem) > 0 {
log.I.F("extra '%s'", rem)
}
var accept bool
allowed, accept, _ := x.AcceptReq(
x.Context(), r, filters.New(f), pubkey, remote,
)
if !accept {
err = huma.Error401Unauthorized("Not Authorized for query")
return
}
var events event.S
for _, ff := range allowed.F {
// var i uint
if pointers.Present(ff.Limit) {
if *ff.Limit == 0 {
continue
}
}
if events, err = x.Storage().QueryEvents(
x.Context(), ff,
); err != nil {
if errors.Is(err, badger.ErrDBClosed) {
return
}
continue
}
// filter events the authed pubkey is not privileged to fetch.
// relay replicas don't have this limitation.
if x.AuthRequired() && len(pubkey) > 0 && !super {
var tmp event.S
for _, ev := range events {
if !auth.CheckPrivilege(pubkey, ev) {
log.W.F(
"not privileged: client pubkey '%0x' event pubkey '%0x' kind %s privileged: %v",
pubkey, ev.Pubkey,
ev.Kind.Name(),
ev.Kind.IsPrivileged(),
)
continue
}
tmp = append(tmp, ev)
}
// cap the number of events to 512 to stop excessively large
// response.
if len(events) > 512 {
break
}
events = tmp
}
}
output = &EventsOutput{}
for _, ev := range events {
output.Body = append(output.Body, ev.ToEventJ())
}
return
},
)
}