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 } }`) }