Added GraphQL API

This commit is contained in:
Eduard Urbach 2018-04-08 17:30:32 +02:00
parent 12b0cb00b2
commit ae610c9e9d
4 changed files with 303 additions and 4 deletions

86
graphql.go Normal file
View File

@ -0,0 +1,86 @@
package main
import (
"fmt"
"log"
"net/http"
"reflect"
"github.com/aerogo/aero"
"github.com/animenotifier/arn"
"github.com/animenotifier/notify.moe/utils/gql"
"github.com/fatih/color"
"github.com/graphql-go/graphql"
)
func init() {
rootQueryFields := graphql.Fields{}
for name, typ := range arn.DB.Types() {
if typ.Kind() != reflect.Struct {
continue
}
// Bind name for the closure
typeName := name
fmt.Println(typeName)
rootQueryFields[typeName] = &graphql.Field{
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{
Type: graphql.String,
DefaultValue: "",
},
},
Type: graphql.NewObject(graphql.ObjectConfig{
Name: typeName,
Fields: gql.BindFields(typ),
}),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
id := p.Args["id"].(string)
return arn.DB.Get(typeName, id)
},
}
}
// Schema
rootQuery := graphql.ObjectConfig{
Name: "RootQuery",
Fields: rootQueryFields,
}
schemaConfig := graphql.SchemaConfig{
Query: graphql.NewObject(rootQuery),
}
schema, err := graphql.NewSchema(schemaConfig)
if err != nil {
log.Fatalf("failed to create new schema, error: %v", err)
}
app.Post("/graphql", func(ctx *aero.Context) string {
body, err := ctx.Request().Body().JSONObject()
if err != nil {
return ctx.Error(http.StatusBadRequest, "Expected JSON data containing a query and variables", err)
}
query := body["query"].(string)
variables := body["variables"].(map[string]interface{})
params := graphql.Params{
Schema: schema,
RequestString: query,
VariableValues: variables,
}
result := graphql.Do(params)
if len(result.Errors) > 0 {
color.Red("failed to execute graphql operation, errors: %+v", result.Errors)
}
return ctx.JSON(result)
})
}

View File

@ -37,9 +37,9 @@ func Get(ctx *aero.Context) string {
},
}
if company.Image != "" {
openGraph.Tags["og:image"] = company.Image
}
// if company.Image != "" {
// openGraph.Tags["og:image"] = company.Image
// }
if description != "" {
openGraph.Tags["og:description"] = description

View File

@ -25,7 +25,7 @@ func Edit(ctx *aero.Context) string {
"og:title": company.Name.English,
"og:url": "https://" + ctx.App.Config.Domain + company.Link(),
"og:site_name": "notify.moe",
"og:image": company.Image,
// "og:image": company.Image,
},
}

213
utils/gql/BindFields.go Normal file
View File

@ -0,0 +1,213 @@
package gql
import (
"fmt"
"reflect"
"strings"
"github.com/graphql-go/graphql"
)
// TAG is the tag used for json
const TAG = "json"
// BindFields can't take recursive slice type
// e.g
// type Person struct{
// Friends []Person
// }
// it will throw panic stack-overflow
func BindFields(t reflect.Type) graphql.Fields {
fields := make(map[string]*graphql.Field)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
// Skip private fields
if field.Tag.Get("private") == "true" {
continue
}
tag := extractTag(field.Tag)
if tag == "-" {
continue
}
fieldType := field.Type
if fieldType.Kind() == reflect.Ptr {
fieldType = fieldType.Elem()
}
var graphType graphql.Output
if fieldType.Kind() == reflect.Struct {
structFields := BindFields(t.Field(i).Type)
if tag == "" {
fields = appendFields(fields, structFields)
continue
} else {
graphType = graphql.NewObject(graphql.ObjectConfig{
Name: t.Name() + "_" + field.Name,
Fields: structFields,
})
}
}
if tag == "" {
continue
}
if graphType == nil {
graphType = getGraphType(fieldType)
}
fields[tag] = &graphql.Field{
Type: graphType,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return extractValue(tag, p.Source), nil
},
}
}
return fields
}
func getGraphType(tipe reflect.Type) graphql.Output {
kind := tipe.Kind()
switch kind {
case reflect.String:
return graphql.String
case reflect.Int:
fallthrough
case reflect.Int8:
fallthrough
case reflect.Int32:
fallthrough
case reflect.Int64:
return graphql.Int
case reflect.Float32:
fallthrough
case reflect.Float64:
return graphql.Float
case reflect.Bool:
return graphql.Boolean
case reflect.Slice:
return getGraphList(tipe)
}
return graphql.String
}
func getGraphList(tipe reflect.Type) *graphql.List {
if tipe.Kind() == reflect.Slice {
switch tipe.Elem().Kind() {
case reflect.Int:
fallthrough
case reflect.Int8:
fallthrough
case reflect.Int32:
fallthrough
case reflect.Int64:
return graphql.NewList(graphql.Int)
case reflect.Bool:
return graphql.NewList(graphql.Boolean)
case reflect.Float32:
fallthrough
case reflect.Float64:
return graphql.NewList(graphql.Float)
case reflect.String:
return graphql.NewList(graphql.String)
}
}
// finaly bind object
t := reflect.New(tipe.Elem())
name := strings.Replace(fmt.Sprint(tipe.Elem()), ".", "_", -1)
obj := graphql.NewObject(graphql.ObjectConfig{
Name: name,
Fields: BindFields(t.Elem().Type()),
})
return graphql.NewList(obj)
}
func appendFields(dest, origin graphql.Fields) graphql.Fields {
for key, value := range origin {
dest[key] = value
}
return dest
}
func extractValue(originTag string, obj interface{}) interface{} {
val := reflect.Indirect(reflect.ValueOf(obj))
for j := 0; j < val.NumField(); j++ {
field := val.Type().Field(j)
if field.Type.Kind() == reflect.Struct {
res := extractValue(originTag, val.Field(j).Interface())
if res != nil {
return res
}
}
if originTag == extractTag(field.Tag) {
return reflect.Indirect(val.Field(j)).Interface()
}
}
return nil
}
func extractTag(tag reflect.StructTag) string {
t := tag.Get(TAG)
if t != "" {
t = strings.Split(t, ",")[0]
}
return t
}
// BindArg is a lazy way of binding args
func BindArg(obj interface{}, tags ...string) graphql.FieldConfigArgument {
v := reflect.Indirect(reflect.ValueOf(obj))
var config = make(graphql.FieldConfigArgument)
for i := 0; i < v.NumField(); i++ {
field := v.Type().Field(i)
mytag := extractTag(field.Tag)
if inArray(tags, mytag) {
config[mytag] = &graphql.ArgumentConfig{
Type: getGraphType(field.Type),
}
}
}
return config
}
func inArray(slice interface{}, item interface{}) bool {
s := reflect.ValueOf(slice)
if s.Kind() != reflect.Slice {
panic("inArray() given a non-slice type")
}
for i := 0; i < s.Len(); i++ {
if reflect.DeepEqual(item, s.Index(i).Interface()) {
return true
}
}
return false
}