Last active
March 16, 2023 23:03
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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