From 7315759b20833cb66223ebf305da9cd70f576e34 Mon Sep 17 00:00:00 2001 From: Jon Lundy Date: Wed, 23 Nov 2022 13:51:55 -0700 Subject: [PATCH] refactor: moving items around into related files/packages --- app/gql/resolver.go | 124 +-- app/mercury/mercury.graphqls | 14 +- app/msgbus/service.go | 7 +- app/peerfinder/assets/bootstrap.min.css.map | 1 + app/peerfinder/assets/peerfinder.css | 28 +- app/peerfinder/ev-info.go | 75 ++ app/peerfinder/ev-peer.go | 87 +++ app/peerfinder/{peer.go => ev-request.go} | 162 +--- app/peerfinder/http.go | 559 ++++++++++++++ app/peerfinder/jobs.go | 217 ++++++ app/peerfinder/layouts/main.tpl | 2 +- app/peerfinder/pages/home.tpl | 2 +- app/peerfinder/pages/req.tpl | 4 +- app/peerfinder/service.go | 814 ++------------------ app/salty/service.go | 4 +- internal/graph/generated/generated.go | 8 + main.go | 59 +- pkg/es/driver/driver.go | 2 +- pkg/es/driver/streamer/streamer.go | 95 ++- pkg/es/graph.go | 12 +- {app => pkg}/gql/graphiql/playground.go | 0 {app => pkg}/gql/playground/playground.go | 0 pkg/gql/resolver/resolver.go | 141 ++++ 23 files changed, 1320 insertions(+), 1097 deletions(-) create mode 100644 app/peerfinder/assets/bootstrap.min.css.map create mode 100644 app/peerfinder/ev-info.go create mode 100644 app/peerfinder/ev-peer.go rename app/peerfinder/{peer.go => ev-request.go} (60%) create mode 100644 app/peerfinder/http.go create mode 100644 app/peerfinder/jobs.go rename {app => pkg}/gql/graphiql/playground.go (100%) rename {app => pkg}/gql/playground/playground.go (100%) create mode 100644 pkg/gql/resolver/resolver.go diff --git a/app/gql/resolver.go b/app/gql/resolver.go index c3ab15a..560af59 100644 --- a/app/gql/resolver.go +++ b/app/gql/resolver.go @@ -2,30 +2,14 @@ package gql import ( "context" - "fmt" - "net/http" - "os" - "reflect" - "runtime/debug" - "time" "github.com/99designs/gqlgen/graphql" - "github.com/99designs/gqlgen/graphql/handler" - "github.com/99designs/gqlgen/graphql/handler/extension" - "github.com/99designs/gqlgen/graphql/handler/lru" - "github.com/99designs/gqlgen/graphql/handler/transport" - "github.com/gorilla/websocket" - "github.com/ravilushqa/otelgqlgen" - "github.com/vektah/gqlparser/v2/gqlerror" - - "github.com/sour-is/ev/app/gql/graphiql" - "github.com/sour-is/ev/app/gql/playground" "github.com/sour-is/ev/app/msgbus" "github.com/sour-is/ev/app/salty" "github.com/sour-is/ev/internal/graph/generated" - "github.com/sour-is/ev/internal/lg" "github.com/sour-is/ev/pkg/es" "github.com/sour-is/ev/pkg/gql" + "github.com/sour-is/ev/pkg/gql/resolver" ) type Resolver struct { @@ -34,36 +18,6 @@ type Resolver struct { es.EventResolver } -func New(ctx context.Context, resolvers ...interface{ RegisterHTTP(*http.ServeMux) }) (*Resolver, error) { - _, span := lg.Span(ctx) - defer span.End() - - r := &Resolver{} - v := reflect.ValueOf(r) - v = reflect.Indirect(v) - noop := reflect.ValueOf(&noop{}) - -outer: - for _, idx := range reflect.VisibleFields(v.Type()) { - field := v.FieldByIndex(idx.Index) - - for i := range resolvers { - rs := reflect.ValueOf(resolvers[i]) - - if field.IsNil() && rs.Type().Implements(field.Type()) { - span.AddEvent(fmt.Sprint("found ", field.Type().Name())) - field.Set(rs) - continue outer - } - } - - span.AddEvent(fmt.Sprint("default ", field.Type().Name())) - field.Set(noop) - } - - return r, nil -} - // Query returns generated.QueryResolver implementation. func (r *Resolver) Query() generated.QueryResolver { return r } @@ -73,53 +27,21 @@ func (r *Resolver) Mutation() generated.MutationResolver { return r } // Subscription returns generated.SubscriptionResolver implementation. func (r *Resolver) Subscription() generated.SubscriptionResolver { return r } -// ChainMiddlewares will check all embeded resolvers for a GetMiddleware func and add to handler. -func (r *Resolver) ChainMiddlewares(h http.Handler) http.Handler { - v := reflect.ValueOf(r) // Get reflected value of *Resolver - v = reflect.Indirect(v) // Get the pointed value (returns a zero value on nil) - n := v.NumField() // Get number of fields to iterate over. - for i := 0; i < n; i++ { - f := v.Field(i) - if !f.CanInterface() { // Skip non-interface types. - continue - } - if iface, ok := f.Interface().(interface { - GetMiddleware() func(http.Handler) http.Handler - }); ok { - h = iface.GetMiddleware()(h) // Append only items that fulfill the interface. - } - } - - return h +// func (r *Resolver) isResolver() {} +func (r *Resolver) ExecutableSchema() graphql.ExecutableSchema { + return generated.NewExecutableSchema(generated.Config{Resolvers: r}) } - -func (r *Resolver) RegisterHTTP(mux *http.ServeMux) { - gql := NewServer(generated.NewExecutableSchema(generated.Config{Resolvers: r})) - - gql.SetRecoverFunc(NoopRecover) - gql.Use(otelgqlgen.Middleware()) - mux.Handle("/graphiql", graphiql.Handler("GraphiQL playground", "/gql")) - mux.Handle("/gql", lg.Htrace(r.ChainMiddlewares(gql), "gql")) - mux.Handle("/playground", playground.Handler("GraphQL playground", "/gql")) +func (r *Resolver) BaseResolver() resolver.IsResolver { + return &noop{} } type noop struct{} -func NoopRecover(ctx context.Context, err interface{}) error { - if err, ok := err.(string); ok && err == "not implemented" { - return gqlerror.Errorf("not implemented") - } - fmt.Fprintln(os.Stderr, err) - fmt.Fprintln(os.Stderr) - debug.PrintStack() - - return gqlerror.Errorf("internal system error") -} - var _ msgbus.MsgbusResolver = (*noop)(nil) var _ salty.SaltyResolver = (*noop)(nil) var _ es.EventResolver = (*noop)(nil) +func (*noop) IsResolver() {} func (*noop) CreateSaltyUser(ctx context.Context, nick string, pubkey string) (*salty.SaltyUser, error) { panic("not implemented") } @@ -141,35 +63,3 @@ func (*noop) EventAdded(ctx context.Context, streamID string, after int64) (<-ch func (*noop) TruncateStream(ctx context.Context, streamID string, index int64) (bool, error) { panic("not implemented") } -func (*noop) RegisterHTTP(*http.ServeMux) {} - -func NewServer(es graphql.ExecutableSchema) *handler.Server { - srv := handler.New(es) - - srv.AddTransport(transport.Websocket{ - Upgrader: websocket.Upgrader{ - CheckOrigin: func(r *http.Request) bool { - switch r.Header.Get("Origin") { - case "https://ev.sour.is", "https://www.graphqlbin.com": - return true - default: - return false - } - }, - }, - KeepAlivePingInterval: 10 * time.Second, - }) - srv.AddTransport(transport.Options{}) - srv.AddTransport(transport.GET{}) - srv.AddTransport(transport.POST{}) - srv.AddTransport(transport.MultipartForm{}) - - srv.SetQueryCache(lru.New(1000)) - - srv.Use(extension.Introspection{}) - srv.Use(extension.AutomaticPersistedQuery{ - Cache: lru.New(100), - }) - - return srv -} diff --git a/app/mercury/mercury.graphqls b/app/mercury/mercury.graphqls index d35001c..84ddfe5 100644 --- a/app/mercury/mercury.graphqls +++ b/app/mercury/mercury.graphqls @@ -1,8 +1,8 @@ -extend type Query{ - keys(namespace: String!) [String!]! - get(namespace: String! keys: [String!]) [String]! -} +# extend type Query{ +# keys(namespace: String!) [String!]! +# get(namespace: String! keys: [String!]) [String]! +# } -extend type Mutation{ - set(namespace: String! key: String! value: String): Bool! -} \ No newline at end of file +# extend type Mutation{ +# set(namespace: String! key: String! value: String): Bool! +# } \ No newline at end of file diff --git a/app/msgbus/service.go b/app/msgbus/service.go index 9e78ad5..58e9217 100644 --- a/app/msgbus/service.go +++ b/app/msgbus/service.go @@ -35,7 +35,7 @@ type service struct { type MsgbusResolver interface { Posts(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error) PostAdded(ctx context.Context, streamID string, after int64) (<-chan *PostEvent, error) - RegisterHTTP(mux *http.ServeMux) + IsResolver() } func New(ctx context.Context, es *es.EventStore) (*service, error) { @@ -87,6 +87,7 @@ var upgrader = websocket.Upgrader{ }, } +func (s *service) IsResolver() {} func (s *service) RegisterHTTP(mux *http.ServeMux) { mux.Handle("/inbox/", lg.Htrace(http.StripPrefix("/inbox/", s), "inbox")) } @@ -197,7 +198,7 @@ func (r *service) PostAdded(ctx context.Context, streamID string, after int64) ( }() } - for sub.Recv(ctx) { + for <-sub.Recv(ctx) { events, err := sub.Events(ctx) if err != nil { span.RecordError(err) @@ -425,7 +426,7 @@ func (s *service) websocket(w http.ResponseWriter, r *http.Request) { } span.AddEvent("start ws") - for sub.Recv(ctx) { + for <-sub.Recv(ctx) { events, err := sub.Events(ctx) if err != nil { break diff --git a/app/peerfinder/assets/bootstrap.min.css.map b/app/peerfinder/assets/bootstrap.min.css.map new file mode 100644 index 0000000..5f49bb3 --- /dev/null +++ b/app/peerfinder/assets/bootstrap.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["less/normalize.less","less/print.less","bootstrap.css","dist/css/bootstrap.css","less/glyphicons.less","less/scaffolding.less","less/mixins/vendor-prefixes.less","less/mixins/tab-focus.less","less/mixins/image.less","less/type.less","less/mixins/text-emphasis.less","less/mixins/background-variant.less","less/mixins/text-overflow.less","less/code.less","less/grid.less","less/mixins/grid.less","less/mixins/grid-framework.less","less/tables.less","less/mixins/table-row.less","less/forms.less","less/mixins/forms.less","less/buttons.less","less/mixins/buttons.less","less/mixins/opacity.less","less/component-animations.less","less/dropdowns.less","less/mixins/nav-divider.less","less/mixins/reset-filter.less","less/button-groups.less","less/mixins/border-radius.less","less/input-groups.less","less/navs.less","less/navbar.less","less/mixins/nav-vertical-align.less","less/utilities.less","less/breadcrumbs.less","less/pagination.less","less/mixins/pagination.less","less/pager.less","less/labels.less","less/mixins/labels.less","less/badges.less","less/jumbotron.less","less/thumbnails.less","less/alerts.less","less/mixins/alerts.less","less/progress-bars.less","less/mixins/gradients.less","less/mixins/progress-bar.less","less/media.less","less/list-group.less","less/mixins/list-group.less","less/panels.less","less/mixins/panels.less","less/responsive-embed.less","less/wells.less","less/close.less","less/modals.less","less/tooltip.less","less/mixins/reset-text.less","less/popovers.less","less/carousel.less","less/mixins/clearfix.less","less/mixins/center-block.less","less/mixins/hide-text.less","less/responsive-utilities.less","less/mixins/responsive-visibility.less"],"names":[],"mappings":"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DG,edkkDH,MAAA,aclkDG,edqkDH,MAAA,acrkDG,edwkDH,MAAA,IcxkDG,ed2kDH,MAAA,ac3kDG,ed8kDH,MAAA,ac9kDG,edilDH,MAAA,IcjlDG,edolDH,MAAA,ac/kDG,edklDH,MAAA,YcjmDG,edomDH,MAAA,KcpmDG,gBdumDH,KAAA,KcvmDG,gBd0mDH,KAAA,ac1mDG,gBd6mDH,KAAA,ac7mDG,edgnDH,KAAA,IchnDG,edmnDH,KAAA,acnnDG,edsnDH,KAAA,actnDG,edynDH,KAAA,IcznDG,ed4nDH,KAAA,ac5nDG,ed+nDH,KAAA,ac/nDG,edkoDH,KAAA,IcloDG,edqoDH,KAAA,achoDG,edmoDH,KAAA,YcpnDG,edunDH,KAAA,KcvnDG,kBd0nDH,YAAA,Kc1nDG,kBd6nDH,YAAA,ac7nDG,kBdgoDH,YAAA,achoDG,iBdmoDH,YAAA,IcnoDG,iBdsoDH,YAAA,actoDG,iBdyoDH,YAAA,aczoDG,iBd4oDH,YAAA,Ic5oDG,iBd+oDH,YAAA,ac/oDG,iBdkpDH,YAAA,aclpDG,iBdqpDH,YAAA,IcrpDG,iBdwpDH,YAAA,acxpDG,iBd2pDH,YAAA,Yc7rDG,iBACE,YAAA,EAOJ,yBACE,UAAA,WAAA,WAAA,WAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,Ud2rDD,MAAA,Kc3rDC,Wd8rDD,MAAA,Kc9rDC,WdisDD,MAAA,acjsDC,WdosDD,MAAA,acpsDC,UdusDD,MAAA,IcvsDC,Ud0sDD,MAAA,ac1sDC,Ud6sDD,MAAA,ac7sDC,UdgtDD,MAAA,IchtDC,UdmtDD,MAAA,acntDC,UdstDD,MAAA,acttDC,UdytDD,MAAA,IcztDC,Ud4tDD,MAAA,ac7sDC,UdgtDD,MAAA,YchtDC,gBdmtDD,MAAA,KcntDC,gBdstDD,MAAA,acttDC,gBdytDD,MAAA,acztDC,ed4tDD,MAAA,Ic5tDC,ed+tDD,MAAA,ac/tDC,edkuDD,MAAA,acluDC,edquDD,MAAA,IcruDC,edwuDD,MAAA,acxuDC,ed2uDD,MAAA,ac3uDC,ed8uDD,MAAA,Ic9uDC,edivDD,MAAA,ac5uDC,ed+uDD,MAAA,Yc9vDC,ediwDD,MAAA,KcjwDC,gBdowDD,KAAA,KcpwDC,gBduwDD,KAAA,acvwDC,gBd0wDD,KAAA,ac1wDC,ed6wDD,KAAA,Ic7wDC,edgxDD,KAAA,achxDC,edmxDD,KAAA,acnxDC,edsxDD,KAAA,IctxDC,edyxDD,KAAA,aczxDC,ed4xDD,KAAA,ac5xDC,ed+xDD,KAAA,Ic/xDC,edkyDD,KAAA,ac7xDC,edgyDD,KAAA,YcjxDC,edoxDD,KAAA,KcpxDC,kBduxDD,YAAA,KcvxDC,kBd0xDD,YAAA,ac1xDC,kBd6xDD,YAAA,ac7xDC,iBdgyDD,YAAA,IchyDC,iBdmyDD,YAAA,acnyDC,iBdsyDD,YAAA,actyDC,iBdyyDD,YAAA,IczyDC,iBd4yDD,YAAA,ac5yDC,iBd+yDD,YAAA,ac/yDC,iBdkzDD,YAAA,IclzDC,iBdqzDD,YAAA,acrzDC,iBdwzDD,YAAA,YY/yDD,iBE3CE,YAAA,GAQF,yBACE,UAAA,WAAA,WAAA,WAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,Udy1DD,MAAA,Kcz1DC,Wd41DD,MAAA,Kc51DC,Wd+1DD,MAAA,ac/1DC,Wdk2DD,MAAA,acl2DC,Udq2DD,MAAA,Icr2DC,Udw2DD,MAAA,acx2DC,Ud22DD,MAAA,ac32DC,Ud82DD,MAAA,Ic92DC,Udi3DD,MAAA,acj3DC,Udo3DD,MAAA,acp3DC,Udu3DD,MAAA,Icv3DC,Ud03DD,MAAA,ac32DC,Ud82DD,MAAA,Yc92DC,gBdi3DD,MAAA,Kcj3DC,gBdo3DD,MAAA,acp3DC,gBdu3DD,MAAA,acv3DC,ed03DD,MAAA,Ic13DC,ed63DD,MAAA,ac73DC,edg4DD,MAAA,ach4DC,edm4DD,MAAA,Icn4DC,eds4DD,MAAA,act4DC,edy4DD,MAAA,acz4DC,ed44DD,MAAA,Ic54DC,ed+4DD,MAAA,ac14DC,ed64DD,MAAA,Yc55DC,ed+5DD,MAAA,Kc/5DC,gBdk6DD,KAAA,Kcl6DC,gBdq6DD,KAAA,acr6DC,gBdw6DD,KAAA,acx6DC,ed26DD,KAAA,Ic36DC,ed86DD,KAAA,ac96DC,edi7DD,KAAA,acj7DC,edo7DD,KAAA,Icp7DC,edu7DD,KAAA,acv7DC,ed07DD,KAAA,ac17DC,ed67DD,KAAA,Ic77DC,edg8DD,KAAA,ac37DC,ed87DD,KAAA,Yc/6DC,edk7DD,KAAA,Kcl7DC,kBdq7DD,YAAA,Kcr7DC,kBdw7DD,YAAA,acx7DC,kBd27DD,YAAA,ac37DC,iBd87DD,YAAA,Ic97DC,iBdi8DD,YAAA,acj8DC,iBdo8DD,YAAA,acp8DC,iBdu8DD,YAAA,Icv8DC,iBd08DD,YAAA,ac18DC,iBd68DD,YAAA,ac78DC,iBdg9DD,YAAA,Ich9DC,iBdm9DD,YAAA,acn9DC,iBds9DD,YAAA,YY18DD,iBE9CE,YAAA,GAQF,0BACE,UAAA,WAAA,WAAA,WAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,UAAA,Udu/DD,MAAA,Kcv/DC,Wd0/DD,MAAA,Kc1/DC,Wd6/DD,MAAA,ac7/DC,WdggED,MAAA,achgEC,UdmgED,MAAA,IcngEC,UdsgED,MAAA,actgEC,UdygED,MAAA,aczgEC,Ud4gED,MAAA,Ic5gEC,Ud+gED,MAAA,ac/gEC,UdkhED,MAAA,aclhEC,UdqhED,MAAA,IcrhEC,UdwhED,MAAA,aczgEC,Ud4gED,MAAA,Yc5gEC,gBd+gED,MAAA,Kc/gEC,gBdkhED,MAAA,aclhEC,gBdqhED,MAAA,acrhEC,edwhED,MAAA,IcxhEC,ed2hED,MAAA,ac3hEC,ed8hED,MAAA,ac9hEC,ediiED,MAAA,IcjiEC,edoiED,MAAA,acpiEC,eduiED,MAAA,acviEC,ed0iED,MAAA,Ic1iEC,ed6iED,MAAA,acxiEC,ed2iED,MAAA,Yc1jEC,ed6jED,MAAA,Kc7jEC,gBdgkED,KAAA,KchkEC,gBdmkED,KAAA,acnkEC,gBdskED,KAAA,actkEC,edykED,KAAA,IczkEC,ed4kED,KAAA,ac5kEC,ed+kED,KAAA,ac/kEC,edklED,KAAA,IcllEC,edqlED,KAAA,acrlEC,edwlED,KAAA,acxlEC,ed2lED,KAAA,Ic3lEC,ed8lED,KAAA,aczlEC,ed4lED,KAAA,Yc7kEC,e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pC,cAGA,ejB8DA,wBACQ,OAAA,YJ65FT,OAAA,kBmB56FG,mBAAA,KlBw7FM,WAAY,KkBt7FhB,QAAA,IASN,eC3DE,yBACA,eAAA,KpBo+FD,aoBj+FC,MAAA,KnB6+FA,iBAAkB,KmB3+FhB,aAAA,KpBq+FH,mBoBn+FO,mBAEN,MAAA,KACE,iBAAA,QACA,aAAA,QpBo+FH,mBoBj+FC,MAAA,KnB6+FA,iBAAkB,QAClB,aAAc,QmBz+FR,oBADJ,oBpBo+FH,mCoBj+FG,MAAA,KnB6+FF,iBAAkB,QAClB,aAAc,QmBz+FN,0BnB++FV,0BAHA,0BmB7+FM,0BnB++FN,0BAHA,0BDFC,yCoB3+FK,yCnB++FN,yCmB1+FE,MAAA,KnBk/FA,iBAAkB,QAClB,aAAc,QmB3+FZ,oBpBm+FH,oBoBn+FG,mCnBg/FF,iBAAkB,KmB5+FV,4BnBi/FV,4BAHA,4BDHC,6BCOD,6BAHA,6BkB99FA,sCClBM,sCnBi/FN,sCmB3+FI,iBAAA,KACA,aAAA,KDcJ,oBC9DE,MAAA,KACA,iBAAA,KpB6hGD,aoB1hGC,MAAA,KnBsiGA,iBAAkB,QmBpiGhB,aAAA,QpB8hGH,mBoB5hGO,mBAEN,MAAA,KACE,iBAAA,QACA,aAAA,QpB6hGH,mBoB1hGC,MAAA,KnBsiGA,iBAAkB,QAClB,aAAc,QmBliGR,oBADJ,oBpB6hGH,mCoB1hGG,MAAA,KnBsiGF,iBAAkB,QAClB,aAAc,QmBliGN,0BnBwiGV,0BAHA,0BmBtiGM,0BnBwiGN,0BAHA,0BDFC,yCoBpiGK,yCnBwiGN,yCmBniGE,MAAA,KnB2iGA,iBAAkB,QAClB,aAAc,QmBpiGZ,oBpB4hGH,oBoB5hGG,mCnByiGF,iBAAkB,KmBriGV,4BnB0iGV,4BAHA,4BDHC,6BCOD,6BAHA,6BkBphGA,sCCrBM,sCnB0iGN,sCmBpiGI,iBAAA,QACA,aAAA,QDkBJ,oBClEE,MAAA,QACA,iBAAA,KpBslGD,aoBnlGC,MAAA,KnB+lGA,iBAAkB,QmB7lGhB,aAAA,QpBulGH,mBoBrlGO,mBAEN,MAAA,KACE,iBAAA,QACA,aAAA,QpBslGH,mBoBnlGC,MAAA,KnB+lGA,iBAAkB,QAClB,aAAc,QmB3lGR,oBADJ,oBpBslGH,mCoBnlGG,MAAA,KnB+lGF,iBAAkB,QAClB,aAAc,QmB3lGN,0BnBimGV,0BAHA,0BmB/lGM,0BnBimGN,0BAHA,0BDFC,yCoB7lGK,yCnBimGN,yCmB5lGE,MAAA,KnBomGA,iBAAkB,QAClB,aAAc,QmB7lGZ,oBpBqlGH,oBoBrlGG,mCnBkmGF,iBAAkB,KmB9lGV,4BnBmmGV,4BAHA,4BDHC,6BCOD,6BAHA,6BkBzkGA,sCCzBM,sCnBmmGN,sCmB7lGI,iBAAA,QACA,aAAA,QDsBJ,oBCtEE,MAAA,QACA,iBAAA,KpB+oGD,UoB5oGC,MAAA,KnBwpGA,iBAAkB,QmBtpGhB,aAAA,QpBgpGH,gBoB9oGO,gBAEN,MAAA,KACE,iBAAA,QACA,aAAA,QpB+oGH,gBoB5oGC,MAAA,KnBwpGA,iBAAkB,QAClB,aAAc,QmBppGR,iBADJ,iBpB+oGH,gCoB5oGG,MAAA,KnBwpGF,iBAAkB,QAClB,aAAc,QmBppGN,uBnB0pGV,uBAHA,uBmBxpGM,uBnB0pGN,uBAHA,uBDFC,sCoBtpGK,sCnB0pGN,sCmBrpGE,MAAA,KnB6pGA,iBAAkB,QAClB,aAAc,QmBtpGZ,iBpB8oGH,iBoB9oGG,gCnB2pGF,iBAAkB,KmBvpGV,yBnB4pGV,yBAHA,yBDHC,0BCOD,0BAHA,0BkB9nGA,mCC7BM,mCnB4pGN,mCmBtpGI,iBAAA,QACA,aAAA,QD0BJ,iBC1EE,MAAA,QACA,iBAAA,KpBwsGD,aoBrsGC,MAAA,KnBitGA,iBAAkB,QmB/sGhB,aAAA,QpBysGH,mBoBvsGO,mBAEN,MAAA,KACE,iBAAA,QACA,aAAA,QpBwsGH,mBoBrsGC,MAAA,KnBitGA,iBAAkB,QAClB,aAAc,QmB7sGR,oBADJ,oBpBwsGH,mCoBrsGG,MAAA,KnBitGF,iBAAkB,QAClB,aAAc,QmB7sGN,0BnBmtGV,0BAHA,0BmBjtGM,0BnBmtGN,0BAHA,0BDFC,yCoB/sGK,yCnBmtGN,yCmB9sGE,MAAA,KnBstGA,iBAAkB,QAClB,aAAc,QmB/sGZ,oBpBusGH,oBoBvsGG,mCnBotGF,iBAAkB,KmBh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mBAKJ,qBAEE,MAAA,QvBvCA,cACC,QAAA,KAAA,K3By0KF,iBAAA,QkDxxKC,WAAY,IAAI,MAAM,KjDozKtB,2BAA4B,IiDjzK1B,0BAAA,IAHJ,mBAAA,mCAMM,cAAA,ElD2xKL,oCkDtxKG,oDjDkzKF,aAAc,IAAI,EiDhzKZ,cAAA,EvBtEL,4D3Bg2KF,4EkDpxKG,WAAA,EjDgzKF,uBAAwB,IiD9yKlB,wBAAA,IvBtEL,0D3B81KF,0EkD7yKC,cAAe,EvB1Df,2BAAA,IACC,0BAAA,IuB0FH,+EAEI,uBAAA,ElDixKH,wBAAA,EkD7wKC,wDlDgxKD,iBAAA,EC4BD,0BACE,iBAAkB,EiDryKpB,8BlD6wKC,ckD7wKD,gCjD0yKE,cAAe,EiD1yKjB,sCAQM,sBlD2wKL,wCC4BC,cAAe,K0Bx5Kf,aAAA,KuByGF,wDlDwxKC,0BC4BC,uBAAwB,IACxB,wBAAyB,IiDrzK3B,yFAoBQ,yFlD2wKP,2DkD5wKO,2DjDwyKN,uBAAwB,IACxB,wBAAyB,IAK3B,wGiDj0KA,wGjD+zKA,wGDtBC,wGCuBD,0EiDh0KA,0EjD8zKA,0EiDtyKU,0EjD8yKR,uBAAwB,IAK1B,uGiD30KA,uGjDy0KA,uGDtBC,uGCuBD,yEiD10KA,yEjDw0KA,yEiD5yKU,yEvB7HR,wBAAA,IuBiGF,sDlDwzKC,yBC4BC,2BAA4B,IAC5B,0BAA2B,IiD3yKrB,qFA1CR,qFAyCQ,wDlDsxKP,wDC4BC,2BAA4B,IAC5B,0BAA2B,IAG7B,oGDtBC,oGCwBD,oGiDj2KA,oGjD81KA,uEiDhzKU,uEjDkzKV,uEiDh2KA,uEjDs2KE,0BAA2B,IAG7B,mGDtBC,mGCwBD,mGiD32KA,mGjDw2KA,sEiDtzKU,sEjDwzKV,sEiD12KA,sEjDg3KE,2BAA4B,IiDrzK1B,0BlD8xKH,qCkDz1KD,0BAAA,qCA+DI,WAAA,IAAA,MAAA,KA/DJ,kDAAA,kDAmEI,WAAA,EAnEJ,uBAAA,yCjD83KE,OAAQ,EiDpzKA,+CjDwzKV,+CiDl4KA,+CjDo4KA,+CAEA,+CANA,+CDjBC,iECoBD,iEiDn4KA,iEjDq4KA,iEAEA,iEANA,iEAWE,YAAa,EiD9zKL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1MG,QAAS,oB/Dm2MV,c+Dr2MC,c/Ds2MH,QAAA,sB+Dj2MG,yB/Dq2MD,kBACF,QAAA,iB+Dj2MG,yB/Dq2MD,mBACF,QAAA,kBgEn6MC,yBhEu6MC,yBgEt6MD,QAAA,wBACA,+CAAU,YhE26MT,QAAA,gBC4BD,iB+Dr8MU,QAAA,gBhE86MX,c+Dx2MG,QAAS,oB/D42MV,c+D92MC,c/D+2MH,QAAA,sB+D12MG,+C/D82MD,kBACF,QAAA,iB+D12MG,+C/D82MD,mBACF,QAAA,kBgEj8MC,+ChEq8MC,yBgEp8MD,QAAA,wBACA,gDAAU,YhEy8MT,QAAA,gBC4BD,iB+Dn+MU,QAAA,gBhE48MX,c+Dj3MG,QAAS,oB/Dq3MV,c+Dv3MC,c/Dw3MH,QAAA,sB+Dn3MG,gD/Du3MD,kBACF,QAAA,iB+Dn3MG,gD/Du3MD,mBACF,QAAA,kBgE/9MC,gDhEm+MC,yBgEl+MD,QAAA,wBACA,0BAAU,YhEu+MT,QAAA,gBC4BD,iB+DjgNU,QAAA,gBhE0+MX,c+D13MG,QAAS,oB/D83MV,c+Dh4MC,c/Di4MH,QAAA,sB+D53MG,0B/Dg4MD,kBACF,QAAA,iB+D53MG,0B/Dg4MD,mBACF,QAAA,kBgEr/MC,0BhEy/MC,yBACF,QAAA,wBgE1/MC,yBhE8/MC,WACF,QAAA,gBgE//MC,+ChEmgNC,WACF,QAAA,gBgEpgNC,gDhEwgNC,WACF,QAAA,gBAGA,0B+Dn3MC,WA4BE,QAAS,gBC5LX,eAAU,QAAA,eACV,aAAU,ehE4hNT,QAAA,gBC4BD,oB+DtjNU,QAAA,gBhE+hNX,iB+Dj4MG,QAAS,oBAMX,iB/D83MD,iB+Dz2MG,QAAS,sB/D82MZ,qB+Dl4MC,QAAS,e/Dq4MV,a+D/3MC,qBAcE,QAAS,iB/Ds3MZ,sB+Dn4MC,QAAS,e/Ds4MV,a+Dh4MC,sBAOE,QAAS,kB/D83MZ,4B+D/3MC,QAAS,eCpLT,ahEujNC,4BACF,QAAA,wBC6BD,aACE,cACE,QAAS"} \ No newline at end of file diff --git a/app/peerfinder/assets/peerfinder.css b/app/peerfinder/assets/peerfinder.css index d6cb745..2c7b4c4 100644 --- a/app/peerfinder/assets/peerfinder.css +++ b/app/peerfinder/assets/peerfinder.css @@ -30,29 +30,11 @@ body { border-top: 1px solid #e5e5e5; } -/* Customize container */ -@media (min-width: 768px) { - .container { - max-width: 730px; - } +.panel-heading a { + color: white; + font-weight: bold; } + .container-narrow > hr { margin: 30px 0; -} - - -/* Responsive: Portrait tablets and up */ -@media screen and (min-width: 768px) { - /* Remove the padding we set earlier */ - .header, - .footer { - padding-right: 0; - padding-left: 0; - } - /* Space out the masthead */ - .header { - margin-bottom: 30px; - } -} - -.panel-primary a { color: white; font-weight:bold } +} \ No newline at end of file diff --git a/app/peerfinder/ev-info.go b/app/peerfinder/ev-info.go new file mode 100644 index 0000000..6f00b92 --- /dev/null +++ b/app/peerfinder/ev-info.go @@ -0,0 +1,75 @@ +package peerfinder + +import ( + "bytes" + "encoding/json" + + "github.com/tj/go-semver" + + "github.com/sour-is/ev/pkg/es/event" +) + +type Info struct { + ScriptVersion string `json:"script_version"` + + event.AggregateRoot +} + +var _ event.Aggregate = (*Info)(nil) + +func (a *Info) ApplyEvent(lis ...event.Event) { + for _, e := range lis { + switch e := e.(type) { + case *VersionChanged: + a.ScriptVersion = e.ScriptVersion + } + } +} +func (a *Info) MarshalEnviron() ([]byte, error) { + var b bytes.Buffer + + b.WriteString("SCRIPT_VERSION=") + b.WriteString(a.ScriptVersion) + b.WriteRune('\n') + + return b.Bytes(), nil +} +func (a *Info) OnUpsert() error { + if a.StreamVersion() == 0 { + event.Raise(a, &VersionChanged{ScriptVersion: initVersion}) + } + current, _ := semver.Parse(initVersion) + previous, _ := semver.Parse(a.ScriptVersion) + + if current.Compare(previous) > 0 { + event.Raise(a, &VersionChanged{ScriptVersion: initVersion}) + } + + return nil +} + +type VersionChanged struct { + ScriptVersion string `json:"script_version"` + + eventMeta event.Meta +} + +var _ event.Event = (*VersionChanged)(nil) + +func (e *VersionChanged) EventMeta() event.Meta { + if e == nil { + return event.Meta{} + } + return e.eventMeta +} +func (e *VersionChanged) SetEventMeta(m event.Meta) { + if e != nil { + e.eventMeta = m + } +} +func (e *VersionChanged) MarshalBinary() (text []byte, err error) { + return json.Marshal(e) +} +func (e *VersionChanged) UnmarshalBinary(b []byte) error { + return json.Unmarshal(b, e) +} diff --git a/app/peerfinder/ev-peer.go b/app/peerfinder/ev-peer.go new file mode 100644 index 0000000..a56ca45 --- /dev/null +++ b/app/peerfinder/ev-peer.go @@ -0,0 +1,87 @@ +package peerfinder + +import ( + "net" + "strconv" + "strings" + "time" +) + +type Time time.Time + +func (t *Time) UnmarshalJSON(b []byte) error { + time, err := time.Parse(`"2006-01-02 15:04:05"`, string(b)) + *t = Time(time) + return err +} +func (t *Time) MarshalJSON() ([]byte, error) { + if t == nil { + return nil, nil + } + i := *t + return time.Time(i).MarshalJSON() +} + +type ipFamily string + +const ( + ipFamilyV4 ipFamily = "IPv4" + ipFamilyV6 ipFamily = "IPv6" + ipFamilyBoth ipFamily = "both" + ipFamilyNone ipFamily = "none" +) + +func (t *ipFamily) UnmarshalJSON(b []byte) error { + i, err := strconv.Atoi(strings.Trim(string(b), `"`)) + switch i { + case 1: + *t = ipFamilyV4 + case 2: + *t = ipFamilyV6 + case 3: + *t = ipFamilyBoth + default: + *t = ipFamilyNone + } + return err +} + +type peerType []string + +func (t *peerType) UnmarshalJSON(b []byte) error { + *t = strings.Split(strings.Trim(string(b), `"`), ",") + return nil +} + +type Peer struct { + ID string `json:"peer_id,omitempty"` + Owner string `json:"peer_owner"` + Nick string `json:"peer_nick"` + Name string `json:"peer_name"` + Country string `json:"peer_country"` + Note string `json:"peer_note"` + Family ipFamily `json:"peer_family"` + Type peerType `json:"peer_type"` + Created Time `json:"peer_created"` +} + +func (p *Peer) CanSupport(ip string) bool { + addr := net.ParseIP(ip) + if addr == nil { + return false + } + if !addr.IsGlobalUnicast() { + return false + } + + switch p.Family { + case ipFamilyV4: + return addr.To4() != nil + case ipFamilyV6: + return addr.To16() != nil + case ipFamilyNone: + return false + } + + return true +} diff --git a/app/peerfinder/peer.go b/app/peerfinder/ev-request.go similarity index 60% rename from app/peerfinder/peer.go rename to app/peerfinder/ev-request.go index 036de1e..35b77ae 100644 --- a/app/peerfinder/peer.go +++ b/app/peerfinder/ev-request.go @@ -4,15 +4,11 @@ import ( "bytes" "encoding/json" "fmt" - "net" "net/netip" "strconv" - "strings" "time" - "github.com/tj/go-semver" - - "github.com/oklog/ulid/v2" + "github.com/oklog/ulid" "github.com/sour-is/ev/pkg/es/event" ) @@ -54,85 +50,6 @@ func (a *Request) ApplyEvent(lis ...event.Event) { } } -type Time time.Time - -func (t *Time) UnmarshalJSON(b []byte) error { - time, err := time.Parse(`"2006-01-02 15:04:05"`, string(b)) - *t = Time(time) - return err -} -func (t *Time) MarshalJSON() ([]byte, error) { - if t == nil { - return nil, nil - } - i := *t - return time.Time(i).MarshalJSON() -} - -type ipFamily string - -const ( - ipFamilyV4 ipFamily = "IPv4" - ipFamilyV6 ipFamily = "IPv6" - ipFamilyBoth ipFamily = "both" - ipFamilyNone ipFamily = "none" -) - -func (t *ipFamily) UnmarshalJSON(b []byte) error { - i, err := strconv.Atoi(strings.Trim(string(b), `"`)) - switch i { - case 1: - *t = ipFamilyV4 - case 2: - *t = ipFamilyV6 - case 3: - *t = ipFamilyBoth - default: - *t = ipFamilyNone - } - return err -} - -type peerType []string - -func (t *peerType) UnmarshalJSON(b []byte) error { - *t = strings.Split(strings.Trim(string(b), `"`), ",") - return nil -} - -type Peer struct { - ID string `json:"peer_id,omitempty"` - Owner string `json:"peer_owner"` - Nick string `json:"peer_nick"` - Name string `json:"peer_name"` - Country string `json:"peer_country"` - Note string `json:"peer_note"` - Family ipFamily `json:"peer_family"` - Type peerType `json:"peer_type"` - Created Time `json:"peer_created"` -} - -func (p *Peer) CanSupport(ip string) bool { - addr := net.ParseIP(ip) - if addr == nil { - return false - } - if !addr.IsGlobalUnicast() { - return false - } - - switch p.Family { - case ipFamilyV4: - return addr.To4() != nil - case ipFamilyV6: - return addr.To16() != nil - case ipFamilyNone: - return false - } - - return true -} - type Response struct { Peer *Peer `json:"peer"` PeerID string `json:"-"` @@ -149,6 +66,18 @@ type Response struct { Created time.Time `json:"res_created"` } +type ListResponse []Response + +func (lis ListResponse) Len() int { + return len(lis) +} +func (lis ListResponse) Less(i, j int) bool { + return lis[i].Latency < lis[j].Latency +} +func (lis ListResponse) Swap(i, j int) { + lis[i], lis[j] = lis[j], lis[i] +} + type RequestSubmitted struct { eventMeta event.Meta @@ -271,68 +200,3 @@ func (e *ResultSubmitted) UnmarshalBinary(b []byte) error { func (e *ResultSubmitted) String() string { return fmt.Sprintf("id: %s\npeer: %s\nversion: %s\nlatency: %0.4f", e.RequestID, e.PeerID, e.PeerVersion, e.Latency) } - -type Info struct { - ScriptVersion string `json:"script_version"` - - event.AggregateRoot -} - -var _ event.Aggregate = (*Info)(nil) - -func (a *Info) ApplyEvent(lis ...event.Event) { - for _, e := range lis { - switch e := e.(type) { - case *VersionChanged: - a.ScriptVersion = e.ScriptVersion - } - } -} -func (a *Info) MarshalEnviron() ([]byte, error) { - var b bytes.Buffer - - b.WriteString("SCRIPT_VERSION=") - b.WriteString(a.ScriptVersion) - b.WriteRune('\n') - - return b.Bytes(), nil -} -func (a *Info) OnUpsert() error { - if a.StreamVersion() == 0 { - event.Raise(a, &VersionChanged{ScriptVersion: initVersion}) - } - current, _ := semver.Parse(initVersion) - previous, _ := semver.Parse(a.ScriptVersion) - - if current.Compare(previous) > 0 { - event.Raise(a, &VersionChanged{ScriptVersion: initVersion}) - } - - return nil -} - -type VersionChanged struct { - ScriptVersion string `json:"script_version"` - - eventMeta event.Meta -} - -var _ event.Event = (*VersionChanged)(nil) - -func (e *VersionChanged) EventMeta() event.Meta { - if e == nil { - return event.Meta{} - } - return e.eventMeta -} -func (e *VersionChanged) SetEventMeta(m event.Meta) { - if e != nil { - e.eventMeta = m - } -} -func (e *VersionChanged) MarshalBinary() (text []byte, err error) { - return json.Marshal(e) -} -func (e *VersionChanged) UnmarshalBinary(b []byte) error { - return json.Unmarshal(b, e) -} diff --git a/app/peerfinder/http.go b/app/peerfinder/http.go new file mode 100644 index 0000000..7a67a6f --- /dev/null +++ b/app/peerfinder/http.go @@ -0,0 +1,559 @@ +package peerfinder + +import ( + "context" + "embed" + "encoding/json" + "fmt" + "html/template" + "io" + "io/fs" + "log" + "net" + "net/http" + "sort" + "strconv" + "strings" + + "github.com/oklog/ulid" + contentnegotiation "gitlab.com/jamietanna/content-negotiation-go" + "go.opentelemetry.io/otel/attribute" + + "github.com/sour-is/ev/internal/lg" + "github.com/sour-is/ev/pkg/es" + "github.com/sour-is/ev/pkg/es/event" +) + +var ( + //go:embed pages/* layouts/* assets/* + files embed.FS + templates map[string]*template.Template +) + +// Args passed to templates +type Args struct { + RemoteIP string + Requests []*Request + CountPeers int +} + +// requestArgs builds args from http.Request +func requestArgs(r *http.Request) Args { + remoteIP, _, _ := strings.Cut(r.RemoteAddr, ":") + if s := r.Header.Get("X-Forwarded-For"); s != "" { + s, _, _ = strings.Cut(s, ", ") + remoteIP = s + } + return Args{ + RemoteIP: remoteIP, + } +} + +// RegisterHTTP adds handler paths to the ServeMux +func (s *service) RegisterHTTP(mux *http.ServeMux) { + a, _ := fs.Sub(files, "assets") + assets := http.StripPrefix("/peers/assets/", http.FileServer(http.FS(a))) + + mux.Handle("/peers/assets/", lg.Htrace(assets, "peer-assets")) + mux.Handle("/peers/", lg.Htrace(s, "peers")) +} + +// ServeHTTP handle HTTP requests +func (s *service) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + ctx, span := lg.Span(ctx) + defer span.End() + + r = r.WithContext(ctx) + + switch r.Method { + case http.MethodGet: + switch { + case strings.HasPrefix(r.URL.Path, "/peers/pending/"): + s.getPending(w, r, strings.TrimPrefix(r.URL.Path, "/peers/pending/")) + return + + case strings.HasPrefix(r.URL.Path, "/peers/req/"): + s.getResultsForRequest(w, r, strings.TrimPrefix(r.URL.Path, "/peers/req/")) + return + + case strings.HasPrefix(r.URL.Path, "/peers/status"): + s.state.Modify(r.Context(), func(ctx context.Context, state *state) error { + + for id, p := range state.requests { + fmt.Fprintln(w, "REQ: ", id, p.RequestIP, len(p.Responses)) + for id, r := range p.Responses { + fmt.Fprintln(w, " RES: ", id, r.PeerID[24:], r.Latency, r.Jitter) + } + } + + return nil + }) + + default: + s.getResults(w, r) + return + } + case http.MethodPost: + switch { + case strings.HasPrefix(r.URL.Path, "/peers/req/"): + s.postResult(w, r, strings.TrimPrefix(r.URL.Path, "/peers/req/")) + return + + case strings.HasPrefix(r.URL.Path, "/peers/req"): + s.postRequest(w, r) + return + + default: + w.WriteHeader(http.StatusNotFound) + return + } + default: + w.WriteHeader(http.StatusMethodNotAllowed) + return + } +} +func (s *service) getPending(w http.ResponseWriter, r *http.Request, uuid string) { + ctx, span := lg.Span(r.Context()) + defer span.End() + + span.SetAttributes( + attribute.String("uuid", uuid), + ) + + var peer *Peer + err := s.state.Modify(ctx, func(ctx context.Context, state *state) error { + var ok bool + if peer, ok = state.peers[uuid]; !ok { + return fmt.Errorf("peer not found: %s", uuid) + } + + return nil + }) + if err != nil { + span.RecordError(err) + w.WriteHeader(http.StatusNotFound) + return + } + + info, err := es.Upsert(ctx, s.es, aggInfo, func(ctx context.Context, agg *Info) error { + return agg.OnUpsert() // initialize if not exists + }) + if err != nil { + span.RecordError(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + requests, err := s.es.Read(ctx, queueRequests, -1, -30) + if err != nil { + span.RecordError(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + responses, err := s.es.Read(ctx, aggPeer(uuid), -1, -30) + if err != nil { + span.RecordError(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + span.AddEvent(fmt.Sprintf("req = %d, res = %d", len(requests), len(responses))) + + req := filter(peer, requests, responses) + + negotiator := contentnegotiation.NewNegotiator("application/json", "text/environment", "text/plain", "text/html") + negotiated, _, err := negotiator.Negotiate(r.Header.Get("Accept")) + if err != nil { + span.RecordError(err) + w.WriteHeader(http.StatusNotAcceptable) + return + } + + span.AddEvent(negotiated.String()) + mime := negotiated.String() + switch mime { + case "text/environment": + w.Header().Set("content-type", negotiated.String()) + _, err = encodeTo(w, info.MarshalEnviron, req.MarshalEnviron) + case "application/json": + w.Header().Set("content-type", negotiated.String()) + var out interface{} = info + if req != nil { + out = struct { + ScriptVersion string `json:"script_version"` + RequestID string `json:"req_id"` + RequestIP string `json:"req_ip"` + Family string `json:"req_family"` + Created string `json:"req_created"` + }{ + info.ScriptVersion, + req.RequestID(), + req.RequestIP, + strconv.Itoa(req.Family()), + req.CreatedString(), + } + } + err = json.NewEncoder(w).Encode(out) + } + span.RecordError(err) +} +func (s *service) getResults(w http.ResponseWriter, r *http.Request) { + ctx, span := lg.Span(r.Context()) + defer span.End() + + events, err := s.es.Read(ctx, queueRequests, -1, -30) + if err != nil { + span.RecordError(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + requests := make([]*Request, len(events)) + for i, req := range events { + if req, ok := req.(*RequestSubmitted); ok { + requests[i], err = s.loadResult(ctx, req.RequestID()) + if err != nil { + span.RecordError(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + } + } + + args := requestArgs(r) + args.Requests = requests + + s.state.Modify(ctx, func(ctx context.Context, state *state) error { + args.CountPeers = len(state.peers) + return nil + }) + + t := templates["home.tpl"] + t.Execute(w, args) +} +func (s *service) getResultsForRequest(w http.ResponseWriter, r *http.Request, uuid string) { + ctx, span := lg.Span(r.Context()) + defer span.End() + + span.SetAttributes( + attribute.String("uuid", uuid), + ) + + request, err := s.loadResult(ctx, uuid) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + negotiator := contentnegotiation.NewNegotiator("application/json", "text/environment", "text/csv", "text/plain", "text/html") + negotiated, _, err := negotiator.Negotiate(r.Header.Get("Accept")) + if err != nil { + w.WriteHeader(http.StatusNotAcceptable) + return + } + span.AddEvent(negotiated.String()) + switch negotiated.String() { + // case "text/environment": + // encodeTo(w, responses.MarshalBinary) + case "application/json": + json.NewEncoder(w).Encode(request) + return + default: + args := requestArgs(r) + args.Requests = append(args.Requests, request) + span.AddEvent(fmt.Sprint(args)) + err := renderTo(w, "req.tpl", args) + span.RecordError(err) + + return + } +} +func (s *service) postRequest(w http.ResponseWriter, r *http.Request) { + ctx, span := lg.Span(r.Context()) + defer span.End() + + if err := r.ParseForm(); err != nil { + w.WriteHeader(http.StatusUnprocessableEntity) + return + } + + args := requestArgs(r) + requestIP := args.RemoteIP + + if ip := r.Form.Get("req_ip"); ip != "" { + requestIP = ip + } + + ip := net.ParseIP(requestIP) + if ip == nil { + w.WriteHeader(http.StatusBadRequest) + return + } + req := &RequestSubmitted{ + RequestIP: ip.String(), + } + if hidden, err := strconv.ParseBool(r.Form.Get("req_hidden")); err != nil { + req.Hidden = hidden + } + + span.SetAttributes( + attribute.Stringer("req_ip", ip), + ) + + s.es.Append(ctx, queueRequests, event.NewEvents(req)) + + http.Redirect(w, r, "/peers/req/"+req.RequestID(), http.StatusSeeOther) +} +func (s *service) postResult(w http.ResponseWriter, r *http.Request, id string) { + ctx, span := lg.Span(r.Context()) + defer span.End() + + span.SetAttributes( + attribute.String("id", id), + ) + + if _, err := ulid.ParseStrict(id); err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + + if err := r.ParseForm(); err != nil { + w.WriteHeader(http.StatusUnprocessableEntity) + return + } + + form := make([]string, 0, len(r.Form)) + for k, vals := range r.Form { + for _, v := range vals { + form = append(form, fmt.Sprint(k, v)) + } + } + span.SetAttributes( + attribute.StringSlice("form", form), + ) + + peerID := r.Form.Get("peer_id") + err := s.state.Modify(ctx, func(ctx context.Context, state *state) error { + var ok bool + if _, ok = state.peers[peerID]; !ok { + // fmt.Printf("peer not found: %s\n", peerID) + return fmt.Errorf("peer not found: %s", peerID) + } + + return nil + }) + if err != nil { + span.RecordError(err) + w.WriteHeader(http.StatusNotFound) + return + } + + var unreach bool + latency, err := strconv.ParseFloat(r.Form.Get("res_latency"), 64) + if err != nil { + unreach = true + } + + req := &ResultSubmitted{ + RequestID: id, + PeerID: r.Form.Get("peer_id"), + PeerVersion: r.Form.Get("peer_version"), + Latency: latency, + Unreachable: unreach, + } + + if jitter, err := strconv.ParseFloat(r.Form.Get("res_jitter"), 64); err == nil { + req.Jitter = jitter + } else { + span.RecordError(err) + } + if minrtt, err := strconv.ParseFloat(r.Form.Get("res_minrtt"), 64); err == nil { + req.MinRTT = minrtt + } else { + span.RecordError(err) + } + if maxrtt, err := strconv.ParseFloat(r.Form.Get("res_maxrtt"), 64); err == nil { + req.MaxRTT = maxrtt + } else { + span.RecordError(err) + } + + span.SetAttributes( + attribute.Stringer("result", req), + ) + + s.state.Modify(ctx, func(ctx context.Context, state *state) error { + + return nil + }) + + idx, err := s.es.LastIndex(ctx, aggRequest(id)) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + if idx == 0 { + w.WriteHeader(http.StatusNotFound) + return + } + + s.es.Append(ctx, queueResults, event.NewEvents(req)) +} + +func renderTo(w io.Writer, name string, args any) (err error) { + defer func() { + if p := recover(); p != nil { + err = fmt.Errorf("panic: %s", p) + } + if err != nil { + fmt.Fprint(w, err) + } + }() + + t, ok := templates[name] + if !ok || t == nil { + return fmt.Errorf("missing template") + } + return t.Execute(w, args) +} +func encodeTo(w io.Writer, fns ...func() ([]byte, error)) (int, error) { + i := 0 + for _, fn := range fns { + b, err := fn() + if err != nil { + return i, err + } + + j, err := w.Write(b) + i += j + if err != nil { + return i, err + } + } + return i, nil +} + +func loadTemplates() error { + if templates != nil { + return nil + } + templates = make(map[string]*template.Template) + tmplFiles, err := fs.ReadDir(files, "pages") + if err != nil { + return err + } + + for _, tmpl := range tmplFiles { + if tmpl.IsDir() { + continue + } + pt := template.New(tmpl.Name()) + pt.Funcs(funcMap) + pt, err = pt.ParseFS(files, "pages/"+tmpl.Name(), "layouts/*.tpl") + if err != nil { + log.Println(err) + + return err + } + templates[tmpl.Name()] = pt + } + return nil +} + +var funcMap = map[string]any{ + "orderByPeer": fnOrderByPeer, + "countResponses": fnCountResponses, +} + +func fnOrderByPeer(rq *Request) any { + type peerResult struct { + Name string + Country string + Latency float64 + Jitter float64 + } + type peer struct { + Name string + Note string + Nick string + Country string + Latency float64 + Jitter float64 + VPNTypes []string + + Results []peerResult + } + + peers := make(map[string]peer) + sort.Sort(ListResponse(rq.Responses)) + for _, rs := range rq.Responses { + p, ok := peers[rs.Peer.Owner] + + if !ok { + p.Country = rs.Peer.Country + p.Name = rs.Peer.Name + p.Nick = rs.Peer.Nick + p.Note = rs.Peer.Note + p.Latency = rs.Latency + p.Jitter = rs.Jitter + p.VPNTypes = rs.Peer.Type + } + + p.Results = append(p.Results, peerResult{ + Name: rs.Peer.Name, + Country: rs.Peer.Country, + Latency: rs.Latency, + Jitter: rs.Jitter, + }) + + peers[rs.Peer.Owner] = p + } + + return peers +} +func fnCountResponses(rq *Request) int { + count := 0 + for _, res := range rq.Responses { + if !res.Unreachable { + count++ + } + } + return count +} +func filter(peer *Peer, requests, responses event.Events) *RequestSubmitted { + have := make(map[string]struct{}, len(responses)) + for _, res := range toList[ResultSubmitted](responses...) { + have[res.RequestID] = struct{}{} + } + for _, req := range reverse(toList[RequestSubmitted](requests...)...) { + if _, ok := have[req.RequestID()]; !ok { + if !peer.CanSupport(req.RequestIP) { + continue + } + + return req + } + } + return nil +} +func toList[E any, T es.PE[E]](lis ...event.Event) []T { + newLis := make([]T, 0, len(lis)) + for i := range lis { + if e, ok := lis[i].(T); ok { + newLis = append(newLis, e) + } + } + return newLis +} +func reverse[T any](s ...T) []T { + first, last := 0, len(s)-1 + for first < last { + s[first], s[last] = s[last], s[first] + first++ + last-- + } + return s +} diff --git a/app/peerfinder/jobs.go b/app/peerfinder/jobs.go new file mode 100644 index 0000000..bf818f3 --- /dev/null +++ b/app/peerfinder/jobs.go @@ -0,0 +1,217 @@ +package peerfinder + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "time" + + "github.com/sour-is/ev/internal/lg" + "github.com/sour-is/ev/pkg/es" + "github.com/sour-is/ev/pkg/math" + "github.com/sour-is/ev/pkg/set" +) + +// RefreshJob retrieves peer info from the peerdb +func (s *service) RefreshJob(ctx context.Context, _ time.Time) error { + ctx, span := lg.Span(ctx) + defer span.End() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, s.statusURL, nil) + span.RecordError(err) + if err != nil { + return err + } + req.Header.Set("Accept", "application/json") + + res, err := http.DefaultClient.Do(req) + span.RecordError(err) + if err != nil { + return err + } + + defer res.Body.Close() + var peers []*Peer + err = json.NewDecoder(res.Body).Decode(&peers) + span.RecordError(err) + if err != nil { + return err + } + + span.AddEvent(fmt.Sprintf("processed %d peers", len(peers))) + + err = s.state.Modify(ctx, func(ctx context.Context, t *state) error { + for _, peer := range peers { + t.peers[peer.ID] = peer + } + + return nil + }) + span.RecordError(err) + return err +} + +// CleanJob truncates streams old request data +func (s *service) CleanJob(ctx context.Context, now time.Time) error { + ctx, span := lg.Span(ctx) + defer span.End() + + span.AddEvent("clear peerfinder requests") + + endRequestID, err := s.cleanRequests(ctx, now) + if err != nil { + return err + } + if err = s.cleanResults(ctx, endRequestID); err != nil { + return err + } + + return s.cleanPeerJobs(ctx) +} +func (s *service) cleanPeerJobs(ctx context.Context) error { + ctx, span := lg.Span(ctx) + defer span.End() + + peers := set.New[string]() + err := s.state.Modify(ctx, func(ctx context.Context, state *state) error { + for id := range state.peers { + peers.Add(id) + } + return nil + }) + if err != nil { + return err + } + + // trunctate all the peer streams to last 30 + for streamID := range peers { + streamID = aggPeer(streamID) + first, err := s.es.FirstIndex(ctx, streamID) + if err != nil { + return err + } + last, err := s.es.LastIndex(ctx, streamID) + if err != nil { + return err + } + newFirst := math.Max(int64(last-30), int64(first)) + if last == 0 || newFirst == int64(first) { + // fmt.Println("SKIP", streamID, first, newFirst, last) + span.AddEvent(fmt.Sprint("SKIP", streamID, first, newFirst, last)) + continue + } + // fmt.Println("TRUNC", streamID, first, newFirst, last) + span.AddEvent(fmt.Sprint("TRUNC", streamID, first, newFirst, last)) + err = s.es.Truncate(ctx, streamID, int64(newFirst)) + if err != nil { + return err + } + } + + return nil +} +func (s *service) cleanRequests(ctx context.Context, now time.Time) (string, error) { + ctx, span := lg.Span(ctx) + defer span.End() + + var streamIDs []string + var endPosition uint64 + var endRequestID string + + last, err := s.es.LastIndex(ctx, queueRequests) + if err != nil { + return "", err + } + +end: + for { + events, err := s.es.Read(ctx, queueRequests, int64(endPosition), 1000) // read 1000 from the top each loop. + if err != nil && !errors.Is(err, es.ErrNotFound) { + span.RecordError(err) + return "", err + } + + if len(events) == 0 { + break + } + + endPosition = events.Last().EventMeta().ActualPosition + for _, event := range events { + switch e := event.(type) { + case *RequestSubmitted: + if e.eventMeta.ActualPosition < last-30 { + streamIDs = append(streamIDs, aggRequest(e.RequestID())) + } else { + endRequestID = e.RequestID() + endPosition = e.eventMeta.ActualPosition + break end + } + } + } + } + + // truncate all reqs to found end position + // fmt.Println("TRUNC", queueRequests, int64(endPosition), last) + span.AddEvent(fmt.Sprint("TRUNC", queueRequests, int64(endPosition), last)) + err = s.es.Truncate(ctx, queueRequests, int64(endPosition)) + if err != nil { + return "", err + } + + // truncate all the request streams + for _, streamID := range streamIDs { + last, err := s.es.LastIndex(ctx, streamID) + if err != nil { + return "", err + } + // fmt.Println("TRUNC", streamID, last) + span.AddEvent(fmt.Sprint("TRUNC", streamID, last)) + err = s.es.Truncate(ctx, streamID, int64(last)) + if err != nil { + return "", err + } + } + + return endRequestID, nil +} +func (s *service) cleanResults(ctx context.Context, endRequestID string) error { + ctx, span := lg.Span(ctx) + defer span.End() + + var endPosition uint64 + + done := false + for !done { + events, err := s.es.Read(ctx, queueResults, int64(endPosition), 1000) // read 30 from the top each loop. + if err != nil { + return err + } + + if len(events) == 0 { + done = true + continue + } + + endPosition = events.Last().EventMeta().ActualPosition + + for _, event := range events { + switch e := event.(type) { + case *ResultSubmitted: + if e.RequestID == endRequestID { + done = true + endPosition = e.eventMeta.ActualPosition + } + } + } + } + // truncate all reqs to found end position + // fmt.Println("TRUNC", queueResults, int64(endPosition), last) + span.AddEvent(fmt.Sprint("TRUNC", queueResults, int64(endPosition))) + err := s.es.Truncate(ctx, queueResults, int64(endPosition)) + if err != nil { + return err + } + return nil +} diff --git a/app/peerfinder/layouts/main.tpl b/app/peerfinder/layouts/main.tpl index a880a57..d87f5da 100644 --- a/app/peerfinder/layouts/main.tpl +++ b/app/peerfinder/layouts/main.tpl @@ -8,7 +8,7 @@ DN42 PingFinder - + diff --git a/app/peerfinder/pages/home.tpl b/app/peerfinder/pages/home.tpl index e3dfdd4..89f131d 100644 --- a/app/peerfinder/pages/home.tpl +++ b/app/peerfinder/pages/home.tpl @@ -48,7 +48,7 @@ {{range $req := .Requests}}
Request ID: {{ $req.RequestID }}
diff --git a/app/peerfinder/pages/req.tpl b/app/peerfinder/pages/req.tpl index d67341c..bc396d0 100644 --- a/app/peerfinder/pages/req.tpl +++ b/app/peerfinder/pages/req.tpl @@ -15,7 +15,7 @@
@@ -38,7 +38,7 @@ {{.Name}} {{.Country}} - {{printf "%0.3f ms" .Latency}} + {{ if eq .Latency 0.0 }}—{{ else }}{{printf "%0.3f ms" .Latency}}{{ end }} {{ if eq .Jitter 0.0 }}—{{ else }}{{ printf "%0.3f ms" .Jitter }}{{ end }} {{end}} diff --git a/app/peerfinder/service.go b/app/peerfinder/service.go index b7ddec6..502259e 100644 --- a/app/peerfinder/service.go +++ b/app/peerfinder/service.go @@ -2,31 +2,14 @@ package peerfinder import ( "context" - "embed" - "encoding/json" - "errors" "fmt" - "html/template" - "io" - "io/fs" - "log" - "net" - "net/http" - "sort" - "strconv" - "strings" "time" - "github.com/oklog/ulid" - contentnegotiation "gitlab.com/jamietanna/content-negotiation-go" - "go.opentelemetry.io/otel/attribute" - "github.com/sour-is/ev/internal/lg" "github.com/sour-is/ev/pkg/es" "github.com/sour-is/ev/pkg/es/event" "github.com/sour-is/ev/pkg/locker" - "github.com/sour-is/ev/pkg/math" - "github.com/sour-is/ev/pkg/set" + "go.uber.org/multierr" ) const ( @@ -39,21 +22,17 @@ const ( func aggRequest(id string) string { return "pf-request-" + id } func aggPeer(id string) string { return "pf-peer-" + id } -var ( - //go:embed pages/* layouts/* assets/* - files embed.FS - templates map[string]*template.Template -) - type service struct { es *es.EventStore statusURL string state *locker.Locked[state] + stop func() } type state struct { - peers map[string]*Peer + peers map[string]*Peer + requests map[string]*Request } func New(ctx context.Context, es *es.EventStore, statusURL string) (*service, error) { @@ -67,621 +46,109 @@ func New(ctx context.Context, es *es.EventStore, statusURL string) (*service, er return nil, err } - svc := &service{es: es, statusURL: statusURL, state: locker.New(&state{peers: make(map[string]*Peer)})} + svc := &service{ + es: es, + statusURL: statusURL, + state: locker.New(&state{ + peers: make(map[string]*Peer), + requests: make(map[string]*Request), + })} return svc, nil } -func (s *service) RegisterHTTP(mux *http.ServeMux) { - a, _ := fs.Sub(files, "assets") - assets := http.StripPrefix("/peers/assets/", http.FileServer(http.FS(a))) +func (s *service) loadResult(ctx context.Context, uuid string) (*Request, error) { + request := &Request{} + request.SetStreamID(aggRequest(uuid)) + err := s.es.Load(ctx, request) + if err != nil { + return nil, err + } - mux.Handle("/peers/assets/", lg.Htrace(assets, "peer-assets")) - mux.Handle("/peers/", lg.Htrace(s, "peers")) + return request, s.state.Modify(ctx, func(ctx context.Context, t *state) error { + for i := range request.Responses { + res := &request.Responses[i] + if peer, ok := t.peers[res.PeerID]; ok { + res.Peer = peer + res.Peer.ID = "" + } + } + + return nil + }) } -func (s *service) ServeHTTP(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() +func (s *service) Run(ctx context.Context) (err error) { + var errs error ctx, span := lg.Span(ctx) defer span.End() - r = r.WithContext(ctx) + ctx, s.stop = context.WithCancel(ctx) - switch r.Method { - case http.MethodGet: - switch { - case strings.HasPrefix(r.URL.Path, "/peers/pending/"): - s.getPending(w, r, strings.TrimPrefix(r.URL.Path, "/peers/pending/")) - return + subReq, e := s.es.EventStream().Subscribe(ctx, queueRequests, 0) + errs = multierr.Append(errs, e) - case strings.HasPrefix(r.URL.Path, "/peers/req/"): - s.getResultsForRequest(w, r, strings.TrimPrefix(r.URL.Path, "/peers/req/")) - return + subRes, e := s.es.EventStream().Subscribe(ctx, queueResults, 0) + errs = multierr.Append(errs, e) - case strings.HasPrefix(r.URL.Path, "/peers/status"): - s.state.Modify(r.Context(), func(ctx context.Context, state *state) error { - for id, p := range state.peers { - fmt.Fprintln(w, "PEER: ", id, p.Owner, p.Name) - } + defer func() { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() - return nil - }) + err = multierr.Combine(subReq.Close(ctx), subRes.Close(ctx), err) + }() - default: - s.getResults(w, r) - return - } - case http.MethodPost: - switch { - case strings.HasPrefix(r.URL.Path, "/peers/req/"): - s.postResult(w, r, strings.TrimPrefix(r.URL.Path, "/peers/req/")) - return - - case strings.HasPrefix(r.URL.Path, "/peers/req"): - s.postRequest(w, r) - return - - default: - w.WriteHeader(http.StatusNotFound) - return - } - default: - w.WriteHeader(http.StatusMethodNotAllowed) - return - } -} - -func (s *service) getPending(w http.ResponseWriter, r *http.Request, uuid string) { - ctx, span := lg.Span(r.Context()) - defer span.End() - - span.SetAttributes( - attribute.String("uuid", uuid), - ) - - var peer *Peer - err := s.state.Modify(ctx, func(ctx context.Context, state *state) error { - var ok bool - if peer, ok = state.peers[uuid]; !ok { - return fmt.Errorf("peer not found: %s", uuid) - } - - return nil - }) - if err != nil { - span.RecordError(err) - w.WriteHeader(http.StatusNotFound) - return + if errs != nil { + return errs } - info, err := es.Upsert(ctx, s.es, aggInfo, func(ctx context.Context, agg *Info) error { - return agg.OnUpsert() // initialize if not exists - }) - if err != nil { - span.RecordError(err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - requests, err := s.es.Read(ctx, queueRequests, -1, -30) - if err != nil { - span.RecordError(err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - responses, err := s.es.Read(ctx, aggPeer(uuid), -1, -30) - if err != nil { - span.RecordError(err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - span.AddEvent(fmt.Sprintf("req = %d, res = %d", len(requests), len(responses))) - - req := filter(peer, requests, responses) - - negotiator := contentnegotiation.NewNegotiator("application/json", "text/environment", "text/plain", "text/html") - negotiated, _, err := negotiator.Negotiate(r.Header.Get("Accept")) - if err != nil { - span.RecordError(err) - w.WriteHeader(http.StatusNotAcceptable) - return - } - - span.AddEvent(negotiated.String()) - mime := negotiated.String() - switch mime { - case "text/environment": - w.Header().Set("content-type", negotiated.String()) - _, err = encodeTo(w, info.MarshalEnviron, req.MarshalEnviron) - case "application/json": - w.Header().Set("content-type", negotiated.String()) - var out interface{} = info - if req != nil { - out = struct { - ScriptVersion string `json:"script_version"` - RequestID string `json:"req_id"` - RequestIP string `json:"req_ip"` - Family string `json:"req_family"` - Created string `json:"req_created"` - }{ - info.ScriptVersion, - req.RequestID(), - req.RequestIP, - strconv.Itoa(req.Family()), - req.CreatedString(), + for { + var events event.Events + select { + case <-ctx.Done(): + return nil + case ok := <-subReq.Recv(ctx): + if ok { + events, err = subReq.Events(ctx) + } + case ok := <-subRes.Recv(ctx): + if ok { + events, err = subRes.Events(ctx) } } - err = json.NewEncoder(w).Encode(out) + + s.state.Modify(ctx, func(ctx context.Context, state *state) error { + return state.ApplyEvents(events) + }) + events = events[:0] } - span.RecordError(err) } -func (s *service) getResults(w http.ResponseWriter, r *http.Request) { - ctx, span := lg.Span(r.Context()) - defer span.End() - - events, err := s.es.Read(ctx, queueRequests, -1, -30) - if err != nil { - span.RecordError(err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - requests := make([]*Request, len(events)) - for i, req := range events { - if req, ok := req.(*RequestSubmitted); ok { - requests[i], err = s.loadResult(ctx, req.RequestID()) - if err != nil { - span.RecordError(err) - w.WriteHeader(http.StatusInternalServerError) - return - } +func (s *service) Stop(ctx context.Context) (err error) { + defer func() { + if p := recover(); p != nil { + err = fmt.Errorf("PANIC: %v", p) } - } + }() - args := requestArgs(r) - args.Requests = requests - - s.state.Modify(ctx, func(ctx context.Context, state *state) error { - args.CountPeers = len(state.peers) - return nil - }) - - t := templates["home.tpl"] - t.Execute(w, args) -} -func (s *service) getResultsForRequest(w http.ResponseWriter, r *http.Request, uuid string) { - ctx, span := lg.Span(r.Context()) - defer span.End() - - span.SetAttributes( - attribute.String("uuid", uuid), - ) - - request, err := s.loadResult(ctx, uuid) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - - negotiator := contentnegotiation.NewNegotiator("application/json", "text/environment", "text/csv", "text/plain", "text/html") - negotiated, _, err := negotiator.Negotiate(r.Header.Get("Accept")) - if err != nil { - w.WriteHeader(http.StatusNotAcceptable) - return - } - span.AddEvent(negotiated.String()) - switch negotiated.String() { - // case "text/environment": - // encodeTo(w, responses.MarshalBinary) - case "application/json": - json.NewEncoder(w).Encode(request) - return - default: - args := requestArgs(r) - args.Requests = append(args.Requests, request) - span.AddEvent(fmt.Sprint(args)) - err := renderTo(w, "req.tpl", args) - span.RecordError(err) - - return - } -} -func (s *service) postRequest(w http.ResponseWriter, r *http.Request) { - ctx, span := lg.Span(r.Context()) - defer span.End() - - if err := r.ParseForm(); err != nil { - w.WriteHeader(http.StatusUnprocessableEntity) - return - } - - args := requestArgs(r) - requestIP := args.RemoteIP - - if ip := r.Form.Get("req_ip"); ip != "" { - requestIP = ip - } - - ip := net.ParseIP(requestIP) - if ip == nil { - w.WriteHeader(http.StatusBadRequest) - return - } - req := &RequestSubmitted{ - RequestIP: ip.String(), - } - if hidden, err := strconv.ParseBool(r.Form.Get("req_hidden")); err != nil { - req.Hidden = hidden - } - - span.SetAttributes( - attribute.Stringer("req_ip", ip), - ) - - s.es.Append(ctx, queueRequests, event.NewEvents(req)) - - http.Redirect(w, r, "/peers/req/"+req.RequestID(), http.StatusSeeOther) -} -func (s *service) postResult(w http.ResponseWriter, r *http.Request, id string) { - ctx, span := lg.Span(r.Context()) - defer span.End() - - span.SetAttributes( - attribute.String("id", id), - ) - - if _, err := ulid.ParseStrict(id); err != nil { - w.WriteHeader(http.StatusNotFound) - return - } - - if err := r.ParseForm(); err != nil { - w.WriteHeader(http.StatusUnprocessableEntity) - return - } - - peerID := r.Form.Get("peer_id") - err := s.state.Modify(ctx, func(ctx context.Context, state *state) error { - var ok bool - if _, ok = state.peers[peerID]; !ok { - // fmt.Printf("peer not found: %s\n", peerID) - return fmt.Errorf("peer not found: %s", peerID) - } - - return nil - }) - if err != nil { - span.RecordError(err) - w.WriteHeader(http.StatusNotFound) - return - } - - var unreach bool - latency, err := strconv.ParseFloat(r.Form.Get("res_latency"), 64) - if err != nil { - unreach = true - } - - req := &ResultSubmitted{ - RequestID: id, - PeerID: r.Form.Get("peer_id"), - PeerVersion: r.Form.Get("peer_version"), - Latency: latency, - Unreachable: unreach, - } - - if jitter, err := strconv.ParseFloat(r.Form.Get("res_jitter"), 64); err == nil { - req.Jitter = jitter - } else { - span.RecordError(err) - } - if minrtt, err := strconv.ParseFloat(r.Form.Get("res_minrtt"), 64); err == nil { - req.MinRTT = minrtt - } else { - span.RecordError(err) - } - if maxrtt, err := strconv.ParseFloat(r.Form.Get("res_maxrtt"), 64); err == nil { - req.MaxRTT = maxrtt - } else { - span.RecordError(err) - } - - span.SetAttributes( - attribute.Stringer("result", req), - ) - - s.state.Modify(ctx, func(ctx context.Context, state *state) error { - - return nil - }) - - idx, err := s.es.LastIndex(ctx, aggRequest(id)) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - if idx == 0 { - w.WriteHeader(http.StatusNotFound) - return - } - - s.es.Append(ctx, queueResults, event.NewEvents(req)) -} -func (s *service) RefreshJob(ctx context.Context, _ time.Time) error { - ctx, span := lg.Span(ctx) - defer span.End() - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, s.statusURL, nil) - span.RecordError(err) - if err != nil { - return err - } - req.Header.Set("Accept", "application/json") - - res, err := http.DefaultClient.Do(req) - span.RecordError(err) - if err != nil { - return err - } - - defer res.Body.Close() - var peers []*Peer - err = json.NewDecoder(res.Body).Decode(&peers) - span.RecordError(err) - if err != nil { - return err - } - - span.AddEvent(fmt.Sprintf("processed %d peers", len(peers))) - - err = s.state.Modify(ctx, func(ctx context.Context, t *state) error { - for _, peer := range peers { - t.peers[peer.ID] = peer - } - - return nil - }) - span.RecordError(err) + s.stop() return err } -func (s *service) CleanJob(ctx context.Context, now time.Time) error { - ctx, span := lg.Span(ctx) - defer span.End() - fmt.Println("clear peerfinder requests") - span.AddEvent("clear peerfinder requests") - - endRequestID, err := s.cleanRequests(ctx, now) - if err != nil { - return err - } - if err = s.cleanResults(ctx, endRequestID); err != nil { - return err - } - - return s.cleanPeerJobs(ctx) -} -func (s *service) cleanPeerJobs(ctx context.Context) error { - ctx, span := lg.Span(ctx) - defer span.End() - - peers := set.New[string]() - err := s.state.Modify(ctx, func(ctx context.Context, state *state) error { - for id := range state.peers { - peers.Add(id) - } - return nil - }) - if err != nil { - return err - } - - // trunctate all the peer streams to last 30 - for streamID := range peers { - streamID = aggPeer(streamID) - first, err := s.es.FirstIndex(ctx, streamID) - if err != nil { - return err - } - last, err := s.es.LastIndex(ctx, streamID) - if err != nil { - return err - } - newFirst := math.Max(math.Max(int64(last-30), int64(first)), 1) - if last == 0 || newFirst == int64(first) { - // fmt.Println("SKIP", streamID, first, newFirst, last) - span.AddEvent(fmt.Sprint("SKIP", streamID, first, newFirst, last)) - continue - } - // fmt.Println("TRUNC", streamID, first, newFirst, last) - span.AddEvent(fmt.Sprint("TRUNC", streamID, first, newFirst, last)) - err = s.es.Truncate(ctx, streamID, int64(newFirst)) - if err != nil { - return err - } - } - - return nil -} -func (s *service) cleanRequests(ctx context.Context, now time.Time) (string, error) { - ctx, span := lg.Span(ctx) - defer span.End() - - var streamIDs []string - var endPosition uint64 - var endRequestID string - - last, err := s.es.LastIndex(ctx, queueRequests) - if err != nil { - return "", err - } - -end: - for { - events, err := s.es.Read(ctx, queueRequests, int64(endPosition), 1000) // read 1000 from the top each loop. - if err != nil && !errors.Is(err, es.ErrNotFound) { - span.RecordError(err) - return "", err - } - - if len(events) == 0 { - break - } - - endPosition = events.Last().EventMeta().ActualPosition - for _, event := range events { - switch e := event.(type) { - case *RequestSubmitted: - if e.eventMeta.ActualPosition < last-30 || e.Created().Before(now.Add(-24*time.Hour)) { - streamIDs = append(streamIDs, aggRequest(e.RequestID())) - } else { - endRequestID = e.RequestID() - endPosition = e.eventMeta.ActualPosition - break end - } +func (s *state) ApplyEvents(events event.Events) error { + for _, e := range events { + switch e := e.(type) { + case *RequestSubmitted: + if _, ok := s.requests[e.RequestID()]; !ok { + s.requests[e.RequestID()] = &Request{} } - } - } - - // truncate all reqs to found end position - // fmt.Println("TRUNC", queueRequests, int64(endPosition), last) - span.AddEvent(fmt.Sprint("TRUNC", queueRequests, int64(endPosition), last)) - err = s.es.Truncate(ctx, queueRequests, int64(endPosition)) - if err != nil { - return "", err - } - - // truncate all the request streams - for _, streamID := range streamIDs { - last, err := s.es.LastIndex(ctx, streamID) - if err != nil { - return "", err - } - fmt.Println("TRUNC", streamID, last) - span.AddEvent(fmt.Sprint("TRUNC", streamID, last)) - err = s.es.Truncate(ctx, streamID, int64(last)) - if err != nil { - return "", err - } - } - - return endRequestID, nil -} -func (s *service) cleanResults(ctx context.Context, endRequestID string) error { - ctx, span := lg.Span(ctx) - defer span.End() - - var endPosition uint64 - - done := false - for !done { - events, err := s.es.Read(ctx, queueResults, int64(endPosition), 1000) // read 30 from the top each loop. - if err != nil { - return err - } - - if len(events) == 0 { - done = true - continue - } - - endPosition = events.Last().EventMeta().ActualPosition - - for _, event := range events { - switch e := event.(type) { - case *ResultSubmitted: - if e.RequestID == endRequestID { - done = true - endPosition = e.eventMeta.ActualPosition - } + s.requests[e.RequestID()].ApplyEvent(e) + case *ResultSubmitted: + if _, ok := s.requests[e.RequestID]; !ok { + s.requests[e.RequestID] = &Request{} } + s.requests[e.RequestID].ApplyEvent(e) } } - // truncate all reqs to found end position - // fmt.Println("TRUNC", queueResults, int64(endPosition), last) - span.AddEvent(fmt.Sprint("TRUNC", queueResults, int64(endPosition))) - err := s.es.Truncate(ctx, queueResults, int64(endPosition)) - if err != nil { - return err - } - return nil -} - -func filter(peer *Peer, requests, responses event.Events) *RequestSubmitted { - have := make(map[string]struct{}, len(responses)) - for _, res := range toList[ResultSubmitted](responses...) { - have[res.RequestID] = struct{}{} - } - for _, req := range reverse(toList[RequestSubmitted](requests...)...) { - if _, ok := have[req.RequestID()]; !ok { - if !peer.CanSupport(req.RequestIP) { - continue - } - - return req - } - } - return nil -} -func toList[E any, T es.PE[E]](lis ...event.Event) []T { - newLis := make([]T, 0, len(lis)) - for i := range lis { - if e, ok := lis[i].(T); ok { - newLis = append(newLis, e) - } - } - return newLis -} -func reverse[T any](s ...T) []T { - first, last := 0, len(s)-1 - for first < last { - s[first], s[last] = s[last], s[first] - first++ - last-- - } - return s -} -func encodeTo(w io.Writer, fns ...func() ([]byte, error)) (int, error) { - i := 0 - for _, fn := range fns { - b, err := fn() - if err != nil { - return i, err - } - - j, err := w.Write(b) - i += j - if err != nil { - return i, err - } - } - return i, nil -} -func loadTemplates() error { - if templates != nil { - return nil - } - templates = make(map[string]*template.Template) - tmplFiles, err := fs.ReadDir(files, "pages") - if err != nil { - return err - } - for _, tmpl := range tmplFiles { - if tmpl.IsDir() { - continue - } - pt := template.New(tmpl.Name()) - pt.Funcs(funcMap) - pt, err = pt.ParseFS(files, "pages/"+tmpl.Name(), "layouts/*.tpl") - if err != nil { - log.Println(err) - - return err - } - templates[tmpl.Name()] = pt - } return nil } @@ -707,130 +174,3 @@ func Projector(e event.Event) []event.Event { } return nil } - -type Args struct { - RemoteIP string - Requests []*Request - CountPeers int -} - -func requestArgs(r *http.Request) Args { - remoteIP, _, _ := strings.Cut(r.RemoteAddr, ":") - if s := r.Header.Get("X-Forwarded-For"); s != "" { - remoteIP = s - } - return Args{ - RemoteIP: remoteIP, - } -} - -func renderTo(w io.Writer, name string, args any) (err error) { - defer func() { - if p := recover(); p != nil { - err = fmt.Errorf("panic: %s", p) - } - if err != nil { - fmt.Fprint(w, err) - } - }() - - t, ok := templates[name] - if !ok || t == nil { - return fmt.Errorf("missing template") - } - return t.Execute(w, args) -} - -type ListResponse []Response - -func (lis ListResponse) Len() int { - return len(lis) -} -func (lis ListResponse) Less(i, j int) bool { - return lis[i].Latency < lis[j].Latency -} -func (lis ListResponse) Swap(i, j int) { - lis[i], lis[j] = lis[j], lis[i] -} - -func fnOrderByPeer(rq *Request) any { - type peerResult struct { - Name string - Country string - Latency float64 - Jitter float64 - } - type peer struct { - Name string - Note string - Nick string - Country string - Latency float64 - Jitter float64 - VPNTypes []string - - Results []peerResult - } - - peers := make(map[string]peer) - sort.Sort(ListResponse(rq.Responses)) - for _, rs := range rq.Responses { - p, ok := peers[rs.Peer.Owner] - - if !ok { - p.Country = rs.Peer.Country - p.Name = rs.Peer.Name - p.Nick = rs.Peer.Nick - p.Note = rs.Peer.Note - p.Latency = rs.Latency - p.Jitter = rs.Jitter - p.VPNTypes = rs.Peer.Type - } - - p.Results = append(p.Results, peerResult{ - Name: rs.Peer.Name, - Country: rs.Peer.Country, - Latency: rs.Latency, - Jitter: rs.Jitter, - }) - - peers[rs.Peer.Owner] = p - } - - return peers -} -func fnCountResponses(rq *Request) int { - count := 0 - for _, res := range rq.Responses { - if !res.Unreachable { - count++ - } - } - return count -} - -var funcMap = map[string]any{ - "orderByPeer": fnOrderByPeer, - "countResponses": fnCountResponses, -} - -func (s *service) loadResult(ctx context.Context, uuid string) (*Request, error) { - request := &Request{} - request.SetStreamID(aggRequest(uuid)) - err := s.es.Load(ctx, request) - if err != nil { - return nil, err - } - - return request, s.state.Modify(ctx, func(ctx context.Context, t *state) error { - for i := range request.Responses { - res := &request.Responses[i] - if peer, ok := t.peers[res.PeerID]; ok { - res.Peer = peer - res.Peer.ID = "" - } - } - - return nil - }) -} diff --git a/app/salty/service.go b/app/salty/service.go index ad98c25..28d9e7f 100644 --- a/app/salty/service.go +++ b/app/salty/service.go @@ -51,7 +51,7 @@ var saltyKey = contextKey{"salty"} type SaltyResolver interface { CreateSaltyUser(ctx context.Context, nick string, pub string) (*SaltyUser, error) SaltyUser(ctx context.Context, nick string) (*SaltyUser, error) - RegisterHTTP(mux *http.ServeMux) + IsResolver() } func New(ctx context.Context, es *es.EventStore, baseURL string) (*service, error) { @@ -111,6 +111,8 @@ func New(ctx context.Context, es *es.EventStore, baseURL string) (*service, erro return svc, errs } +func (s *service) IsResolver() {} + func (s *service) BaseURL() string { if s == nil { return "http://missing.context/" diff --git a/internal/graph/generated/generated.go b/internal/graph/generated/generated.go index 82bd7be..d4375ee 100644 --- a/internal/graph/generated/generated.go +++ b/internal/graph/generated/generated.go @@ -600,6 +600,14 @@ directive @goTag( key: String! value: String ) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION`, BuiltIn: false}, + {Name: "../../../app/mercury/mercury.graphqls", Input: `# extend type Query{ +# keys(namespace: String!) [String!]! +# get(namespace: String! keys: [String!]) [String]! +# } + +# extend type Mutation{ +# set(namespace: String! key: String! value: String): Bool! +# }`, BuiltIn: false}, {Name: "../../../app/msgbus/msgbus.graphqls", Input: `extend type Query { posts(streamID: String! paging: PageInput): Connection! } diff --git a/main.go b/main.go index 8b876c9..40e0cfe 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "errors" "log" "net/http" "net/url" @@ -28,6 +29,7 @@ import ( resolvelinks "github.com/sour-is/ev/pkg/es/driver/resolve-links" "github.com/sour-is/ev/pkg/es/driver/streamer" "github.com/sour-is/ev/pkg/es/event" + "github.com/sour-is/ev/pkg/gql/resolver" "github.com/sour-is/ev/pkg/set" ) @@ -35,18 +37,21 @@ func main() { ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) go func() { <-ctx.Done() - defer cancel() + defer cancel() // restore interrupt function }() + // Initialize logger ctx, stop := lg.Init(ctx, appName) defer stop() - if err := run(ctx); err != nil && err != http.ErrServerClosed { + // Run application + if err := run(ctx); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Fatal(err) } } func run(ctx context.Context) error { g, ctx := errgroup.WithContext(ctx) + stop := &stopFns{} cron := cron.New(cron.DefaultGranularity) @@ -96,8 +101,9 @@ func run(ctx context.Context) error { enable := set.New(strings.Fields(env("EV_ENABLE", "salty msgbus gql peers"))...) var svcs []interface{ RegisterHTTP(*http.ServeMux) } + var res []resolver.IsResolver - svcs = append(svcs, es) + res = append(res, es) if enable.Has("salty") { span.AddEvent("Enable Salty") @@ -113,6 +119,7 @@ func run(ctx context.Context) error { return err } svcs = append(svcs, salty) + res = append(res, salty) } if enable.Has("msgbus") { @@ -123,6 +130,7 @@ func run(ctx context.Context) error { return err } svcs = append(svcs, msgbus) + res = append(res, msgbus) } if enable.Has("peers") { @@ -137,11 +145,15 @@ func run(ctx context.Context) error { cron.NewJob("0,15,30,45", peers.RefreshJob) cron.Once(ctx, peers.CleanJob) cron.NewJob("0 1", peers.CleanJob) + g.Go(func() error { + return peers.Run(ctx) + }) + stop.add(peers.Stop) } if enable.Has("gql") { span.AddEvent("Enable GraphQL") - gql, err := gql.New(ctx, svcs...) + gql, err := resolver.New(ctx, &gql.Resolver{}, res...) if err != nil { span.RecordError(err) return err @@ -164,18 +176,23 @@ func run(ctx context.Context) error { Mup.Add(ctx, 1) g.Go(s.ListenAndServe) - - g.Go(func() error { - <-ctx.Done() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - return s.Shutdown(ctx) - }) + stop.add(s.Shutdown) span.End() } - g.Go(func() error { return cron.Run(ctx) }) + g.Go(func() error { + <-ctx.Done() + // shutdown jobs + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + return stop.stop(ctx) + }) + g.Go(func() error { + return cron.Run(ctx) + }) if err := g.Wait(); err != nil && err != http.ErrServerClosed { return err @@ -203,3 +220,21 @@ var appName, version = func() (string, string) { return "sour.is-ev", "(devel)" }() + +type stopFns struct { + fns []func(context.Context) error +} + +func (s *stopFns) add(fn func(context.Context) error) { + s.fns = append(s.fns, fn) +} +func (s *stopFns) stop(ctx context.Context) error { + g, _ := errgroup.WithContext(ctx) + for i := range s.fns { + fn := s.fns[i] + g.Go(func() error { + return fn(ctx) + }) + } + return g.Wait() +} diff --git a/pkg/es/driver/driver.go b/pkg/es/driver/driver.go index 382a952..10ac089 100644 --- a/pkg/es/driver/driver.go +++ b/pkg/es/driver/driver.go @@ -28,7 +28,7 @@ type EventLogWithUpdate interface { } type Subscription interface { - Recv(context.Context) bool + Recv(context.Context) <-chan bool Events(context.Context) (event.Events, error) Close(context.Context) error } diff --git a/pkg/es/driver/streamer/streamer.go b/pkg/es/driver/streamer/streamer.go index 13b3552..b8b1f7f 100644 --- a/pkg/es/driver/streamer/streamer.go +++ b/pkg/es/driver/streamer/streamer.go @@ -4,6 +4,7 @@ package streamer import ( "context" "fmt" + "sync" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -67,6 +68,7 @@ func (s *streamer) Subscribe(ctx context.Context, streamID string, start int64) if err != nil { return nil, err } + sub := &subscription{topic: streamID, events: events} sub.position = locker.New(&position{ idx: start, @@ -86,12 +88,14 @@ func (s *streamer) Send(ctx context.Context, streamID string, events event.Event return s.state.Modify(ctx, func(ctx context.Context, state *state) error { ctx, span := lg.Span(ctx) defer span.End() + span.AddEvent(fmt.Sprint("subscribers=", len(state.subscribers[streamID]))) for _, sub := range state.subscribers[streamID] { err := sub.position.Modify(ctx, func(ctx context.Context, position *position) error { ctx, span := lg.Span(ctx) defer span.End() + span.SetAttributes( attribute.String("streamID", streamID), attribute.Int64("actualPosition", int64(events.Last().EventMeta().ActualPosition)), @@ -106,6 +110,7 @@ func (s *streamer) Send(ctx context.Context, streamID string, events event.Event position.link = trace.LinkFromContext(ctx, attribute.String("src", "event")) position.wait = nil } + return nil }) if err != nil { @@ -210,56 +215,65 @@ type subscription struct { events driver.EventLog unsub func(context.Context) error + once sync.Once } -func (s *subscription) Recv(ctx context.Context) bool { +func (s *subscription) Recv(ctx context.Context) <-chan bool { ctx, span := lg.Span(ctx) defer span.End() - var wait func(context.Context) bool + done := make(chan bool) - err := s.position.Modify(ctx, func(ctx context.Context, position *position) error { - _, span := lg.Span(ctx) - defer span.End() + go func() { + var wait func(context.Context) bool + defer close(done) - if position.size == es.AllEvents { - return nil - } - if position.size == 0 { - position.wait = make(chan struct{}) - wait = func(ctx context.Context) bool { - ctx, span := lg.Span(ctx) - defer span.End() + err := s.position.Modify(ctx, func(ctx context.Context, position *position) error { + _, span := lg.Span(ctx) + defer span.End() - select { - case <-position.wait: - if position.link.SpanContext.IsValid() { - _, span := lg.Span(ctx, trace.WithLinks(position.link)) - span.AddEvent("recv event") - span.End() - position.link = trace.Link{} + if position.size == es.AllEvents { + return nil + } + if position.size == 0 { + position.wait = make(chan struct{}) + wait = func(ctx context.Context) bool { + ctx, span := lg.Span(ctx) + defer span.End() + + select { + case <-position.wait: + if position.link.SpanContext.IsValid() { + _, span := lg.Span(ctx, trace.WithLinks(position.link)) + span.AddEvent("recv event") + span.End() + position.link = trace.Link{} + } + return true + + case <-ctx.Done(): + return false } - - return true - case <-ctx.Done(): - - return false } } + position.idx += position.size + position.size = 0 + return nil + }) + if err != nil { + done <- false + return } - position.idx += position.size - position.size = 0 - return nil - }) - if err != nil { - return false - } - if wait != nil { - return wait(ctx) - } + if wait != nil { + done <- wait(ctx) + return + } - return true + done <- true + }() + + return done } func (s *subscription) Events(ctx context.Context) (event.Events, error) { ctx, span := lg.Span(ctx) @@ -293,5 +307,12 @@ func (s *subscription) Close(ctx context.Context) error { ctx, span := lg.Span(ctx) defer span.End() - return s.unsub(ctx) + if s == nil || s.unsub == nil { + return nil + } + + var err error + s.once.Do(func() { err = s.unsub(ctx) }) + + return err } diff --git a/pkg/es/graph.go b/pkg/es/graph.go index a8301ad..e1329d7 100644 --- a/pkg/es/graph.go +++ b/pkg/es/graph.go @@ -23,6 +23,7 @@ type contextKey struct { var esKey = contextKey{"event-store"} +func (es *EventStore) IsResolver() {} func (es *EventStore) Events(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error) { ctx, span := lg.Span(ctx) defer span.End() @@ -51,10 +52,10 @@ func (es *EventStore) Events(ctx context.Context, streamID string, paging *gql.P 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, + Next: lis.Last().EventMeta().ActualPosition < last, + Prev: lis.First().EventMeta().ActualPosition > first, + Begin: lis.First().EventMeta().ActualPosition, + End: lis.Last().EventMeta().ActualPosition, }, Edges: edges, }, nil @@ -91,7 +92,7 @@ func (e *EventStore) EventAdded(ctx context.Context, streamID string, after int6 }() } - for sub.Recv(ctx) { + for <-sub.Recv(ctx) { events, err := sub.Events(ctx) if err != nil { span.RecordError(err) @@ -120,7 +121,6 @@ func (es *EventStore) TruncateStream(ctx context.Context, streamID string, index err := es.Truncate(ctx, streamID, index) return err == nil, err } -func (*EventStore) RegisterHTTP(*http.ServeMux) {} func (e *EventStore) GetMiddleware() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/app/gql/graphiql/playground.go b/pkg/gql/graphiql/playground.go similarity index 100% rename from app/gql/graphiql/playground.go rename to pkg/gql/graphiql/playground.go diff --git a/app/gql/playground/playground.go b/pkg/gql/playground/playground.go similarity index 100% rename from app/gql/playground/playground.go rename to pkg/gql/playground/playground.go diff --git a/pkg/gql/resolver/resolver.go b/pkg/gql/resolver/resolver.go new file mode 100644 index 0000000..a7289fc --- /dev/null +++ b/pkg/gql/resolver/resolver.go @@ -0,0 +1,141 @@ +package resolver + +import ( + "context" + "fmt" + "net/http" + "os" + "reflect" + "runtime/debug" + "time" + + "github.com/99designs/gqlgen/graphql" + "github.com/99designs/gqlgen/graphql/handler" + "github.com/99designs/gqlgen/graphql/handler/extension" + "github.com/99designs/gqlgen/graphql/handler/lru" + "github.com/99designs/gqlgen/graphql/handler/transport" + "github.com/vektah/gqlparser/v2/gqlerror" + + "github.com/gorilla/websocket" + "github.com/ravilushqa/otelgqlgen" + + "github.com/sour-is/ev/internal/lg" + "github.com/sour-is/ev/pkg/gql/graphiql" + "github.com/sour-is/ev/pkg/gql/playground" +) + +type BaseResolver interface { + ExecutableSchema() graphql.ExecutableSchema + BaseResolver() IsResolver +} + +type Resolver[T BaseResolver] struct { + res T +} +type IsResolver interface { + IsResolver() +} + +func New[T BaseResolver](ctx context.Context, base T, resolvers ...IsResolver) (*Resolver[T], error) { + _, span := lg.Span(ctx) + defer span.End() + + v := reflect.ValueOf(base) + v = reflect.Indirect(v) + noop := reflect.ValueOf(base.BaseResolver()) + +outer: + for _, idx := range reflect.VisibleFields(v.Type()) { + field := v.FieldByIndex(idx.Index) + + for i := range resolvers { + rs := reflect.ValueOf(resolvers[i]) + + if field.IsNil() && rs.Type().Implements(field.Type()) { + span.AddEvent(fmt.Sprint("found ", field.Type().Name())) + field.Set(rs) + continue outer + } + } + + span.AddEvent(fmt.Sprint("default ", field.Type().Name())) + field.Set(noop) + } + + return &Resolver[T]{base}, nil +} + +func (r *Resolver[T]) Resolver() T { + return r.res +} + +// ChainMiddlewares will check all embeded resolvers for a GetMiddleware func and add to handler. +func (r *Resolver[T]) ChainMiddlewares(h http.Handler) http.Handler { + v := reflect.ValueOf(r) // Get reflected value of *Resolver + v = reflect.Indirect(v) // Get the pointed value (returns a zero value on nil) + n := v.NumField() // Get number of fields to iterate over. + for i := 0; i < n; i++ { + f := v.Field(i) + if !f.CanInterface() { // Skip non-interface types. + continue + } + if iface, ok := f.Interface().(interface { + GetMiddleware() func(http.Handler) http.Handler + }); ok { + h = iface.GetMiddleware()(h) // Append only items that fulfill the interface. + } + } + + return h +} + +func (r *Resolver[T]) RegisterHTTP(mux *http.ServeMux) { + gql := NewServer(r.res.ExecutableSchema()) + gql.SetRecoverFunc(NoopRecover) + gql.Use(otelgqlgen.Middleware()) + mux.Handle("/graphiql", graphiql.Handler("GraphiQL playground", "/gql")) + mux.Handle("/gql", lg.Htrace(r.ChainMiddlewares(gql), "gql")) + mux.Handle("/playground", playground.Handler("GraphQL playground", "/gql")) +} + +func NoopRecover(ctx context.Context, err interface{}) error { + if err, ok := err.(string); ok && err == "not implemented" { + return gqlerror.Errorf("not implemented") + } + fmt.Fprintln(os.Stderr, err) + fmt.Fprintln(os.Stderr) + debug.PrintStack() + + return gqlerror.Errorf("internal system error") +} + +func NewServer(es graphql.ExecutableSchema) *handler.Server { + srv := handler.New(es) + + srv.AddTransport(transport.Websocket{ + Upgrader: websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + switch r.Header.Get("Origin") { + case "https://ev.sour.is", "https://www.graphqlbin.com", "http://localhost:8080": + return true + default: + return false + } + }, + }, + KeepAlivePingInterval: 10 * time.Second, + }) + srv.AddTransport(transport.Options{}) + srv.AddTransport(transport.GET{}) + srv.AddTransport(transport.POST{}) + srv.AddTransport(transport.MultipartForm{}) + + srv.SetQueryCache(lru.New(1000)) + + srv.Use(extension.Introspection{}) + srv.Use(extension.AutomaticPersistedQuery{ + Cache: lru.New(100), + }) + + return srv +}