Introduced `lol.Tracer` for function entry/exit logging across various packages. This improves traceability and debugging of function executions while preserving existing behavior. Removed unused files `doc.go` and `nothing.go` to clean up the repository.
140 lines
4.2 KiB
Go
140 lines
4.2 KiB
Go
package openapi
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/danielgtaylor/huma/v2"
|
|
"github.com/rickb777/acceptable/header"
|
|
|
|
"realy.lol/chk"
|
|
"realy.lol/context"
|
|
"realy.lol/ec/schnorr"
|
|
"realy.lol/hex"
|
|
"realy.lol/httpauth"
|
|
"realy.lol/lol"
|
|
"realy.lol/realy/helpers"
|
|
"realy.lol/sha256"
|
|
"realy.lol/store"
|
|
"realy.lol/tag"
|
|
)
|
|
|
|
// EventsInput is the parameters for an Events HTTP API method. Basically an array of eventid.T.
|
|
type EventsInput struct {
|
|
Auth string `header:"Authorization" doc:"nostr nip-98 (and expiring variant)" required:"false"`
|
|
Accept string `header:"Accept" default:"application/nostr+json;q=0.9,application/x-realy-event;q=0.1" doc:"event encoding format that is expected, priority using mimetype;q=0.x will indicate preference when multiple are available"`
|
|
Body []string `doc:"list of event Ids"`
|
|
}
|
|
|
|
type EventsOutput struct {
|
|
Limit int `header:"X-Limit" default:"1000" doc:"informs client maximum number of events that they can request"`
|
|
}
|
|
|
|
// RegisterEvents is the implementation of the HTTP API for Events.
|
|
func (x *Operations) RegisterEvents(api huma.API) {
|
|
lol.Tracer("RegisterEvents")
|
|
defer func() { lol.Tracer("end RegisterEvents") }()
|
|
name := "Events"
|
|
description := "Returns the full events from a list of event Ids as a line structured JSON. Auth required to fetch more than 1000 events, and if not enabled, 1000 is the limit."
|
|
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"},
|
|
Description: helpers.GenerateDescription(description, scopes),
|
|
Security: []map[string][]string{{"auth": scopes}},
|
|
}, func(ctx context.T, input *EventsInput) (output *struct{}, err error) {
|
|
lol.Tracer("Events", input)
|
|
defer func() { lol.Tracer("end Events", output, err) }()
|
|
// log.I.S(input)
|
|
if len(input.Body) == 10000 {
|
|
err = huma.Error400BadRequest(
|
|
"cannot process more than 10000 events in a request")
|
|
return
|
|
|
|
}
|
|
var authrequired bool
|
|
if len(input.Body) > 1000 || x.Server.AuthRequired() {
|
|
authrequired = true
|
|
}
|
|
limit := 1000
|
|
if !authrequired {
|
|
limit = 10000
|
|
}
|
|
r := ctx.Value("http-request").(*http.Request)
|
|
var valid bool
|
|
var pubkey []byte
|
|
valid, pubkey, err = httpauth.CheckAuth(r)
|
|
// if there is an error but not that the token is missing, or there is no error
|
|
// but the signature is invalid, return error that request is unauthorized.
|
|
if err != nil && !errors.Is(err, httpauth.ErrMissingKey) {
|
|
err = huma.Error400BadRequest(err.Error())
|
|
return
|
|
}
|
|
err = nil
|
|
if authrequired && len(pubkey) != schnorr.PubKeyBytesLen {
|
|
err = huma.Error400BadRequest(
|
|
"cannot process more than 1000 events in a request without being authenticated")
|
|
return
|
|
}
|
|
if authrequired && valid {
|
|
x.Server.Lock()
|
|
// we only allow the first level of the allowed users this kind of access
|
|
if x.Server.OwnersFollowed(string(pubkey)) {
|
|
err = huma.Error403Forbidden(
|
|
fmt.Sprintf(
|
|
"authenticated user %0x does not have permission for this request (owners can use export)",
|
|
pubkey))
|
|
return
|
|
}
|
|
}
|
|
if !valid {
|
|
err = huma.Error401Unauthorized("Authorization header is invalid")
|
|
return
|
|
}
|
|
sto := x.Storage()
|
|
var evIds [][]byte
|
|
for _, id := range input.Body {
|
|
var idb []byte
|
|
if idb, err = hex.Dec(id); chk.E(err) {
|
|
err = huma.Error422UnprocessableEntity(err.Error())
|
|
return
|
|
}
|
|
if len(idb) != sha256.Size {
|
|
err = huma.Error422UnprocessableEntity(
|
|
fmt.Sprintf("event Id must be 64 hex characters: '%s'", id))
|
|
}
|
|
evIds = append(evIds, idb)
|
|
}
|
|
|
|
if idsWriter, ok := sto.(store.GetIdsWriter); ok {
|
|
w := ctx.Value("http-response").(http.ResponseWriter)
|
|
var binary bool
|
|
precedence := header.ParsePrecedenceValues(r.Header.Get("Accept"))
|
|
done:
|
|
for _, v := range precedence {
|
|
switch v.Value {
|
|
case "application/x-realy-event":
|
|
binary = true
|
|
break done
|
|
case "application/nostr+json":
|
|
break done
|
|
default:
|
|
break done
|
|
}
|
|
}
|
|
w.WriteHeader(200)
|
|
w.Header().Set("X-Limit", fmt.Sprint(limit))
|
|
if err = idsWriter.FetchIds(w, x.Context(), tag.New(evIds...), binary); chk.E(err) {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
})
|
|
}
|