feat: inprogress
This commit is contained in:
62
app/gql/playground/playground.go
Normal file
62
app/gql/playground/playground.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package playground
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var page = template.Must(template.New("graphiql").Parse(`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset=utf-8/>
|
||||
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
|
||||
<link rel="shortcut icon" href="https://graphcool-playground.netlify.com/favicon.png">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/graphql-playground-react@{{ .version }}/build/static/css/index.css"
|
||||
integrity="{{ .cssSRI }}" crossorigin="anonymous"/>
|
||||
<link rel="shortcut icon" href="https://cdn.jsdelivr.net/npm/graphql-playground-react@{{ .version }}/build/favicon.png"
|
||||
integrity="{{ .faviconSRI }}" crossorigin="anonymous"/>
|
||||
<script src="https://cdn.jsdelivr.net/npm/graphql-playground-react@{{ .version }}/build/static/js/middleware.js"
|
||||
integrity="{{ .jsSRI }}" crossorigin="anonymous"></script>
|
||||
<title>{{.title}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<style type="text/css">
|
||||
html { font-family: "Open Sans", sans-serif; overflow: hidden; }
|
||||
body { margin: 0; background: #172a3a; }
|
||||
</style>
|
||||
<div id="root"/>
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('load', function (event) {
|
||||
const root = document.getElementById('root');
|
||||
root.classList.add('playgroundIn');
|
||||
const wsProto = location.protocol == 'https:' ? 'wss:' : 'ws:'
|
||||
GraphQLPlayground.init(root, {
|
||||
endpoint: location.protocol + '//' + location.host + '{{.endpoint}}',
|
||||
subscriptionsEndpoint: wsProto + '//' + location.host + '{{.endpoint }}',
|
||||
shareEnabled: true,
|
||||
settings: {
|
||||
'request.credentials': 'same-origin'
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`))
|
||||
|
||||
func Handler(title string, endpoint string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "text/html")
|
||||
err := page.Execute(w, map[string]string{
|
||||
"title": title,
|
||||
"endpoint": endpoint,
|
||||
"version": "1.7.26",
|
||||
"cssSRI": "sha256-dKnNLEFwKSVFpkpjRWe+o/jQDM6n/JsvQ0J3l5Dk3fc=",
|
||||
"faviconSRI": "sha256-GhTyE+McTU79R4+pRO6ih+4TfsTOrpPwD8ReKFzb3PM=",
|
||||
"jsSRI": "sha256-SG9YAy4eywTcLckwij7V4oSCG3hOdV1m+2e1XuNxIgk=",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
123
app/gql/resolver.go
Normal file
123
app/gql/resolver.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package gql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql/handler"
|
||||
"github.com/ravilushqa/otelgqlgen"
|
||||
"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/logz"
|
||||
"github.com/sour-is/ev/pkg/gql"
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
)
|
||||
|
||||
type Resolver struct {
|
||||
msgbus.MsgbusResolver
|
||||
salty.SaltyResolver
|
||||
}
|
||||
|
||||
func New(ctx context.Context, resolvers ...interface{ RegisterHTTP(*http.ServeMux) }) (*Resolver, error) {
|
||||
_, span := logz.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)
|
||||
break 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 }
|
||||
|
||||
// Query returns generated.QueryResolver implementation.
|
||||
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) RegisterHTTP(mux *http.ServeMux) {
|
||||
gql := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: r}))
|
||||
gql.SetRecoverFunc(NoopRecover)
|
||||
gql.Use(otelgqlgen.Middleware())
|
||||
mux.Handle("/", playground.Handler("GraphQL playground", "/gql"))
|
||||
mux.Handle("/gql", logz.Htrace(r.ChainMiddlewares(gql), "gql"))
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
func (*noop) CreateSaltyUser(ctx context.Context, nick string, pubkey string) (*salty.SaltyUser, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (*noop) Posts(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (*noop) SaltyUser(ctx context.Context, nick string) (*salty.SaltyUser, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (*noop) PostAdded(ctx context.Context, streamID string, after int64) (<-chan *msgbus.PostEvent, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (*noop) RegisterHTTP(*http.ServeMux) {}
|
||||
Reference in New Issue
Block a user