Added GraphQL API
This commit is contained in:
parent
12b0cb00b2
commit
ae610c9e9d
86
graphql.go
Normal file
86
graphql.go
Normal 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)
|
||||
})
|
||||
}
|
@ -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
|
||||
|
@ -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
213
utils/gql/BindFields.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user