feat: updates

This commit is contained in:
Jon Lundy 2022-09-04 08:34:22 -06:00
parent 8c54eefcdd
commit f65e9ca666
Signed by untrusted user who does not match committer: xuu
GPG Key ID: C63E6D61F3035024
10 changed files with 523 additions and 75 deletions

View File

@ -15,6 +15,7 @@ import (
"github.com/sour-is/ev/app/salty"
"github.com/sour-is/ev/internal/graph/generated"
"github.com/sour-is/ev/internal/logz"
"github.com/sour-is/ev/pkg/es"
"github.com/sour-is/ev/pkg/gql"
"github.com/vektah/gqlparser/v2/gqlerror"
)
@ -22,6 +23,7 @@ import (
type Resolver struct {
msgbus.MsgbusResolver
salty.SaltyResolver
es.EventResolver
}
func New(ctx context.Context, resolvers ...interface{ RegisterHTTP(*http.ServeMux) }) (*Resolver, error) {
@ -43,13 +45,12 @@ outer:
if field.IsNil() && rs.Type().Implements(field.Type()) {
span.AddEvent(fmt.Sprint("found ", field.Type().Name()))
field.Set(rs)
break outer
continue outer
}
}
span.AddEvent(fmt.Sprint("default ", field.Type().Name()))
field.Set(noop)
}
return r, nil
@ -107,6 +108,7 @@ func NoopRecover(ctx context.Context, err interface{}) error {
var _ msgbus.MsgbusResolver = (*noop)(nil)
var _ salty.SaltyResolver = (*noop)(nil)
var _ es.EventResolver = (*noop)(nil)
func (*noop) CreateSaltyUser(ctx context.Context, nick string, pubkey string) (*salty.SaltyUser, error) {
panic("not implemented")
@ -120,4 +122,11 @@ func (*noop) SaltyUser(ctx context.Context, nick string) (*salty.SaltyUser, erro
func (*noop) PostAdded(ctx context.Context, streamID string, after int64) (<-chan *msgbus.PostEvent, error) {
panic("not implemented")
}
func (*noop) Events(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error) {
panic("not implemented")
}
func (*noop) EventAdded(ctx context.Context, streamID string, after int64) (<-chan *es.GQLEvent, error) {
panic("not implemented")
}
func (*noop) RegisterHTTP(*http.ServeMux) {}

View File

@ -435,14 +435,21 @@ func (e *PostEvent) SetEventMeta(eventMeta event.Meta) {
}
e.eventMeta = eventMeta
}
func (e *PostEvent) MarshalBinary() ([]byte, error) {
j := struct {
func (e *PostEvent) Values() any {
if e == nil {
return nil
}
return struct {
Payload []byte
Tags []string
}{
Payload: e.payload,
Tags: e.tags,
}
}
func (e *PostEvent) MarshalBinary() ([]byte, error) {
j := e.Values()
return json.Marshal(&j)
}
func (e *PostEvent) UnmarshalBinary(b []byte) error {

142
app/salty/salty-addr.go Normal file
View File

@ -0,0 +1,142 @@
package salty
import (
"context"
"crypto/sha256"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/keys-pub/keys"
"github.com/sour-is/ev/internal/logz"
)
// Config represents a Salty Config for a User which at a minimum is required
// to have an Endpoint and Key (Public Key)
type Config struct {
Endpoint string `json:"endpoint"`
Key string `json:"key"`
}
type Capabilities struct {
AcceptEncoding string
}
func (c Capabilities) String() string {
return fmt.Sprint("accept-encoding: ", c.AcceptEncoding)
}
type Addr struct {
User string
Domain string
capabilities Capabilities
discoveredDomain string
dns DNSResolver
endpoint *url.URL
key *keys.EdX25519PublicKey
}
// ParseAddr parsers a Salty Address for a user into it's user and domain
// parts and returns an Addr object with the User and Domain and a method
// for returning the expected User's Well-Known URI
func (s *service) ParseAddr(addr string) (*Addr, error) {
parts := strings.Split(strings.ToLower(addr), "@")
if len(parts) != 2 {
return nil, fmt.Errorf("expected nick@domain found %q", addr)
}
return &Addr{User: parts[0], Domain: parts[1], dns: s.dns}, nil
}
func (a *Addr) String() string {
return fmt.Sprintf("%s@%s", a.User, a.Domain)
}
// Hash returns the Hex(SHA256Sum()) of the Address
func (a *Addr) Hash() string {
return fmt.Sprintf("%x", sha256.Sum256([]byte(strings.ToLower(a.String()))))
}
// URI returns the Well-Known URI for this Addr
func (a *Addr) URI() string {
return fmt.Sprintf("https://%s/.well-known/salty/%s.json", a.DiscoveredDomain(), a.User)
}
// HashURI returns the Well-Known HashURI for this Addr
func (a *Addr) HashURI() string {
return fmt.Sprintf("https://%s/.well-known/salty/%s.json", a.DiscoveredDomain(), a.Hash())
}
// DiscoveredDomain returns the discovered domain (if any) of fallbacks to the Domain
func (a *Addr) DiscoveredDomain() string {
if a.discoveredDomain != "" {
return a.discoveredDomain
}
return a.Domain
}
func (a *Addr) Refresh(ctx context.Context) error {
ctx, span := logz.Span(ctx)
defer span.End()
span.AddEvent(fmt.Sprintf("Looking up SRV record for _salty._tcp.%s", a.Domain))
if target, _, err := a.dns.LookupSRV(ctx, "salty", "tcp", a.Domain); err == nil {
a.discoveredDomain = target
span.AddEvent(fmt.Sprintf("Discovered salty services %s", a.discoveredDomain))
} else if err != nil {
span.AddEvent(fmt.Sprintf("error looking up SRV record for _salty._tcp.%s : %s", a.Domain, err))
}
config, cap, err := fetchConfig(ctx, a.HashURI())
if err != nil {
// Fallback to plain user nick
config, cap, err = fetchConfig(ctx, a.URI())
}
if err != nil {
return fmt.Errorf("error looking up user %s: %w", a, err)
}
key, err := keys.NewEdX25519PublicKeyFromID(keys.ID(config.Key))
if err != nil {
return fmt.Errorf("error parsing public key %s: %w", config.Key, err)
}
a.key = key
u, err := url.Parse(config.Endpoint)
if err != nil {
return fmt.Errorf("error parsing endpoint %s: %w", config.Endpoint, err)
}
a.endpoint = u
a.capabilities = cap
span.AddEvent(fmt.Sprintf("Discovered endpoint: %v", a.endpoint))
span.AddEvent(fmt.Sprintf("Discovered capability: %v", a.capabilities))
return nil
}
func fetchConfig(ctx context.Context, addr string) (config Config, cap Capabilities, err error) {
ctx, span := logz.Span(ctx)
defer span.End()
var req *http.Request
req, err = http.NewRequestWithContext(ctx, http.MethodGet, addr, nil)
if err != nil {
return
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return
}
if err = json.NewDecoder(res.Body).Decode(&config); err != nil {
return
}
cap.AcceptEncoding = res.Header.Get("Accept-Encoding")
return
}

View File

@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"path"
"strings"
@ -19,12 +20,21 @@ import (
"go.uber.org/multierr"
)
type DNSResolver interface {
LookupSRV(ctx context.Context, service, proto, name string) (string, []*net.SRV, error)
}
type service struct {
baseURL string
es *es.EventStore
dns DNSResolver
Mresolver_create_salty_user syncint64.Counter
Mresolver_salty_user syncint64.Counter
m_create_user syncint64.Counter
m_get_user syncint64.Counter
m_api_ping syncint64.Counter
m_api_register syncint64.Counter
m_api_lookup syncint64.Counter
m_api_send syncint64.Counter
}
type contextKey struct {
name string
@ -51,13 +61,25 @@ func New(ctx context.Context, es *es.EventStore, baseURL string) (*service, erro
m := logz.Meter(ctx)
svc := &service{baseURL: baseURL, es: es}
svc := &service{baseURL: baseURL, es: es, dns: net.DefaultResolver}
var err, errs error
svc.Mresolver_create_salty_user, err = m.SyncInt64().Counter("resolver_create_salty_user")
svc.m_create_user, err = m.SyncInt64().Counter("salty_create_user")
errs = multierr.Append(errs, err)
svc.Mresolver_salty_user, err = m.SyncInt64().Counter("resolver_salty_user")
svc.m_get_user, err = m.SyncInt64().Counter("salty_get_user")
errs = multierr.Append(errs, err)
svc.m_api_ping, err = m.SyncInt64().Counter("salty_api_ping")
errs = multierr.Append(errs, err)
svc.m_api_register, err = m.SyncInt64().Counter("salty_api_register")
errs = multierr.Append(errs, err)
svc.m_api_lookup, err = m.SyncInt64().Counter("salty_api_lookup")
errs = multierr.Append(errs, err)
svc.m_api_send, err = m.SyncInt64().Counter("salty_api_send")
errs = multierr.Append(errs, err)
span.RecordError(err)
@ -73,6 +95,12 @@ func (s *service) BaseURL() string {
func (s *service) RegisterHTTP(mux *http.ServeMux) {
mux.Handle("/.well-known/salty/", logz.Htrace(s, "lookup"))
}
func (s *service) RegisterAPIv1(mux *http.ServeMux) {
mux.HandleFunc("/ping", s.apiv1)
mux.HandleFunc("/register", s.apiv1)
mux.HandleFunc("/lookup/", s.apiv1)
mux.HandleFunc("/send", s.apiv1)
}
func (s *service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := logz.Span(ctx)
@ -112,7 +140,7 @@ func (s *service) CreateSaltyUser(ctx context.Context, nick string, pub string)
ctx, span := logz.Span(ctx)
defer span.End()
s.Mresolver_create_salty_user.Add(ctx, 1)
s.m_create_user.Add(ctx, 1)
streamID := fmt.Sprintf("saltyuser-%x", sha256.Sum256([]byte(strings.ToLower(nick))))
span.AddEvent(streamID)
@ -142,7 +170,7 @@ func (s *service) SaltyUser(ctx context.Context, nick string) (*SaltyUser, error
ctx, span := logz.Span(ctx)
defer span.End()
s.Mresolver_salty_user.Add(ctx, 1)
s.m_get_user.Add(ctx, 1)
streamID := fmt.Sprintf("saltyuser-%x", sha256.Sum256([]byte(strings.ToLower(nick))))
span.AddEvent(streamID)
@ -168,3 +196,54 @@ func (s *service) GetMiddleware() func(http.Handler) http.Handler {
})
}
}
func (s *service) apiv1(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := logz.Span(ctx)
defer span.End()
switch r.Method {
case http.MethodGet:
switch {
case r.URL.Path == "/ping":
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{}`))
case strings.HasPrefix(r.URL.Path, "/lookup/"):
addr, err := s.ParseAddr(strings.TrimPrefix(r.URL.Path, "/lookup/"))
if err != nil {
span.RecordError(err)
w.WriteHeader(http.StatusBadRequest)
return
}
err = addr.Refresh(ctx)
if err != nil {
span.RecordError(err)
w.WriteHeader(http.StatusBadRequest)
return
}
json.NewEncoder(w).Encode(addr)
return
default:
w.WriteHeader(http.StatusNotFound)
return
}
case http.MethodPost:
switch r.URL.Path {
case "/register":
case "/send":
default:
w.WriteHeader(http.StatusNotFound)
return
}
default:
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
}

View File

@ -18,7 +18,7 @@ import (
"github.com/99designs/gqlgen/plugin/federation/fedruntime"
"github.com/sour-is/ev/app/msgbus"
"github.com/sour-is/ev/app/salty"
"github.com/sour-is/ev/internal/graph/model"
"github.com/sour-is/ev/pkg/es"
"github.com/sour-is/ev/pkg/es/event"
"github.com/sour-is/ev/pkg/gql"
gqlparser "github.com/vektah/gqlparser/v2"
@ -58,9 +58,10 @@ type ComplexityRoot struct {
}
Event struct {
Bytes func(childComplexity int) int
EventID func(childComplexity int) int
EventMeta func(childComplexity int) int
ID func(childComplexity int) int
Meta func(childComplexity int) int
Values func(childComplexity int) int
}
@ -123,7 +124,7 @@ type QueryResolver interface {
SaltyUser(ctx context.Context, nick string) (*salty.SaltyUser, error)
}
type SubscriptionResolver interface {
EventAdded(ctx context.Context, streamID string, after int64) (<-chan *model.Event, error)
EventAdded(ctx context.Context, streamID string, after int64) (<-chan *es.GQLEvent, error)
PostAdded(ctx context.Context, streamID string, after int64) (<-chan *msgbus.PostEvent, error)
}
@ -156,6 +157,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Connection.Paging(childComplexity), true
case "Event.bytes":
if e.complexity.Event.Bytes == nil {
break
}
return e.complexity.Event.Bytes(childComplexity), true
case "Event.eventID":
if e.complexity.Event.EventID == nil {
break
@ -163,13 +171,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Event.EventID(childComplexity), true
case "Event.eventMeta":
if e.complexity.Event.EventMeta == nil {
break
}
return e.complexity.Event.EventMeta(childComplexity), true
case "Event.id":
if e.complexity.Event.ID == nil {
break
@ -177,6 +178,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Event.ID(childComplexity), true
case "Event.meta":
if e.complexity.Event.Meta == nil {
break
}
return e.complexity.Event.Meta(childComplexity), true
case "Event.values":
if e.complexity.Event.Values == nil {
break
@ -491,12 +499,13 @@ extend type Subscription {
eventAdded(streamID: String! after: Int! = -1): Event
}
type Event implements Edge {
type Event implements Edge @goModel(model: "github.com/sour-is/ev/pkg/es.GQLEvent") {
id: ID!
eventID: String!
values: Map!
eventMeta: Meta!
bytes: String!
meta: Meta!
}`, BuiltIn: false},
{Name: "../../../pkg/gql/common.graphqls", Input: `scalar Time
scalar Map
@ -877,7 +886,7 @@ func (ec *executionContext) fieldContext_Connection_edges(ctx context.Context, f
return fc, nil
}
func (ec *executionContext) _Event_id(ctx context.Context, field graphql.CollectedField, obj *model.Event) (ret graphql.Marshaler) {
func (ec *executionContext) _Event_id(ctx context.Context, field graphql.CollectedField, obj *es.GQLEvent) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Event_id(ctx, field)
if err != nil {
return graphql.Null
@ -891,7 +900,7 @@ func (ec *executionContext) _Event_id(ctx context.Context, field graphql.Collect
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.ID, nil
return obj.ID(), nil
})
if err != nil {
ec.Error(ctx, err)
@ -912,7 +921,7 @@ func (ec *executionContext) fieldContext_Event_id(ctx context.Context, field gra
fc = &graphql.FieldContext{
Object: "Event",
Field: field,
IsMethod: false,
IsMethod: true,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type ID does not have child fields")
@ -921,7 +930,7 @@ func (ec *executionContext) fieldContext_Event_id(ctx context.Context, field gra
return fc, nil
}
func (ec *executionContext) _Event_eventID(ctx context.Context, field graphql.CollectedField, obj *model.Event) (ret graphql.Marshaler) {
func (ec *executionContext) _Event_eventID(ctx context.Context, field graphql.CollectedField, obj *es.GQLEvent) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Event_eventID(ctx, field)
if err != nil {
return graphql.Null
@ -935,7 +944,7 @@ func (ec *executionContext) _Event_eventID(ctx context.Context, field graphql.Co
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.EventID, nil
return obj.EventID(), nil
})
if err != nil {
ec.Error(ctx, err)
@ -956,7 +965,7 @@ func (ec *executionContext) fieldContext_Event_eventID(ctx context.Context, fiel
fc = &graphql.FieldContext{
Object: "Event",
Field: field,
IsMethod: false,
IsMethod: true,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type String does not have child fields")
@ -965,7 +974,7 @@ func (ec *executionContext) fieldContext_Event_eventID(ctx context.Context, fiel
return fc, nil
}
func (ec *executionContext) _Event_values(ctx context.Context, field graphql.CollectedField, obj *model.Event) (ret graphql.Marshaler) {
func (ec *executionContext) _Event_values(ctx context.Context, field graphql.CollectedField, obj *es.GQLEvent) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Event_values(ctx, field)
if err != nil {
return graphql.Null
@ -979,7 +988,7 @@ func (ec *executionContext) _Event_values(ctx context.Context, field graphql.Col
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Values, nil
return obj.Values(), nil
})
if err != nil {
ec.Error(ctx, err)
@ -1000,7 +1009,7 @@ func (ec *executionContext) fieldContext_Event_values(ctx context.Context, field
fc = &graphql.FieldContext{
Object: "Event",
Field: field,
IsMethod: false,
IsMethod: true,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Map does not have child fields")
@ -1009,8 +1018,8 @@ func (ec *executionContext) fieldContext_Event_values(ctx context.Context, field
return fc, nil
}
func (ec *executionContext) _Event_eventMeta(ctx context.Context, field graphql.CollectedField, obj *model.Event) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Event_eventMeta(ctx, field)
func (ec *executionContext) _Event_bytes(ctx context.Context, field graphql.CollectedField, obj *es.GQLEvent) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Event_bytes(ctx, field)
if err != nil {
return graphql.Null
}
@ -1023,7 +1032,51 @@ func (ec *executionContext) _Event_eventMeta(ctx context.Context, field graphql.
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.EventMeta, nil
return obj.Bytes()
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(string)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Event_bytes(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Event",
Field: field,
IsMethod: true,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type String does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Event_meta(ctx context.Context, field graphql.CollectedField, obj *es.GQLEvent) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Event_meta(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Meta(), nil
})
if err != nil {
ec.Error(ctx, err)
@ -1040,11 +1093,11 @@ func (ec *executionContext) _Event_eventMeta(ctx context.Context, field graphql.
return ec.marshalNMeta2ᚖgithubᚗcomᚋsourᚑisᚋevᚋpkgᚋesᚋeventᚐMeta(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Event_eventMeta(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
func (ec *executionContext) fieldContext_Event_meta(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Event",
Field: field,
IsMethod: false,
IsMethod: true,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
@ -2269,7 +2322,7 @@ func (ec *executionContext) _Subscription_eventAdded(ctx context.Context, field
}
return func(ctx context.Context) graphql.Marshaler {
select {
case res, ok := <-resTmp.(<-chan *model.Event):
case res, ok := <-resTmp.(<-chan *es.GQLEvent):
if !ok {
return nil
}
@ -2277,7 +2330,7 @@ func (ec *executionContext) _Subscription_eventAdded(ctx context.Context, field
w.Write([]byte{'{'})
graphql.MarshalString(field.Alias).MarshalGQL(w)
w.Write([]byte{':'})
ec.marshalOEvent2ᚖgithubᚗcomᚋsourᚑisᚋevᚋinternalᚋgraphᚋmodelᚐEvent(ctx, field.Selections, res).MarshalGQL(w)
ec.marshalOEvent2ᚖgithubᚗcomᚋsourᚑisᚋevᚋpkgᚋesᚐGQLEvent(ctx, field.Selections, res).MarshalGQL(w)
w.Write([]byte{'}'})
})
case <-ctx.Done():
@ -2300,8 +2353,10 @@ func (ec *executionContext) fieldContext_Subscription_eventAdded(ctx context.Con
return ec.fieldContext_Event_eventID(ctx, field)
case "values":
return ec.fieldContext_Event_values(ctx, field)
case "eventMeta":
return ec.fieldContext_Event_eventMeta(ctx, field)
case "bytes":
return ec.fieldContext_Event_bytes(ctx, field)
case "meta":
return ec.fieldContext_Event_meta(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type Event", field.Name)
},
@ -4263,9 +4318,7 @@ func (ec *executionContext) _Edge(ctx context.Context, sel ast.SelectionSet, obj
switch obj := (obj).(type) {
case nil:
return graphql.Null
case model.Event:
return ec._Event(ctx, sel, &obj)
case *model.Event:
case *es.GQLEvent:
if obj == nil {
return graphql.Null
}
@ -4321,7 +4374,7 @@ func (ec *executionContext) _Connection(ctx context.Context, sel ast.SelectionSe
var eventImplementors = []string{"Event", "Edge"}
func (ec *executionContext) _Event(ctx context.Context, sel ast.SelectionSet, obj *model.Event) graphql.Marshaler {
func (ec *executionContext) _Event(ctx context.Context, sel ast.SelectionSet, obj *es.GQLEvent) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, eventImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
@ -4350,9 +4403,16 @@ func (ec *executionContext) _Event(ctx context.Context, sel ast.SelectionSet, ob
if out.Values[i] == graphql.Null {
invalids++
}
case "eventMeta":
case "bytes":
out.Values[i] = ec._Event_eventMeta(ctx, field, obj)
out.Values[i] = ec._Event_bytes(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "meta":
out.Values[i] = ec._Event_meta(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
@ -5658,7 +5718,7 @@ func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast
return res
}
func (ec *executionContext) marshalOEvent2ᚖgithubᚗcomᚋsourᚑisᚋevᚋinternalᚋgraphᚋmodelᚐEvent(ctx context.Context, sel ast.SelectionSet, v *model.Event) graphql.Marshaler {
func (ec *executionContext) marshalOEvent2ᚖgithubᚗcomᚋsourᚑisᚋevᚋpkgᚋesᚐGQLEvent(ctx context.Context, sel ast.SelectionSet, v *es.GQLEvent) graphql.Marshaler {
if v == nil {
return graphql.Null
}

View File

@ -1,16 +1,3 @@
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
package model
import (
"github.com/sour-is/ev/pkg/es/event"
)
type Event struct {
ID string `json:"id"`
EventID string `json:"eventID"`
Values map[string]interface{} `json:"values"`
EventMeta *event.Meta `json:"eventMeta"`
}
func (Event) IsEdge() {}

28
main.go
View File

@ -68,6 +68,8 @@ func run(ctx context.Context) error {
enable := set(strings.Fields(env("EV_ENABLE", "salty msgbus gql peers"))...)
var svcs []interface{ RegisterHTTP(*http.ServeMux) }
svcs = append(svcs, es)
if enable.Has("salty") {
span.AddEvent("Enable Salty")
salty, err := salty.New(ctx, es, path.Join(env("EV_BASE_URL", "http://"+s.Addr), "inbox"))
@ -148,10 +150,15 @@ func env(name, defaultValue string) string {
return defaultValue
}
func httpMux(fns ...interface{ RegisterHTTP(*http.ServeMux) }) http.Handler {
mux := http.NewServeMux()
mux := newMux()
for _, fn := range fns {
fn.RegisterHTTP(mux)
fn.RegisterHTTP(mux.ServeMux)
if fn, ok := fn.(interface{ RegisterAPIv1(*http.ServeMux) }); ok {
fn.RegisterAPIv1(mux.api)
}
}
return cors.AllowAll().Handler(mux)
}
@ -183,3 +190,20 @@ func (s Set[T]) String() string {
b.WriteString(")")
return b.String()
}
type mux struct {
*http.ServeMux
api *http.ServeMux
}
func newMux() *mux {
mux := &mux{
api: http.NewServeMux(),
ServeMux: http.NewServeMux(),
}
mux.Handle("/api/v1/", http.StripPrefix("/api/v1/", mux.api))
return mux
}
func (m mux) HandleAPIv1(pattern string, handler http.Handler) {
m.api.Handle(pattern, handler)
}

View File

@ -14,10 +14,11 @@ extend type Subscription {
eventAdded(streamID: String! after: Int! = -1): Event
}
type Event implements Edge {
type Event implements Edge @goModel(model: "github.com/sour-is/ev/pkg/es.GQLEvent") {
id: ID!
eventID: String!
values: Map!
eventMeta: Meta!
bytes: String!
meta: Meta!
}

View File

@ -247,11 +247,22 @@ func embedJSON(s string) json.RawMessage {
return []byte(fmt.Sprintf(`"%s"`, strings.Replace(s, `"`, `\"`, -1)))
}
func values(e Event) map[string]any {
m := make(map[string]any)
v := reflect.ValueOf(e)
for _, idx := range reflect.VisibleFields(v) {
field := v.FieldByIndex(idx.Index)
m[field.]
func Values(e Event) map[string]any {
var a any = e
if e, ok := e.(interface{ Values() any }); ok {
a = e.Values()
}
m := make(map[string]any)
v := reflect.Indirect(reflect.ValueOf(a))
for _, idx := range reflect.VisibleFields(v.Type()) {
if !idx.IsExported() {
continue
}
field := v.FieldByIndex(idx.Index)
m[idx.Name] = field.Interface()
}
return m
}

128
pkg/es/graph.go Normal file
View File

@ -0,0 +1,128 @@
package es
import (
"context"
"fmt"
"net/http"
"time"
"github.com/sour-is/ev/internal/logz"
"github.com/sour-is/ev/pkg/es/event"
"github.com/sour-is/ev/pkg/gql"
)
type EventResolver interface {
Events(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error)
EventAdded(ctx context.Context, streamID string, after int64) (<-chan *GQLEvent, error)
}
func (es *EventStore) Events(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error) {
ctx, span := logz.Span(ctx)
defer span.End()
lis, err := es.Read(ctx, streamID, paging.GetIdx(0), paging.GetCount(30))
if err != nil {
span.RecordError(err)
return nil, err
}
edges := make([]gql.Edge, 0, len(lis))
for i := range lis {
span.AddEvent(fmt.Sprint("event ", i, " of ", len(lis)))
edges = append(edges, &GQLEvent{lis[i]})
}
var first, last uint64
if first, err = es.FirstIndex(ctx, streamID); err != nil {
span.RecordError(err)
return nil, err
}
if last, err = es.LastIndex(ctx, streamID); err != nil {
span.RecordError(err)
return nil, err
}
return &gql.Connection{
Paging: &gql.PageInfo{
Next: lis.Last().EventMeta().Position < last,
Prev: lis.First().EventMeta().Position > first,
Begin: lis.First().EventMeta().Position,
End: lis.Last().EventMeta().Position,
},
Edges: edges,
}, nil
}
func (e *EventStore) EventAdded(ctx context.Context, streamID string, after int64) (<-chan *GQLEvent, error) {
ctx, span := logz.Span(ctx)
defer span.End()
es := e.EventStream()
if es == nil {
return nil, fmt.Errorf("EventStore does not implement streaming")
}
sub, err := es.Subscribe(ctx, streamID, after)
if err != nil {
span.RecordError(err)
return nil, err
}
ch := make(chan *GQLEvent)
go func() {
ctx, span := logz.Span(ctx)
defer span.End()
defer func() {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
err := sub.Close(ctx)
span.RecordError(err)
}()
for sub.Recv(ctx) {
events, err := sub.Events(ctx)
if err != nil {
span.RecordError(err)
break
}
span.AddEvent(fmt.Sprintf("received %d events", len(events)))
for i := range events {
select {
case ch <- &GQLEvent{events[i]}:
continue
case <-ctx.Done():
return
}
}
}
}()
return ch, nil
}
func (*EventStore) RegisterHTTP(*http.ServeMux) {}
type GQLEvent struct {
e event.Event
}
func (e *GQLEvent) ID() string {
return "Event/" + e.e.EventMeta().GetEventID()
}
func (e *GQLEvent) EventID() string {
return e.e.EventMeta().GetEventID()
}
func (e *GQLEvent) Values() map[string]interface{} {
return event.Values(e.e)
}
func (e *GQLEvent) Bytes() (string, error) {
b, err := e.e.MarshalBinary()
return string(b), err
}
func (e *GQLEvent) Meta() *event.Meta {
meta := e.e.EventMeta()
return &meta
}
func (e *GQLEvent) IsEdge() {}