Compare commits

...

4 Commits

8 changed files with 124 additions and 54 deletions

View File

@ -7,7 +7,7 @@
![Docker build](https://img.shields.io/docker/cloud/build/dosco/super-graph.svg?style=flat-square) ![Docker build](https://img.shields.io/docker/cloud/build/dosco/super-graph.svg?style=flat-square)
[![Discord Chat](https://img.shields.io/discord/628796009539043348.svg)](https://discord.gg/6pSWCTZ) [![Discord Chat](https://img.shields.io/discord/628796009539043348.svg)](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

View File

@ -8,7 +8,7 @@ log_level: "debug"
# enable or disable http compression (uses gzip) # enable or disable http compression (uses gzip)
http_compress: true http_compress: true
# When production mode is 'true' only queries # When production mode is 'true' only queries
# from the allow list are permitted. # from the allow list are permitted.
# When it's 'false' all queries are saved to the # When it's 'false' all queries are saved to the
# the allow list in ./config/allow.list # the allow list in ./config/allow.list
@ -32,13 +32,13 @@ reload_on_config_change: true
# Path pointing to where the migrations can be found # Path pointing to where the migrations can be found
migrations_path: ./migrations migrations_path: ./migrations
# Secret key for general encryption operations like # Secret key for general encryption operations like
# encrypting the cursor data # encrypting the cursor data
secret_key: supercalifajalistics secret_key: supercalifajalistics
# CORS: A list of origins a cross-domain request can be executed from. # CORS: A list of origins a cross-domain request can be executed from.
# If the special * value is present in the list, all origins will be allowed. # If the special * value is present in the list, all origins will be allowed.
# An origin may contain a wildcard (*) to replace 0 or more # An origin may contain a wildcard (*) to replace 0 or more
# characters (i.e.: http://*.domain.com). # characters (i.e.: http://*.domain.com).
cors_allowed_origins: ["*"] cors_allowed_origins: ["*"]
@ -48,8 +48,8 @@ cors_debug: true
# Default API path prefix is /api you can change it if you like # Default API path prefix is /api you can change it if you like
# api_path: "/data" # api_path: "/data"
# Cache-Control header can help cache queries if your CDN supports cache-control # Cache-Control header can help cache queries if your CDN supports cache-control
# on POST requests (does not work with not mutations) # on POST requests (does not work with not mutations)
# cache_control: "public, max-age=300, s-maxage=600" # cache_control: "public, max-age=300, s-maxage=600"
# Postgres related environment Variables # Postgres related environment Variables
@ -74,7 +74,7 @@ auth:
cookie: _app_session cookie: _app_session
# Comment this out if you want to disable setting # Comment this out if you want to disable setting
# the user_id via a header for testing. # the user_id via a header for testing.
# Disable in production # Disable in production
creds_in_header: true creds_in_header: true
@ -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"
@ -144,7 +143,7 @@ tables:
url: http://rails_app:3000/stripe/$id url: http://rails_app:3000/stripe/$id
path: data path: data
# debug: true # debug: true
pass_headers: pass_headers:
- cookie - cookie
set_headers: set_headers:
- name: Host - name: Host
@ -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,12 +172,12 @@ 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:
block: false block: false
update: update:
block: false block: false

View File

@ -106,7 +106,8 @@ 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

View File

@ -216,53 +216,74 @@ 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{
Query: query, ReadOnly: blocked.readOnly,
Insert: insert, Query: query,
Update: update, Insert: insert,
Delete: delete, Update: update,
Delete: delete,
}) })
} }

View File

@ -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 {

View File

@ -16,41 +16,46 @@ 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 {
Query QueryConfig ReadOnly bool
Insert InsertConfig Query QueryConfig
Update UpdateConfig Insert InsertConfig
Delete DeleteConfig Update UpdateConfig
Delete DeleteConfig
} }
type trval struct { type trval struct {
query struct { readOnly bool
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
} }
} }

View File

@ -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,

View File

@ -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