diff --git a/graphql.go b/graphql.go new file mode 100644 index 00000000..c582a93b --- /dev/null +++ b/graphql.go @@ -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) + }) +} diff --git a/pages/company/company.go b/pages/company/company.go index beb0abb9..53fc0fb0 100644 --- a/pages/company/company.go +++ b/pages/company/company.go @@ -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 diff --git a/pages/company/edit.go b/pages/company/edit.go index 44c9a38a..aa501adf 100644 --- a/pages/company/edit.go +++ b/pages/company/edit.go @@ -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, }, } diff --git a/utils/gql/BindFields.go b/utils/gql/BindFields.go new file mode 100644 index 00000000..a76bd58e --- /dev/null +++ b/utils/gql/BindFields.go @@ -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 +}