- Added `Export` method in `database/database.go` to export events to an io.Writer - Implemented detailed logic for exporting all or specific pubkeys' events - Removed placeholder `Export` function with TODO comment from `database/database.go` - Updated error handling in `handleReq.go` and `publisher.go` by using `err != nil` instead of `chk.E(err)` - Added more detailed logging in privilege check conditions in both `publisher.go` and `handleReq.go` - Introduced new imports such as `"fmt"` in `connection.go` for improved error message formatting - Created a new file `export.go` under the `database` package with complete implementation of export functionality
322 lines
6.3 KiB
Go
322 lines
6.3 KiB
Go
package event
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"github.com/minio/sha256-simd"
|
|
"io"
|
|
"orly.dev/pkg/crypto/ec/schnorr"
|
|
"orly.dev/pkg/encoders/hex"
|
|
"orly.dev/pkg/encoders/kind"
|
|
"orly.dev/pkg/encoders/tags"
|
|
text2 "orly.dev/pkg/encoders/text"
|
|
"orly.dev/pkg/encoders/timestamp"
|
|
"orly.dev/pkg/utils/chk"
|
|
"orly.dev/pkg/utils/errorf"
|
|
)
|
|
|
|
var (
|
|
jId = []byte("id")
|
|
jPubkey = []byte("pubkey")
|
|
jCreatedAt = []byte("created_at")
|
|
jKind = []byte("kind")
|
|
jTags = []byte("tags")
|
|
jContent = []byte("content")
|
|
jSig = []byte("sig")
|
|
)
|
|
|
|
// Marshal appends an event.E to a provided destination slice.
|
|
func (ev *E) Marshal(dst []byte) (b []byte) {
|
|
b = ev.MarshalWithWhitespace(dst, false)
|
|
return
|
|
}
|
|
|
|
// MarshalWithWhitespace adds tabs and newlines to make the JSON more readable
|
|
// for humans, if the on flag is set to true.
|
|
func (ev *E) MarshalWithWhitespace(dst []byte, on bool) (b []byte) {
|
|
// open parentheses
|
|
dst = append(dst, '{')
|
|
// Id
|
|
if on {
|
|
dst = append(dst, '\n', '\t')
|
|
}
|
|
dst = text2.JSONKey(dst, jId)
|
|
if on {
|
|
dst = append(dst, ' ')
|
|
}
|
|
dst = text2.AppendQuote(dst, ev.Id, hex.EncAppend)
|
|
dst = append(dst, ',')
|
|
// Pubkey
|
|
if on {
|
|
dst = append(dst, '\n', '\t')
|
|
}
|
|
dst = text2.JSONKey(dst, jPubkey)
|
|
if on {
|
|
dst = append(dst, ' ')
|
|
}
|
|
dst = text2.AppendQuote(dst, ev.Pubkey, hex.EncAppend)
|
|
dst = append(dst, ',')
|
|
if on {
|
|
dst = append(dst, '\n', '\t')
|
|
}
|
|
// CreatedAt
|
|
dst = text2.JSONKey(dst, jCreatedAt)
|
|
if on {
|
|
dst = append(dst, ' ')
|
|
}
|
|
dst = ev.CreatedAt.Marshal(dst)
|
|
dst = append(dst, ',')
|
|
if on {
|
|
dst = append(dst, '\n', '\t')
|
|
}
|
|
// Kind
|
|
dst = text2.JSONKey(dst, jKind)
|
|
if on {
|
|
dst = append(dst, ' ')
|
|
}
|
|
dst = ev.Kind.Marshal(dst)
|
|
dst = append(dst, ',')
|
|
if on {
|
|
dst = append(dst, '\n', '\t')
|
|
}
|
|
// Tags
|
|
dst = text2.JSONKey(dst, jTags)
|
|
if on {
|
|
dst = append(dst, ' ')
|
|
}
|
|
if on {
|
|
dst = ev.Tags.MarshalWithWhitespace(dst)
|
|
} else {
|
|
dst = ev.Tags.Marshal(dst)
|
|
}
|
|
dst = append(dst, ',')
|
|
if on {
|
|
dst = append(dst, '\n', '\t')
|
|
}
|
|
// Content
|
|
dst = text2.JSONKey(dst, jContent)
|
|
if on {
|
|
dst = append(dst, ' ')
|
|
}
|
|
dst = text2.AppendQuote(dst, ev.Content, text2.NostrEscape)
|
|
dst = append(dst, ',')
|
|
if on {
|
|
dst = append(dst, '\n', '\t')
|
|
}
|
|
// jSig
|
|
dst = text2.JSONKey(dst, jSig)
|
|
if on {
|
|
dst = append(dst, ' ')
|
|
}
|
|
dst = text2.AppendQuote(dst, ev.Sig, hex.EncAppend)
|
|
if on {
|
|
dst = append(dst, '\n')
|
|
}
|
|
// close parentheses
|
|
dst = append(dst, '}')
|
|
b = dst
|
|
return
|
|
}
|
|
|
|
// Marshal is a normal function that is the same as event.E Marshal method
|
|
// except you explicitly specify the receiver.
|
|
func Marshal(ev *E, dst []byte) (b []byte) { return ev.Marshal(dst) }
|
|
|
|
// Unmarshal an event from JSON into an event.E.
|
|
// This function handles both minified and whitespace-formatted JSON.
|
|
func (ev *E) Unmarshal(b []byte) (r []byte, err error) {
|
|
key := make([]byte, 0, 9)
|
|
r = b
|
|
for ; len(r) > 0; r = r[1:] {
|
|
// Skip whitespace
|
|
if isWhitespace(r[0]) {
|
|
continue
|
|
}
|
|
if r[0] == '{' {
|
|
r = r[1:]
|
|
goto BetweenKeys
|
|
}
|
|
}
|
|
goto eof
|
|
BetweenKeys:
|
|
for ; len(r) > 0; r = r[1:] {
|
|
// Skip whitespace
|
|
if isWhitespace(r[0]) {
|
|
continue
|
|
}
|
|
if r[0] == '"' {
|
|
r = r[1:]
|
|
goto InKey
|
|
}
|
|
}
|
|
goto eof
|
|
InKey:
|
|
for ; len(r) > 0; r = r[1:] {
|
|
if r[0] == '"' {
|
|
r = r[1:]
|
|
goto InKV
|
|
}
|
|
key = append(key, r[0])
|
|
}
|
|
goto eof
|
|
InKV:
|
|
for ; len(r) > 0; r = r[1:] {
|
|
// Skip whitespace
|
|
if isWhitespace(r[0]) {
|
|
continue
|
|
}
|
|
if r[0] == ':' {
|
|
r = r[1:]
|
|
goto InVal
|
|
}
|
|
}
|
|
goto eof
|
|
InVal:
|
|
// Skip whitespace before value
|
|
for len(r) > 0 && isWhitespace(r[0]) {
|
|
r = r[1:]
|
|
}
|
|
|
|
switch key[0] {
|
|
case jId[0]:
|
|
if !bytes.Equal(jId, key) {
|
|
goto invalid
|
|
}
|
|
var id []byte
|
|
if id, r, err = text2.UnmarshalHex(r); chk.E(err) {
|
|
return
|
|
}
|
|
if len(id) != sha256.Size {
|
|
err = errorf.E(
|
|
"invalid Id, require %d got %d", sha256.Size,
|
|
len(id),
|
|
)
|
|
return
|
|
}
|
|
ev.Id = id
|
|
goto BetweenKV
|
|
case jPubkey[0]:
|
|
if !bytes.Equal(jPubkey, key) {
|
|
goto invalid
|
|
}
|
|
var pk []byte
|
|
if pk, r, err = text2.UnmarshalHex(r); chk.E(err) {
|
|
return
|
|
}
|
|
if len(pk) != schnorr.PubKeyBytesLen {
|
|
err = errorf.E(
|
|
"invalid pubkey, require %d got %d",
|
|
schnorr.PubKeyBytesLen, len(pk),
|
|
)
|
|
return
|
|
}
|
|
ev.Pubkey = pk
|
|
goto BetweenKV
|
|
case jKind[0]:
|
|
if !bytes.Equal(jKind, key) {
|
|
goto invalid
|
|
}
|
|
ev.Kind = kind.New(0)
|
|
if r, err = ev.Kind.Unmarshal(r); chk.E(err) {
|
|
return
|
|
}
|
|
goto BetweenKV
|
|
case jTags[0]:
|
|
if !bytes.Equal(jTags, key) {
|
|
goto invalid
|
|
}
|
|
ev.Tags = tags.New()
|
|
if r, err = ev.Tags.Unmarshal(r); chk.E(err) {
|
|
return
|
|
}
|
|
goto BetweenKV
|
|
case jSig[0]:
|
|
if !bytes.Equal(jSig, key) {
|
|
goto invalid
|
|
}
|
|
var sig []byte
|
|
if sig, r, err = text2.UnmarshalHex(r); chk.E(err) {
|
|
return
|
|
}
|
|
if len(sig) != schnorr.SignatureSize {
|
|
err = errorf.E(
|
|
"invalid sig length, require %d got %d '%s'\n%s",
|
|
schnorr.SignatureSize, len(sig), r, b,
|
|
)
|
|
return
|
|
}
|
|
ev.Sig = sig
|
|
goto BetweenKV
|
|
case jContent[0]:
|
|
if key[1] == jContent[1] {
|
|
if !bytes.Equal(jContent, key) {
|
|
goto invalid
|
|
}
|
|
if ev.Content, r, err = text2.UnmarshalQuoted(r); chk.T(err) {
|
|
return
|
|
}
|
|
goto BetweenKV
|
|
} else if key[1] == jCreatedAt[1] {
|
|
if !bytes.Equal(jCreatedAt, key) {
|
|
goto invalid
|
|
}
|
|
ev.CreatedAt = timestamp.New(int64(0))
|
|
if r, err = ev.CreatedAt.Unmarshal(r); chk.T(err) {
|
|
return
|
|
}
|
|
goto BetweenKV
|
|
} else {
|
|
goto invalid
|
|
}
|
|
default:
|
|
goto invalid
|
|
}
|
|
BetweenKV:
|
|
key = key[:0]
|
|
for ; len(r) > 0; r = r[1:] {
|
|
// Skip whitespace
|
|
if isWhitespace(r[0]) {
|
|
continue
|
|
}
|
|
|
|
switch {
|
|
case len(r) == 0:
|
|
return
|
|
case r[0] == '}':
|
|
r = r[1:]
|
|
goto AfterClose
|
|
case r[0] == ',':
|
|
r = r[1:]
|
|
goto BetweenKeys
|
|
case r[0] == '"':
|
|
r = r[1:]
|
|
goto InKey
|
|
}
|
|
}
|
|
goto eof
|
|
AfterClose:
|
|
// Skip any trailing whitespace
|
|
for len(r) > 0 && isWhitespace(r[0]) {
|
|
r = r[1:]
|
|
}
|
|
return
|
|
invalid:
|
|
err = fmt.Errorf(
|
|
"invalid key,\n'%s'\n'%s'\n'%s'", string(b), string(b[:len(r)]),
|
|
string(r),
|
|
)
|
|
return
|
|
eof:
|
|
err = io.EOF
|
|
return
|
|
}
|
|
|
|
// isWhitespace returns true if the byte is a whitespace character (space, tab, newline, carriage return).
|
|
func isWhitespace(b byte) bool {
|
|
return b == ' ' || b == '\t' || b == '\n' || b == '\r'
|
|
}
|
|
|
|
// Unmarshal is the same as the event.E Unmarshal method except you give it the
|
|
// event to marshal into instead of call it as a method of the type.
|
|
func Unmarshal(ev *E, b []byte) (r []byte, err error) { return ev.Unmarshal(b) }
|