Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
6716b97a39 | |||
7169dd65f5 | |||
b26cdbf960 |
@ -197,30 +197,26 @@ func (c *Config) AddRoleTable(role string, table string, conf interface{}) error
|
||||
// ReadInConfig function reads in the config file for the environment specified in the GO_ENV
|
||||
// environment variable. This is the best way to create a new Super Graph config.
|
||||
func ReadInConfig(configFile string) (*Config, error) {
|
||||
cpath := path.Dir(configFile)
|
||||
cfile := path.Base(configFile)
|
||||
vi := newViper(cpath, cfile)
|
||||
cp := path.Dir(configFile)
|
||||
vi := newViper(cp, path.Base(configFile))
|
||||
|
||||
if err := vi.ReadInConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inherits := vi.GetString("inherits")
|
||||
|
||||
if inherits != "" {
|
||||
vi = newViper(cpath, inherits)
|
||||
if pcf := vi.GetString("inherits"); pcf != "" {
|
||||
cf := vi.ConfigFileUsed()
|
||||
vi = newViper(cp, pcf)
|
||||
|
||||
if err := vi.ReadInConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if vi.IsSet("inherits") {
|
||||
return nil, fmt.Errorf("inherited config (%s) cannot itself inherit (%s)",
|
||||
inherits,
|
||||
vi.GetString("inherits"))
|
||||
if v := vi.GetString("inherits"); v != "" {
|
||||
return nil, fmt.Errorf("inherited config (%s) cannot itself inherit (%s)", pcf, v)
|
||||
}
|
||||
|
||||
vi.SetConfigName(cfile)
|
||||
vi.SetConfigFile(cf)
|
||||
|
||||
if err := vi.MergeInConfig(); err != nil {
|
||||
return nil, err
|
||||
@ -234,7 +230,7 @@ func ReadInConfig(configFile string) (*Config, error) {
|
||||
}
|
||||
|
||||
if c.AllowListFile == "" {
|
||||
c.AllowListFile = path.Join(cpath, "allow.list")
|
||||
c.AllowListFile = path.Join(cp, "allow.list")
|
||||
}
|
||||
|
||||
return c, nil
|
||||
@ -248,7 +244,7 @@ func newViper(configPath, configFile string) *viper.Viper {
|
||||
vi.AutomaticEnv()
|
||||
|
||||
if filepath.Ext(configFile) != "" {
|
||||
vi.SetConfigFile(configFile)
|
||||
vi.SetConfigFile(path.Join(configPath, configFile))
|
||||
} else {
|
||||
vi.SetConfigName(configFile)
|
||||
vi.AddConfigPath(configPath)
|
||||
|
@ -10,21 +10,23 @@ import (
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
|
||||
"github.com/chirino/graphql/schema"
|
||||
"github.com/dosco/super-graph/jsn"
|
||||
)
|
||||
|
||||
const (
|
||||
AL_QUERY int = iota + 1
|
||||
AL_VARS
|
||||
expComment = iota + 1
|
||||
expVar
|
||||
expQuery
|
||||
)
|
||||
|
||||
type Item struct {
|
||||
Name string
|
||||
key string
|
||||
Query string
|
||||
Vars json.RawMessage
|
||||
Vars string
|
||||
Comment string
|
||||
}
|
||||
|
||||
@ -126,121 +128,101 @@ func (al *List) Set(vars []byte, query, comment string) error {
|
||||
return errors.New("empty query")
|
||||
}
|
||||
|
||||
var q string
|
||||
|
||||
for i := 0; i < len(query); i++ {
|
||||
c := query[i]
|
||||
if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' {
|
||||
q = query
|
||||
break
|
||||
|
||||
} else if c == '{' {
|
||||
q = "query " + query
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
al.saveChan <- Item{
|
||||
Comment: comment,
|
||||
Query: q,
|
||||
Vars: vars,
|
||||
Query: query,
|
||||
Vars: string(vars),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (al *List) Load() ([]Item, error) {
|
||||
var list []Item
|
||||
varString := "variables"
|
||||
|
||||
b, err := ioutil.ReadFile(al.filepath)
|
||||
if err != nil {
|
||||
return list, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(b) == 0 {
|
||||
return list, nil
|
||||
return parse(string(b), al.filepath)
|
||||
}
|
||||
|
||||
func parse(b string, filename string) ([]Item, error) {
|
||||
var items []Item
|
||||
|
||||
var s scanner.Scanner
|
||||
s.Init(strings.NewReader(b))
|
||||
s.Filename = filename
|
||||
s.Mode ^= scanner.SkipComments
|
||||
|
||||
var op, sp scanner.Position
|
||||
var item Item
|
||||
|
||||
newComment := false
|
||||
st := expComment
|
||||
|
||||
for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
|
||||
txt := s.TokenText()
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(txt, "/*"):
|
||||
if st == expQuery {
|
||||
v := b[sp.Offset:s.Pos().Offset]
|
||||
item.Query = strings.TrimSpace(v[:strings.LastIndexByte(v, '}')+1])
|
||||
items = append(items, item)
|
||||
}
|
||||
item = Item{Comment: strings.TrimSpace(txt[2 : len(txt)-2])}
|
||||
sp = s.Pos()
|
||||
st = expComment
|
||||
newComment = true
|
||||
|
||||
case !newComment && strings.HasPrefix(txt, "#"):
|
||||
if st == expQuery {
|
||||
v := b[sp.Offset:s.Pos().Offset]
|
||||
item.Query = strings.TrimSpace(v[:strings.LastIndexByte(v, '}')+1])
|
||||
items = append(items, item)
|
||||
}
|
||||
item = Item{}
|
||||
sp = s.Pos()
|
||||
st = expComment
|
||||
|
||||
case strings.HasPrefix(txt, "variables"):
|
||||
if st == expComment {
|
||||
v := b[sp.Offset:s.Pos().Offset]
|
||||
item.Comment = strings.TrimSpace(v[:strings.IndexByte(v, '\n')])
|
||||
}
|
||||
sp = s.Pos()
|
||||
st = expVar
|
||||
|
||||
case isGraphQL(txt):
|
||||
if st == expVar {
|
||||
v := b[sp.Offset:s.Pos().Offset]
|
||||
item.Vars = strings.TrimSpace(v[:strings.LastIndexByte(v, '}')+1])
|
||||
}
|
||||
sp = op
|
||||
st = expQuery
|
||||
|
||||
}
|
||||
op = s.Pos()
|
||||
}
|
||||
|
||||
var comment bytes.Buffer
|
||||
var varBytes []byte
|
||||
|
||||
itemMap := make(map[string]struct{})
|
||||
|
||||
s, e, c := 0, 0, 0
|
||||
ty := 0
|
||||
|
||||
for {
|
||||
fq := false
|
||||
|
||||
if c == 0 && b[e] == '#' {
|
||||
s = e
|
||||
for e < len(b) && b[e] != '\n' {
|
||||
e++
|
||||
}
|
||||
if (e - s) > 2 {
|
||||
comment.Write(b[(s + 1):(e + 1)])
|
||||
}
|
||||
}
|
||||
|
||||
if e >= len(b) {
|
||||
break
|
||||
}
|
||||
|
||||
if matchPrefix(b, e, "query") || matchPrefix(b, e, "mutation") {
|
||||
if c == 0 {
|
||||
s = e
|
||||
}
|
||||
ty = AL_QUERY
|
||||
} else if matchPrefix(b, e, varString) {
|
||||
if c == 0 {
|
||||
s = e + len(varString) + 1
|
||||
}
|
||||
ty = AL_VARS
|
||||
} else if b[e] == '{' {
|
||||
c++
|
||||
|
||||
} else if b[e] == '}' {
|
||||
c--
|
||||
|
||||
if c == 0 {
|
||||
if ty == AL_QUERY {
|
||||
fq = true
|
||||
} else if ty == AL_VARS {
|
||||
varBytes = b[s:(e + 1)]
|
||||
}
|
||||
ty = 0
|
||||
}
|
||||
}
|
||||
|
||||
if fq {
|
||||
query := string(b[s:(e + 1)])
|
||||
name := QueryName(query)
|
||||
key := strings.ToLower(name)
|
||||
|
||||
if _, ok := itemMap[key]; !ok {
|
||||
v := Item{
|
||||
Name: name,
|
||||
key: key,
|
||||
Query: query,
|
||||
Vars: varBytes,
|
||||
Comment: comment.String(),
|
||||
}
|
||||
list = append(list, v)
|
||||
comment.Reset()
|
||||
}
|
||||
|
||||
varBytes = nil
|
||||
|
||||
}
|
||||
|
||||
e++
|
||||
if e >= len(b) {
|
||||
break
|
||||
}
|
||||
if st == expQuery {
|
||||
v := b[sp.Offset:s.Pos().Offset]
|
||||
item.Query = strings.TrimSpace(v[:strings.LastIndexByte(v, '}')+1])
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
for i := range items {
|
||||
items[i].Name = QueryName(items[i].Query)
|
||||
items[i].key = strings.ToLower(items[i].Name)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func isGraphQL(s string) bool {
|
||||
return strings.HasPrefix(s, "query") ||
|
||||
strings.HasPrefix(s, "mutation") ||
|
||||
strings.HasPrefix(s, "subscription")
|
||||
}
|
||||
|
||||
func (al *List) save(item Item) error {
|
||||
@ -297,57 +279,39 @@ func (al *List) save(item Item) error {
|
||||
return strings.Compare(list[i].key, list[j].key) == -1
|
||||
})
|
||||
|
||||
for _, v := range list {
|
||||
cmtLines := strings.Split(v.Comment, "\n")
|
||||
|
||||
i := 0
|
||||
for _, c := range cmtLines {
|
||||
if c = strings.TrimSpace(c); c == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := f.WriteString(fmt.Sprintf("# %s\n", c))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
if i != 0 {
|
||||
if _, err := f.WriteString("\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, err := f.WriteString(fmt.Sprintf("# Query named %s\n\n", v.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(v.Vars) != 0 && !bytes.Equal(v.Vars, []byte("{}")) {
|
||||
for i, v := range list {
|
||||
var vars string
|
||||
if v.Vars != "" {
|
||||
buf.Reset()
|
||||
|
||||
if err := jsn.Clear(&buf, v.Vars); err != nil {
|
||||
return fmt.Errorf("failed to clean vars: %w", err)
|
||||
if err := jsn.Clear(&buf, []byte(v.Vars)); err != nil {
|
||||
continue
|
||||
}
|
||||
vj := json.RawMessage(buf.Bytes())
|
||||
|
||||
vj, err = json.MarshalIndent(vj, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal vars: %w", err)
|
||||
if vj, err = json.MarshalIndent(vj, "", " "); err != nil {
|
||||
continue
|
||||
}
|
||||
vars = string(vj)
|
||||
}
|
||||
list[i].Vars = vars
|
||||
list[i].Comment = strings.TrimSpace(v.Comment)
|
||||
}
|
||||
|
||||
_, err = f.WriteString(fmt.Sprintf("variables %s\n\n", vj))
|
||||
for _, v := range list {
|
||||
if v.Comment != "" {
|
||||
f.WriteString(fmt.Sprintf("/* %s */\n\n", v.Comment))
|
||||
} else {
|
||||
f.WriteString(fmt.Sprintf("/* %s */\n\n", v.Name))
|
||||
}
|
||||
|
||||
if v.Vars != "" {
|
||||
_, err = f.WriteString(fmt.Sprintf("variables %s\n\n", v.Vars))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if v.Query[0] == '{' {
|
||||
_, err = f.WriteString(fmt.Sprintf("query %s\n\n", v.Query))
|
||||
} else {
|
||||
_, err = f.WriteString(fmt.Sprintf("%s\n\n", v.Query))
|
||||
}
|
||||
|
||||
_, err = f.WriteString(fmt.Sprintf("%s\n\n", v.Query))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -82,3 +82,160 @@ func TestGQLName5(t *testing.T) {
|
||||
t.Fatal("Name should be empty, not ", name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse1(t *testing.T) {
|
||||
var al = `
|
||||
# Hello world
|
||||
|
||||
variables {
|
||||
"data": {
|
||||
"slug": "",
|
||||
"body": "",
|
||||
"post": {
|
||||
"connect": {
|
||||
"slug": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutation createComment {
|
||||
comment(insert: $data) {
|
||||
slug
|
||||
body
|
||||
createdAt: created_at
|
||||
totalVotes: cached_votes_total
|
||||
totalReplies: cached_replies_total
|
||||
vote: comment_vote(where: {user_id: {eq: $user_id}}) {
|
||||
created_at
|
||||
__typename
|
||||
}
|
||||
author: user {
|
||||
slug
|
||||
firstName: first_name
|
||||
lastName: last_name
|
||||
pictureURL: picture_url
|
||||
bio
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
}
|
||||
|
||||
# Query named createPost
|
||||
|
||||
query createPost {
|
||||
post(insert: $data) {
|
||||
slug
|
||||
body
|
||||
published
|
||||
createdAt: created_at
|
||||
totalVotes: cached_votes_total
|
||||
totalComments: cached_comments_total
|
||||
vote: post_vote(where: {user_id: {eq: $user_id}}) {
|
||||
created_at
|
||||
__typename
|
||||
}
|
||||
author: user {
|
||||
slug
|
||||
firstName: first_name
|
||||
lastName: last_name
|
||||
pictureURL: picture_url
|
||||
bio
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
}`
|
||||
|
||||
_, err := parse(al, "allow.list")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse2(t *testing.T) {
|
||||
var al = `
|
||||
/* Hello world */
|
||||
|
||||
variables {
|
||||
"data": {
|
||||
"slug": "",
|
||||
"body": "",
|
||||
"post": {
|
||||
"connect": {
|
||||
"slug": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutation createComment {
|
||||
comment(insert: $data) {
|
||||
slug
|
||||
body
|
||||
createdAt: created_at
|
||||
totalVotes: cached_votes_total
|
||||
totalReplies: cached_replies_total
|
||||
vote: comment_vote(where: {user_id: {eq: $user_id}}) {
|
||||
created_at
|
||||
__typename
|
||||
}
|
||||
author: user {
|
||||
slug
|
||||
firstName: first_name
|
||||
lastName: last_name
|
||||
pictureURL: picture_url
|
||||
bio
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Query named createPost
|
||||
*/
|
||||
|
||||
variables {
|
||||
"data": {
|
||||
"thread": {
|
||||
"connect": {
|
||||
"slug": ""
|
||||
}
|
||||
},
|
||||
"slug": "",
|
||||
"published": false,
|
||||
"body": ""
|
||||
}
|
||||
}
|
||||
|
||||
query createPost {
|
||||
post(insert: $data) {
|
||||
slug
|
||||
body
|
||||
published
|
||||
createdAt: created_at
|
||||
totalVotes: cached_votes_total
|
||||
totalComments: cached_comments_total
|
||||
vote: post_vote(where: {user_id: {eq: $user_id}}) {
|
||||
created_at
|
||||
__typename
|
||||
}
|
||||
author: user {
|
||||
slug
|
||||
firstName: first_name
|
||||
lastName: last_name
|
||||
pictureURL: picture_url
|
||||
bio
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
}`
|
||||
|
||||
_, err := parse(al, "allow.list")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package qcode
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/maphash"
|
||||
@ -381,9 +382,10 @@ func (p *Parser) parseFields(fields []Field) ([]Field, error) {
|
||||
n := int32(len(fields))
|
||||
fields = append(fields, fr.Fields...)
|
||||
|
||||
for i := int(n); i < len(fields); i++ {
|
||||
f := &fields[i]
|
||||
f.ID = int32(i)
|
||||
for i := 0; i < len(fr.Fields); i++ {
|
||||
k := (n + int32(i))
|
||||
f := &fields[k]
|
||||
f.ID = int32(k)
|
||||
|
||||
// If this is the top-level point the parent to the parent of the
|
||||
// previous field.
|
||||
@ -398,6 +400,9 @@ func (p *Parser) parseFields(fields []Field) ([]Field, error) {
|
||||
f.ParentID += n
|
||||
}
|
||||
|
||||
f.Children = make([]int32, len(f.Children))
|
||||
copy(f.Children, fr.Fields[i].Children)
|
||||
|
||||
// Update all the children which is needed.
|
||||
for j := range f.Children {
|
||||
f.Children[j] += n
|
||||
@ -685,6 +690,16 @@ func (p *Parser) reset(to int) {
|
||||
p.pos = to
|
||||
}
|
||||
|
||||
func (p *Parser) fHash(name string, parentID int32) uint64 {
|
||||
var b []byte
|
||||
binary.LittleEndian.PutUint32(b, uint32(parentID))
|
||||
p.h.WriteString(name)
|
||||
p.h.Write(b)
|
||||
v := p.h.Sum64()
|
||||
p.h.Reset()
|
||||
return v
|
||||
}
|
||||
|
||||
func b2s(b []byte) string {
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
}
|
||||
|
@ -239,6 +239,29 @@ var gql = []byte(`
|
||||
price
|
||||
}}`)
|
||||
|
||||
var gqlWithFragments = []byte(`
|
||||
fragment userFields1 on user {
|
||||
id
|
||||
email
|
||||
__typename
|
||||
}
|
||||
|
||||
query {
|
||||
users {
|
||||
...userFields2
|
||||
|
||||
created_at
|
||||
...userFields1
|
||||
__typename
|
||||
}
|
||||
}
|
||||
|
||||
fragment userFields2 on user {
|
||||
first_name
|
||||
last_name
|
||||
__typename
|
||||
}`)
|
||||
|
||||
func BenchmarkQCompile(b *testing.B) {
|
||||
qcompile, _ := NewCompiler(Config{})
|
||||
|
||||
@ -271,6 +294,21 @@ func BenchmarkQCompileP(b *testing.B) {
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkQCompileFragment(b *testing.B) {
|
||||
qcompile, _ := NewCompiler(Config{})
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, err := qcompile.Compile(gqlWithFragments, "user")
|
||||
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkParse(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
@ -298,6 +336,18 @@ func BenchmarkParseP(b *testing.B) {
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkParseFragment(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, err := Parse(gqlWithFragments)
|
||||
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSchemaParse(b *testing.B) {
|
||||
|
||||
b.ResetTimer()
|
||||
|
@ -419,6 +419,7 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
|
||||
com.AddFilters(qc, s, role)
|
||||
|
||||
s.Cols = make([]Column, 0, len(field.Children))
|
||||
cm := make(map[string]struct{})
|
||||
action = QTQuery
|
||||
|
||||
for _, cid := range field.Children {
|
||||
@ -428,19 +429,27 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
|
||||
continue
|
||||
}
|
||||
|
||||
var fname string
|
||||
|
||||
if f.Alias != "" {
|
||||
fname = f.Alias
|
||||
} else {
|
||||
fname = f.Name
|
||||
}
|
||||
|
||||
if _, ok := cm[fname]; ok {
|
||||
continue
|
||||
} else {
|
||||
cm[fname] = struct{}{}
|
||||
}
|
||||
|
||||
if len(f.Children) != 0 {
|
||||
val := f.ID | (s.ID << 16)
|
||||
st.Push(val)
|
||||
continue
|
||||
}
|
||||
|
||||
col := Column{Name: f.Name}
|
||||
|
||||
if len(f.Alias) != 0 {
|
||||
col.FieldName = f.Alias
|
||||
} else {
|
||||
col.FieldName = f.Name
|
||||
}
|
||||
col := Column{Name: f.Name, FieldName: fname}
|
||||
s.Cols = append(s.Cols, col)
|
||||
}
|
||||
|
||||
|
@ -28,17 +28,18 @@ func (sg *SuperGraph) prepare(q *query, role string) {
|
||||
var err error
|
||||
|
||||
qb := []byte(q.ai.Query)
|
||||
vars := []byte(q.ai.Vars)
|
||||
|
||||
switch q.qt {
|
||||
case qcode.QTQuery:
|
||||
if sg.abacEnabled {
|
||||
stmts, err = sg.buildMultiStmt(qb, q.ai.Vars)
|
||||
stmts, err = sg.buildMultiStmt(qb, vars)
|
||||
} else {
|
||||
stmts, err = sg.buildRoleStmt(qb, q.ai.Vars, role)
|
||||
stmts, err = sg.buildRoleStmt(qb, vars, role)
|
||||
}
|
||||
|
||||
case qcode.QTMutation:
|
||||
stmts, err = sg.buildRoleStmt(qb, q.ai.Vars, role)
|
||||
stmts, err = sg.buildRoleStmt(qb, vars, role)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
@ -55,6 +55,30 @@ query {
|
||||
}
|
||||
```
|
||||
|
||||
### Fragments
|
||||
|
||||
Fragments make it easy to build large complex queries with small composible and re-usable fragment blocks.
|
||||
|
||||
```graphql
|
||||
query {
|
||||
users {
|
||||
...userFields2
|
||||
...userFields1
|
||||
picture_url
|
||||
}
|
||||
}
|
||||
|
||||
fragment userFields1 on user {
|
||||
id
|
||||
email
|
||||
}
|
||||
|
||||
fragment userFields2 on user {
|
||||
first_name
|
||||
last_name
|
||||
}
|
||||
```
|
||||
|
||||
### Sorting
|
||||
|
||||
To sort or ordering results just use the `order_by` argument. This can be combined with `where`, `search`, etc to build complex queries to fit you needs.
|
||||
|
@ -4,6 +4,8 @@ title: Introduction
|
||||
sidebar_label: Introduction
|
||||
---
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl'; // Add to the top of the file below the front matter.
|
||||
|
||||
Super Graph is a service that instantly and without code gives you a high performance and secure GraphQL API. Your GraphQL queries are auto translated into a single fast SQL query. No more spending weeks or months writing backend API code. Just make the query you need and Super Graph will do the rest.
|
||||
|
||||
Super Graph has a rich feature set like integrating with your existing Ruby on Rails apps, joining your DB with data from remote APIs, Role and Attribute based access control, Support for JWT tokens, DB migrations, seeding and a lot more.
|
||||
@ -134,3 +136,9 @@ mutation {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Built-in GraphQL Editor
|
||||
|
||||
Quickly craft and test your queries with a full-featured GraphQL editor. Auto-complete and schema documentation is automatically available.
|
||||
|
||||
<img alt="Zipkin Traces" src={useBaseUrl("img/webui.jpg")} />
|
||||
|
@ -95,7 +95,7 @@ auth:
|
||||
type: jwt
|
||||
|
||||
jwt:
|
||||
# the two providers are 'auth0' and 'none'
|
||||
# valid providers are auth0, firebase and none
|
||||
provider: auth0
|
||||
secret: abc335bfcfdb04e50db5bb0a4d67ab9
|
||||
public_key_file: /secrets/public_key.pem
|
||||
@ -108,6 +108,19 @@ We can get the JWT token either from the `authorization` header where we expect
|
||||
|
||||
For validation a `secret` or a public key (ecdsa or rsa) is required. When using public keys they have to be in a PEM format file.
|
||||
|
||||
### Firebase Auth
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
type: jwt
|
||||
|
||||
jwt:
|
||||
provider: firebase
|
||||
audience: <firebase-project-id>
|
||||
```
|
||||
|
||||
Firebase auth also uses JWT the keys are auto-fetched from Google and used according to their documentation mechanism. The `audience` config value needs to be set to your project id and everything else is taken care for you.
|
||||
|
||||
### HTTP Headers
|
||||
|
||||
```yaml
|
||||
|
13
docs/website/docs/webui.md
Normal file
13
docs/website/docs/webui.md
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
id: webui
|
||||
title: Web UI / GraphQL Editor
|
||||
sidebar_label: Web UI
|
||||
---
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl'; // Add to the top of the file below the front matter.
|
||||
|
||||
<img alt="Zipkin Traces" src={useBaseUrl("img/webui.jpg")} />
|
||||
|
||||
Super Graph comes with a build-in GraphQL editor that only runs in development. Use it to craft your queries and copy-paste them into you're app once you're ready. The editor supports auto-completation and schema documentation. This makes it super easy to craft and test your query all in one go without knowing anything about the underlying database structure.
|
||||
|
||||
You can even set query variables or http headers as required. To simulate an authenticated user set the http header `"X-USER-ID": 5` to the user id of the user you want to test with.
|
@ -3,6 +3,7 @@ module.exports = {
|
||||
Docusaurus: [
|
||||
"home",
|
||||
"intro",
|
||||
"webui",
|
||||
"start",
|
||||
"why",
|
||||
"graphql",
|
||||
|
BIN
docs/website/static/img/webui.jpg
Normal file
BIN
docs/website/static/img/webui.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 117 KiB |
@ -66,7 +66,7 @@ func newViper(configPath, configFile string) *viper.Viper {
|
||||
vi.SetDefault("host_port", "0.0.0.0:8080")
|
||||
vi.SetDefault("web_ui", false)
|
||||
vi.SetDefault("enable_tracing", false)
|
||||
vi.SetDefault("auth_fail_block", "always")
|
||||
vi.SetDefault("auth_fail_block", false)
|
||||
vi.SetDefault("seed_file", "seed.js")
|
||||
|
||||
vi.SetDefault("default_block", true)
|
||||
|
Reference in New Issue
Block a user