Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
448e6bb72a | |||
f7d3760af7 | |||
2acb05741e | |||
8104ee9df2 |
@ -7,7 +7,7 @@
|
|||||||

|

|
||||||
[](https://discord.gg/6pSWCTZ)
|
[](https://discord.gg/6pSWCTZ)
|
||||||
|
|
||||||
Super Graph gives you a high performance GraphQL API without you having to write any code. GraphQL is automatically compiled efficient an SQL query. Use it either as a library or a standalone service.
|
Super Graph gives you a high performance GraphQL API without you having to write any code. GraphQL is automagically compiled into an efficient SQL query. Use it either as a library or a standalone service.
|
||||||
|
|
||||||
## Using it as a service
|
## Using it as a service
|
||||||
|
|
||||||
|
@ -91,7 +91,6 @@ auth:
|
|||||||
# password: ""
|
# password: ""
|
||||||
# max_idle: 80
|
# max_idle: 80
|
||||||
# max_active: 12000
|
# max_active: 12000
|
||||||
|
|
||||||
# In most cases you don't need these
|
# In most cases you don't need these
|
||||||
# salt: "encrypted cookie"
|
# salt: "encrypted cookie"
|
||||||
# sign_salt: "signed encrypted cookie"
|
# sign_salt: "signed encrypted cookie"
|
||||||
@ -165,7 +164,6 @@ tables:
|
|||||||
- name: email
|
- name: email
|
||||||
related_to: products.name
|
related_to: products.name
|
||||||
|
|
||||||
|
|
||||||
roles_query: "SELECT * FROM users WHERE id = $user_id"
|
roles_query: "SELECT * FROM users WHERE id = $user_id"
|
||||||
|
|
||||||
roles:
|
roles:
|
||||||
@ -174,7 +172,7 @@ roles:
|
|||||||
- name: products
|
- name: products
|
||||||
query:
|
query:
|
||||||
limit: 10
|
limit: 10
|
||||||
columns: ["id", "name", "description" ]
|
columns: ["id", "name", "description"]
|
||||||
aggregation: false
|
aggregation: false
|
||||||
|
|
||||||
insert:
|
insert:
|
||||||
|
@ -107,6 +107,7 @@ type Role struct {
|
|||||||
// RoleTable struct contains role specific access control values for a database table
|
// RoleTable struct contains role specific access control values for a database table
|
||||||
type RoleTable struct {
|
type RoleTable struct {
|
||||||
Name string
|
Name string
|
||||||
|
ReadOnly *bool `mapstructure:"read_only"`
|
||||||
|
|
||||||
Query Query
|
Query Query
|
||||||
Insert Insert
|
Insert Insert
|
||||||
@ -120,7 +121,7 @@ type Query struct {
|
|||||||
Filters []string
|
Filters []string
|
||||||
Columns []string
|
Columns []string
|
||||||
DisableFunctions bool `mapstructure:"disable_functions"`
|
DisableFunctions bool `mapstructure:"disable_functions"`
|
||||||
Block bool
|
Block *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert struct contains access control values for insert operations
|
// Insert struct contains access control values for insert operations
|
||||||
@ -128,7 +129,7 @@ type Insert struct {
|
|||||||
Filters []string
|
Filters []string
|
||||||
Columns []string
|
Columns []string
|
||||||
Presets map[string]string
|
Presets map[string]string
|
||||||
Block bool
|
Block *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert struct contains access control values for update operations
|
// Insert struct contains access control values for update operations
|
||||||
@ -136,14 +137,14 @@ type Update struct {
|
|||||||
Filters []string
|
Filters []string
|
||||||
Columns []string
|
Columns []string
|
||||||
Presets map[string]string
|
Presets map[string]string
|
||||||
Block bool
|
Block *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete struct contains access control values for delete operations
|
// Delete struct contains access control values for delete operations
|
||||||
type Delete struct {
|
type Delete struct {
|
||||||
Filters []string
|
Filters []string
|
||||||
Columns []string
|
Columns []string
|
||||||
Block bool
|
Block *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadInConfig function reads in the config file for the environment specified in the GO_ENV
|
// ReadInConfig function reads in the config file for the environment specified in the GO_ENV
|
||||||
|
55
core/init.go
55
core/init.go
@ -216,49 +216,70 @@ func addRoles(c *Config, qc *qcode.Compiler) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addRole(qc *qcode.Compiler, r Role, t RoleTable) error {
|
func addRole(qc *qcode.Compiler, r Role, t RoleTable) error {
|
||||||
blockFilter := []string{"false"}
|
blocked := struct {
|
||||||
|
readOnly bool
|
||||||
|
query bool
|
||||||
|
insert bool
|
||||||
|
update bool
|
||||||
|
delete bool
|
||||||
|
}{true, true, true, true, true}
|
||||||
|
|
||||||
|
if r.Name == "anon" {
|
||||||
|
blocked.query = false
|
||||||
|
} else {
|
||||||
|
blocked.readOnly = false
|
||||||
|
blocked.query = false
|
||||||
|
blocked.insert = false
|
||||||
|
blocked.update = false
|
||||||
|
blocked.delete = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.ReadOnly != nil {
|
||||||
|
blocked.readOnly = *t.ReadOnly
|
||||||
|
}
|
||||||
|
if t.Query.Block != nil {
|
||||||
|
blocked.query = *t.Query.Block
|
||||||
|
}
|
||||||
|
if t.Insert.Block != nil {
|
||||||
|
blocked.insert = *t.Insert.Block
|
||||||
|
}
|
||||||
|
if t.Update.Block != nil {
|
||||||
|
blocked.update = *t.Update.Block
|
||||||
|
}
|
||||||
|
if t.Delete.Block != nil {
|
||||||
|
blocked.delete = *t.Delete.Block
|
||||||
|
}
|
||||||
|
|
||||||
query := qcode.QueryConfig{
|
query := qcode.QueryConfig{
|
||||||
Limit: t.Query.Limit,
|
Limit: t.Query.Limit,
|
||||||
Filters: t.Query.Filters,
|
Filters: t.Query.Filters,
|
||||||
Columns: t.Query.Columns,
|
Columns: t.Query.Columns,
|
||||||
DisableFunctions: t.Query.DisableFunctions,
|
DisableFunctions: t.Query.DisableFunctions,
|
||||||
}
|
Block: blocked.query,
|
||||||
|
|
||||||
if t.Query.Block {
|
|
||||||
query.Filters = blockFilter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
insert := qcode.InsertConfig{
|
insert := qcode.InsertConfig{
|
||||||
Filters: t.Insert.Filters,
|
Filters: t.Insert.Filters,
|
||||||
Columns: t.Insert.Columns,
|
Columns: t.Insert.Columns,
|
||||||
Presets: t.Insert.Presets,
|
Presets: t.Insert.Presets,
|
||||||
}
|
Block: blocked.insert,
|
||||||
|
|
||||||
if t.Insert.Block {
|
|
||||||
insert.Filters = blockFilter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update := qcode.UpdateConfig{
|
update := qcode.UpdateConfig{
|
||||||
Filters: t.Update.Filters,
|
Filters: t.Update.Filters,
|
||||||
Columns: t.Update.Columns,
|
Columns: t.Update.Columns,
|
||||||
Presets: t.Update.Presets,
|
Presets: t.Update.Presets,
|
||||||
}
|
Block: blocked.update,
|
||||||
|
|
||||||
if t.Update.Block {
|
|
||||||
update.Filters = blockFilter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete := qcode.DeleteConfig{
|
delete := qcode.DeleteConfig{
|
||||||
Filters: t.Delete.Filters,
|
Filters: t.Delete.Filters,
|
||||||
Columns: t.Delete.Columns,
|
Columns: t.Delete.Columns,
|
||||||
}
|
Block: blocked.delete,
|
||||||
|
|
||||||
if t.Delete.Block {
|
|
||||||
delete.Filters = blockFilter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return qc.AddRole(r.Name, t.Name, qcode.TRConfig{
|
return qc.AddRole(r.Name, t.Name, qcode.TRConfig{
|
||||||
|
ReadOnly: blocked.readOnly,
|
||||||
Query: query,
|
Query: query,
|
||||||
Insert: insert,
|
Insert: insert,
|
||||||
Update: update,
|
Update: update,
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/chirino/graphql/schema"
|
||||||
"github.com/dosco/super-graph/jsn"
|
"github.com/dosco/super-graph/jsn"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -140,6 +141,7 @@ func (al *List) Set(vars []byte, query, comment string) error {
|
|||||||
|
|
||||||
func (al *List) Load() ([]Item, error) {
|
func (al *List) Load() ([]Item, error) {
|
||||||
var list []Item
|
var list []Item
|
||||||
|
varString := "variables"
|
||||||
|
|
||||||
b, err := ioutil.ReadFile(al.filepath)
|
b, err := ioutil.ReadFile(al.filepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -180,9 +182,9 @@ func (al *List) Load() ([]Item, error) {
|
|||||||
s = e
|
s = e
|
||||||
}
|
}
|
||||||
ty = AL_QUERY
|
ty = AL_QUERY
|
||||||
} else if matchPrefix(b, e, "variables") {
|
} else if matchPrefix(b, e, varString) {
|
||||||
if c == 0 {
|
if c == 0 {
|
||||||
s = e + len("variables") + 1
|
s = e + len(varString) + 1
|
||||||
}
|
}
|
||||||
ty = AL_VARS
|
ty = AL_VARS
|
||||||
} else if b[e] == '{' {
|
} else if b[e] == '{' {
|
||||||
@ -234,7 +236,21 @@ func (al *List) Load() ([]Item, error) {
|
|||||||
func (al *List) save(item Item) error {
|
func (al *List) save(item Item) error {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
item.Name = QueryName(item.Query)
|
qd := &schema.QueryDocument{}
|
||||||
|
|
||||||
|
if err := qd.Parse(item.Query); err != nil {
|
||||||
|
fmt.Println("##", item.Query)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
qd.WriteTo(&buf)
|
||||||
|
query := buf.String()
|
||||||
|
buf.Reset()
|
||||||
|
|
||||||
|
// fmt.Println(">", query)
|
||||||
|
|
||||||
|
item.Name = QueryName(query)
|
||||||
item.key = strings.ToLower(item.Name)
|
item.key = strings.ToLower(item.Name)
|
||||||
|
|
||||||
if len(item.Name) == 0 {
|
if len(item.Name) == 0 {
|
||||||
|
@ -16,26 +16,31 @@ type QueryConfig struct {
|
|||||||
Filters []string
|
Filters []string
|
||||||
Columns []string
|
Columns []string
|
||||||
DisableFunctions bool
|
DisableFunctions bool
|
||||||
|
Block bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type InsertConfig struct {
|
type InsertConfig struct {
|
||||||
Filters []string
|
Filters []string
|
||||||
Columns []string
|
Columns []string
|
||||||
Presets map[string]string
|
Presets map[string]string
|
||||||
|
Block bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateConfig struct {
|
type UpdateConfig struct {
|
||||||
Filters []string
|
Filters []string
|
||||||
Columns []string
|
Columns []string
|
||||||
Presets map[string]string
|
Presets map[string]string
|
||||||
|
Block bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeleteConfig struct {
|
type DeleteConfig struct {
|
||||||
Filters []string
|
Filters []string
|
||||||
Columns []string
|
Columns []string
|
||||||
|
Block bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type TRConfig struct {
|
type TRConfig struct {
|
||||||
|
ReadOnly bool
|
||||||
Query QueryConfig
|
Query QueryConfig
|
||||||
Insert InsertConfig
|
Insert InsertConfig
|
||||||
Update UpdateConfig
|
Update UpdateConfig
|
||||||
@ -43,14 +48,14 @@ type TRConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type trval struct {
|
type trval struct {
|
||||||
|
readOnly bool
|
||||||
query struct {
|
query struct {
|
||||||
limit string
|
limit string
|
||||||
fil *Exp
|
fil *Exp
|
||||||
filNU bool
|
filNU bool
|
||||||
cols map[string]struct{}
|
cols map[string]struct{}
|
||||||
disable struct {
|
disable struct{ funcs bool }
|
||||||
funcs bool
|
block bool
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
insert struct {
|
insert struct {
|
||||||
@ -59,6 +64,7 @@ type trval struct {
|
|||||||
cols map[string]struct{}
|
cols map[string]struct{}
|
||||||
psmap map[string]string
|
psmap map[string]string
|
||||||
pslist []string
|
pslist []string
|
||||||
|
block bool
|
||||||
}
|
}
|
||||||
|
|
||||||
update struct {
|
update struct {
|
||||||
@ -67,12 +73,14 @@ type trval struct {
|
|||||||
cols map[string]struct{}
|
cols map[string]struct{}
|
||||||
psmap map[string]string
|
psmap map[string]string
|
||||||
pslist []string
|
pslist []string
|
||||||
|
block bool
|
||||||
}
|
}
|
||||||
|
|
||||||
delete struct {
|
delete struct {
|
||||||
fil *Exp
|
fil *Exp
|
||||||
filNU bool
|
filNU bool
|
||||||
cols map[string]struct{}
|
cols map[string]struct{}
|
||||||
|
block bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,7 +207,7 @@ func NewFilter() *Exp {
|
|||||||
|
|
||||||
func (com *Compiler) AddRole(role, table string, trc TRConfig) error {
|
func (com *Compiler) AddRole(role, table string, trc TRConfig) error {
|
||||||
var err error
|
var err error
|
||||||
trv := &trval{}
|
trv := &trval{readOnly: trc.ReadOnly}
|
||||||
|
|
||||||
// query config
|
// query config
|
||||||
trv.query.fil, trv.query.filNU, err = compileFilter(trc.Query.Filters)
|
trv.query.fil, trv.query.filNU, err = compileFilter(trc.Query.Filters)
|
||||||
@ -219,6 +219,7 @@ func (com *Compiler) AddRole(role, table string, trc TRConfig) error {
|
|||||||
}
|
}
|
||||||
trv.query.cols = listToMap(trc.Query.Columns)
|
trv.query.cols = listToMap(trc.Query.Columns)
|
||||||
trv.query.disable.funcs = trc.Query.DisableFunctions
|
trv.query.disable.funcs = trc.Query.DisableFunctions
|
||||||
|
trv.query.block = trc.Query.Block
|
||||||
|
|
||||||
// insert config
|
// insert config
|
||||||
trv.insert.fil, trv.insert.filNU, err = compileFilter(trc.Insert.Filters)
|
trv.insert.fil, trv.insert.filNU, err = compileFilter(trc.Insert.Filters)
|
||||||
@ -228,6 +229,7 @@ func (com *Compiler) AddRole(role, table string, trc TRConfig) error {
|
|||||||
trv.insert.cols = listToMap(trc.Insert.Columns)
|
trv.insert.cols = listToMap(trc.Insert.Columns)
|
||||||
trv.insert.psmap = parsePresets(trc.Insert.Presets)
|
trv.insert.psmap = parsePresets(trc.Insert.Presets)
|
||||||
trv.insert.pslist = mapToList(trv.insert.psmap)
|
trv.insert.pslist = mapToList(trv.insert.psmap)
|
||||||
|
trv.insert.block = trc.Insert.Block
|
||||||
|
|
||||||
// update config
|
// update config
|
||||||
trv.update.fil, trv.update.filNU, err = compileFilter(trc.Update.Filters)
|
trv.update.fil, trv.update.filNU, err = compileFilter(trc.Update.Filters)
|
||||||
@ -237,6 +239,7 @@ func (com *Compiler) AddRole(role, table string, trc TRConfig) error {
|
|||||||
trv.update.cols = listToMap(trc.Update.Columns)
|
trv.update.cols = listToMap(trc.Update.Columns)
|
||||||
trv.update.psmap = parsePresets(trc.Update.Presets)
|
trv.update.psmap = parsePresets(trc.Update.Presets)
|
||||||
trv.update.pslist = mapToList(trv.update.psmap)
|
trv.update.pslist = mapToList(trv.update.psmap)
|
||||||
|
trv.update.block = trc.Update.Block
|
||||||
|
|
||||||
// delete config
|
// delete config
|
||||||
trv.delete.fil, trv.delete.filNU, err = compileFilter(trc.Delete.Filters)
|
trv.delete.fil, trv.delete.filNU, err = compileFilter(trc.Delete.Filters)
|
||||||
@ -244,6 +247,7 @@ func (com *Compiler) AddRole(role, table string, trc TRConfig) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
trv.delete.cols = listToMap(trc.Delete.Columns)
|
trv.delete.cols = listToMap(trc.Delete.Columns)
|
||||||
|
trv.delete.block = trc.Delete.Block
|
||||||
|
|
||||||
singular := flect.Singularize(table)
|
singular := flect.Singularize(table)
|
||||||
plural := flect.Pluralize(table)
|
plural := flect.Pluralize(table)
|
||||||
@ -330,6 +334,28 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
|
|||||||
|
|
||||||
trv := com.getRole(role, field.Name)
|
trv := com.getRole(role, field.Name)
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case QTQuery:
|
||||||
|
if trv.query.block {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
case QTInsert:
|
||||||
|
if trv.insert.block || trv.readOnly {
|
||||||
|
return fmt.Errorf("insert blocked: %s", field.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
case QTUpdate:
|
||||||
|
if trv.update.block || trv.readOnly {
|
||||||
|
return fmt.Errorf("update blocked: %s", field.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
case QTDelete:
|
||||||
|
if trv.delete.block || trv.readOnly {
|
||||||
|
return fmt.Errorf("delete blocked: %s", field.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
selects = append(selects, Select{
|
selects = append(selects, Select{
|
||||||
ID: id,
|
ID: id,
|
||||||
ParentID: parentID,
|
ParentID: parentID,
|
||||||
|
@ -36,7 +36,7 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; // Add to the top of the file b
|
|||||||
|
|
||||||
### Fetching data with GraphQL
|
### Fetching data with GraphQL
|
||||||
|
|
||||||
Just image the code or SQL you'll need to fetch this data, the user, all his posts, all the votes on the posts, the authors information and the related tags. Oh ya you also need efficient cursor based pagination. And Remember you also need to maintain this code forever.
|
Just imagine the code or SQL you'll need to fetch this data, the user, all his posts, all the votes on the posts, the authors information and the related tags. Oh yeah and you also need efficient cursor based pagination. And Remember you also need to maintain this code forever.
|
||||||
|
|
||||||
Instead just describe the data you need in GraphQL and give that to Super Graph it'll automatically learn your database and generate the most efficient SQL query fetching your data in the JSON structure you expected.
|
Instead just describe the data you need in GraphQL and give that to Super Graph it'll automatically learn your database and generate the most efficient SQL query fetching your data in the JSON structure you expected.
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ query {
|
|||||||
|
|
||||||
### Instant results
|
### Instant results
|
||||||
|
|
||||||
Here's the data Super Graph fetched using the GraphQL above it's even in the JSON structure you
|
Here's the data Super Graph fetched using the GraphQL above, it's even in the JSON structure you
|
||||||
wanted it in. All this without you writing any code or SQL.
|
wanted it in. All this without you writing any code or SQL.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
Reference in New Issue
Block a user