Skip to content

Instantly share code, notes, and snippets.

@JoshCheek
Last active March 16, 2023 23:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JoshCheek/0654a3570b7f3db6e1dfc459488695f1 to your computer and use it in GitHub Desktop.
Save JoshCheek/0654a3570b7f3db6e1dfc459488695f1 to your computer and use it in GitHub Desktop.
Example of how we can prob use resolvers in graphql-go so that you only need to be authenticated if you are asking for fields that require it.
package main
import (
"context"
"encoding/json"
"fmt"
gql "github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/directives"
)
// Made up schema: you can ask for a course, and if you are logged in, then you
// can ask if you are enrolled in that course.
var s = `
directive @isAuthenticated on FIELD_DEFINITION
type Query {
course(name: String!): Course
}
type Course {
name: String!
enrolled: Boolean! @isAuthenticated
}
`
type RootResolver struct{}
type QueryResolver struct{ user *User }
type CourseResolver struct {
user *User
requestedCourse string
}
// NOTE THAT THIS DIRECTIVE THING CHANGED COMPARED WITH WHAT WE HAVE IN OUR REPO
// you will have to update it with `go get github.com/graph-gophers/graphql-go@master`
// and then you will have to change the directive to look like this,
// and change teh way we pass it in the shema options to match what is in the main fn
type IsAuthenticatedDirective struct{}
func (h *IsAuthenticatedDirective) Resolve(ctx context.Context, args interface{}, next directives.Resolver) (output any, err error) {
_, ok := ctx.Value("currentUser").(*User)
if !ok {
return nil, fmt.Errorf("Unauthorized")
}
return next.Resolve(ctx, args)
}
func (h *IsAuthenticatedDirective) ImplementsDirective() string {
return "isAuthenticated"
}
// users will just have a "set" of courses they're enrolled in
type User struct{ courses map[string]bool }
// This doesn't work for some reason, I want to grab the user from the context in the root resolver
// https://github.com/graph-gophers/graphql-go/blob/224841f523b3b94f1d75a0d428719a77b9bfc8a8/internal/exec/resolvable/resolvable.go#L198
//
// func (r *RootResolver) Query(ctx context.Context) *QueryResolver {
// user, _ := ctx.Value("currentUser").(*User)
// return &QueryResolver{user: user}
// }
func (r *RootResolver) Query() *QueryResolver {
return &QueryResolver{user: nil}
}
func (r *QueryResolver) Course(args *struct{ Name string }) *CourseResolver {
return &CourseResolver{user: r.user, requestedCourse: args.Name}
}
func (r *CourseResolver) Name() string {
return r.requestedCourse
}
// Really stupid, it has to receive the context b/c the query resolver couldn't
// https://github.com/graph-gophers/graphql-go/blob/224841f523b3b94f1d75a0d428719a77b9bfc8a8/internal/exec/resolvable/resolvable.go#L198
// So now every authenticated field will have to pull the current user out of the
// context instead of being able to do that at the root query object.
func (r *CourseResolver) Enrolled(ctx context.Context) bool {
// _, isEnrolled := r.user.courses[r.requestedCourse] // <-- what we want to do, we know the user is present b/c this field is authenticated
user := ctx.Value("currentUser").(*User) // <-- what we have to do instead
_, isEnrolled := user.courses[r.requestedCourse]
return isEnrolled
}
func main() {
opts := []gql.SchemaOpt{
gql.UseFieldResolvers(),
gql.Directives(&IsAuthenticatedDirective{}),
}
schema := gql.MustParseSchema(s, &RootResolver{}, opts...)
run := func(user *User, query string) {
ctx := context.Background()
if user != nil {
ctx = context.WithValue(ctx, "currentUser", user) // in the app, this comes from a middleware
}
response := schema.Exec(ctx, query, "", map[string]any{})
json, _ := json.Marshal(response)
fmt.Printf("response: %s\n", json)
}
u := &User{courses: map[string]bool{"course2": true}}
run(u, `query { course(name: "course1") { name enrolled } }`)
run(u, `query { course(name: "course2") { name enrolled } }`)
run(u, `query { course(name: "course3") { name enrolled } }`)
run(nil, `query { course(name: "course4") { name enrolled } }`)
run(nil, `query { course(name: "course5") { name } }`)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment