Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
06ed823a51 | |||
3438c3efb9 | |||
605ec63466 |
@ -5,11 +5,11 @@ web_ui: true
|
||||
# debug, info, warn, error, fatal, panic
|
||||
log_level: "debug"
|
||||
|
||||
# Disable this in development to get a list of
|
||||
# queries used. When enabled super graph
|
||||
# will only allow queries from this list
|
||||
# List saved to ./config/allow.list
|
||||
use_allow_list: false
|
||||
# When production mode is 'true' only queries
|
||||
# from the allow list are permitted.
|
||||
# When it's 'false' all queries are saved to the
|
||||
# the allow list in ./config/allow.list
|
||||
production: true
|
||||
|
||||
# Throw a 401 on auth failure for queries that need auth
|
||||
auth_fail_block: false
|
||||
|
@ -9,11 +9,11 @@ web_ui: false
|
||||
# debug, info, warn, error, fatal, panic, disable
|
||||
log_level: "info"
|
||||
|
||||
# Disable this in development to get a list of
|
||||
# queries used. When enabled super graph
|
||||
# will only allow queries from this list
|
||||
# List saved to ./config/allow.list
|
||||
use_allow_list: true
|
||||
# When production mode is 'true' only queries
|
||||
# from the allow list are permitted.
|
||||
# When it's 'false' all queries are saved to the
|
||||
# the allow list in ./config/allow.list
|
||||
production: true
|
||||
|
||||
# Throw a 401 on auth failure for queries that need auth
|
||||
auth_fail_block: true
|
||||
|
@ -24,6 +24,12 @@
|
||||
:item="actionLink"
|
||||
/>
|
||||
|
||||
<a
|
||||
class="px-4 py-3 my-8 border-2 border-gray-500 text-gray-600 font-bold rounded"
|
||||
href="https://github.com/dosco/super-graph"
|
||||
target="_blank"
|
||||
>Github</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,6 +1,11 @@
|
||||
let ogprefix = 'og: http://ogp.me/ns#'
|
||||
let title = 'Super Graph'
|
||||
let description = 'An instant GraphQL API for your app. No code needed.'
|
||||
let color = '#f42525'
|
||||
|
||||
module.exports = {
|
||||
title: 'Super Graph',
|
||||
description: 'Get an instant GraphQL API for your Rails apps.',
|
||||
title: title,
|
||||
description: description,
|
||||
|
||||
themeConfig: {
|
||||
logo: '/hologram.svg',
|
||||
@ -15,6 +20,22 @@ module.exports = {
|
||||
serviceWorker: {
|
||||
updatePopup: true
|
||||
},
|
||||
|
||||
head: [
|
||||
//['link', { rel: 'icon', href: `/assets/favicon.ico` }],
|
||||
['meta', { prefix: ogprefix, property: 'og:title', content: title }],
|
||||
['meta', { prefix: ogprefix, property: 'twitter:title', content: title }],
|
||||
['meta', { prefix: ogprefix, property: 'og:type', content: 'website' }],
|
||||
['meta', { prefix: ogprefix, property: 'og:url', content: 'https://supergraph.dev }],
|
||||
['meta', { prefix: ogprefix, property: 'og:description', content: description }],
|
||||
//['meta', { prefix: ogprefix, property: 'og:image', content: 'https://wireupyourfrontend.com/assets/logo.png' }],
|
||||
// ['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
|
||||
// ['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black' }],
|
||||
// ['link', { rel: 'apple-touch-icon', href: `/assets/apple-touch-icon.png` }],
|
||||
// ['link', { rel: 'mask-icon', href: '/assets/safari-pinned-tab.svg', color: color }],
|
||||
// ['meta', { name: 'msapplication-TileImage', content: '/assets/mstile-150x150.png' }],
|
||||
// ['meta', { name: 'msapplication-TileColor', content: color }],
|
||||
],
|
||||
},
|
||||
|
||||
postcss: {
|
||||
|
@ -1149,11 +1149,11 @@ web_ui: true
|
||||
# debug, info, warn, error, fatal, panic
|
||||
log_level: "debug"
|
||||
|
||||
# Disable this in development to get a list of
|
||||
# queries used. When enabled super graph
|
||||
# will only allow queries from this list
|
||||
# List saved to ./config/allow.list
|
||||
use_allow_list: false
|
||||
# When production mode is 'true' only queries
|
||||
# from the allow list are permitted.
|
||||
# When it's 'false' all queries are saved to the
|
||||
# the allow list in ./config/allow.list
|
||||
production: false
|
||||
|
||||
# Throw a 401 on auth failure for queries that need auth
|
||||
auth_fail_block: false
|
||||
|
@ -16,7 +16,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var migrationPattern = regexp.MustCompile(`\A(\d+)_.+\.sql\z`)
|
||||
var migrationPattern = regexp.MustCompile(`\A(\d+)_[^\.]+\.sql\z`)
|
||||
|
||||
var ErrNoFwMigration = errors.Errorf("no sql in forward migration step")
|
||||
|
||||
@ -127,7 +127,7 @@ func FindMigrationsEx(path string, fs MigratorFS) ([]string, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mcount := len(paths) + 100
|
||||
mcount := len(paths)
|
||||
|
||||
if n < int64(mcount) {
|
||||
return nil, fmt.Errorf("Duplicate migration %d", n)
|
||||
|
@ -137,16 +137,23 @@ func (c *compilerContext) renderInsertUpdateColumns(qc *qcode.QCode, w io.Writer
|
||||
}
|
||||
|
||||
for i := range root.PresetList {
|
||||
cn := root.PresetList[i]
|
||||
col, ok := ti.Columns[cn]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if i != 0 {
|
||||
io.WriteString(c.w, `, `)
|
||||
}
|
||||
if values {
|
||||
io.WriteString(c.w, `'`)
|
||||
io.WriteString(c.w, root.PresetMap[root.PresetList[i]])
|
||||
io.WriteString(c.w, `'`)
|
||||
io.WriteString(c.w, root.PresetMap[cn])
|
||||
io.WriteString(c.w, `' :: `)
|
||||
io.WriteString(c.w, col.Type)
|
||||
|
||||
} else {
|
||||
io.WriteString(c.w, `"`)
|
||||
io.WriteString(c.w, root.PresetList[i])
|
||||
io.WriteString(c.w, cn)
|
||||
io.WriteString(c.w, `"`)
|
||||
}
|
||||
}
|
||||
|
@ -250,7 +250,7 @@ func simpleInsertWithPresets(t *testing.T) {
|
||||
}
|
||||
}`
|
||||
|
||||
sql := `WITH "products" AS (WITH "input" AS (SELECT {{data}}::json AS j) INSERT INTO "products" ("name", "price", "created_at", "updated_at", "user_id") SELECT "name", "price", 'now', 'now', '$user_id' FROM input i, json_populate_record(NULL::products, i.j) t RETURNING *) SELECT json_object_agg('product', sel_json_0) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."id" FROM "products") AS "products_0") AS "done_1337"`
|
||||
sql := `WITH "products" AS (WITH "input" AS (SELECT {{data}}::json AS j) INSERT INTO "products" ("name", "price", "created_at", "updated_at", "user_id") SELECT "name", "price", 'now' :: timestamp without time zone, 'now' :: timestamp without time zone, '$user_id' :: bigint FROM input i, json_populate_record(NULL::products, i.j) t RETURNING *) SELECT json_object_agg('product', sel_json_0) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."id" FROM "products") AS "products_0") AS "done_1337"`
|
||||
|
||||
vars := map[string]json.RawMessage{
|
||||
"data": json.RawMessage(`{"name": "Tomato", "price": 5.76}`),
|
||||
@ -273,7 +273,7 @@ func simpleUpdateWithPresets(t *testing.T) {
|
||||
}
|
||||
}`
|
||||
|
||||
sql := `WITH "products" AS (WITH "input" AS (SELECT {{data}}::json AS j) UPDATE "products" SET ("name", "price", "updated_at") = (SELECT "name", "price", 'now' FROM input i, json_populate_record(NULL::products, i.j) t) WHERE (("products"."user_id") = {{user_id}}) RETURNING *) SELECT json_object_agg('product', sel_json_0) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."id" FROM "products") AS "products_0") AS "done_1337"`
|
||||
sql := `WITH "products" AS (WITH "input" AS (SELECT {{data}}::json AS j) UPDATE "products" SET ("name", "price", "updated_at") = (SELECT "name", "price", 'now' :: timestamp without time zone FROM input i, json_populate_record(NULL::products, i.j) t) WHERE (("products"."user_id") = {{user_id}}) RETURNING *) SELECT json_object_agg('product', sel_json_0) FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id") AS "sel_0")) AS "sel_json_0" FROM (SELECT "products"."id" FROM "products") AS "products_0") AS "done_1337"`
|
||||
|
||||
vars := map[string]json.RawMessage{
|
||||
"data": json.RawMessage(`{"name": "Apple", "price": 1.25}`),
|
||||
|
@ -191,15 +191,15 @@ func (c *compilerContext) processChildren(sel *qcode.Select, ti *DBTableInfo) (u
|
||||
fallthrough
|
||||
case RelBelongTo:
|
||||
if _, ok := colmap[rel.Col2]; !ok {
|
||||
cols = append(cols, &qcode.Column{ti.Name, rel.Col2, rel.Col2})
|
||||
cols = append(cols, &qcode.Column{Table: ti.Name, Name: rel.Col2, FieldName: rel.Col2})
|
||||
}
|
||||
case RelOneToManyThrough:
|
||||
if _, ok := colmap[rel.Col1]; !ok {
|
||||
cols = append(cols, &qcode.Column{ti.Name, rel.Col1, rel.Col1})
|
||||
cols = append(cols, &qcode.Column{Table: ti.Name, Name: rel.Col1, FieldName: rel.Col1})
|
||||
}
|
||||
case RelRemote:
|
||||
if _, ok := colmap[rel.Col1]; !ok {
|
||||
cols = append(cols, &qcode.Column{ti.Name, rel.Col1, rel.Col2})
|
||||
cols = append(cols, &qcode.Column{Table: ti.Name, Name: rel.Col1, FieldName: rel.Col2})
|
||||
}
|
||||
skipped |= (1 << uint(id))
|
||||
|
||||
@ -340,9 +340,9 @@ func (c *compilerContext) renderLateralJoinClose(sel *qcode.Select) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *compilerContext) renderJoin(sel *qcode.Select) error {
|
||||
func (c *compilerContext) renderJoin(sel *qcode.Select, ti *DBTableInfo) error {
|
||||
parent := &c.s[sel.ParentID]
|
||||
return c.renderJoinByName(sel.Table, parent.Table, parent.ID)
|
||||
return c.renderJoinByName(ti.Name, parent.Table, parent.ID)
|
||||
}
|
||||
|
||||
func (c *compilerContext) renderJoinByName(table, parent string, id int32) error {
|
||||
@ -607,7 +607,7 @@ func (c *compilerContext) renderBaseSelect(sel *qcode.Select, ti *DBTableInfo,
|
||||
}
|
||||
|
||||
if !isRoot {
|
||||
if err := c.renderJoin(sel); err != nil {
|
||||
if err := c.renderJoin(sel, ti); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -691,7 +691,7 @@ func (c *compilerContext) renderOrderByColumns(sel *qcode.Select, ti *DBTableInf
|
||||
|
||||
func (c *compilerContext) renderRelationship(sel *qcode.Select, ti *DBTableInfo) error {
|
||||
parent := c.s[sel.ParentID]
|
||||
return c.renderRelationshipByName(sel.Table, parent.Table, parent.ID)
|
||||
return c.renderRelationshipByName(ti.Name, parent.Table, parent.ID)
|
||||
}
|
||||
|
||||
func (c *compilerContext) renderRelationshipByName(table, parent string, id int32) error {
|
||||
|
2
qcode/cleanup.sh
Executable file
2
qcode/cleanup.sh
Executable file
@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
cd corpus && rm -rf $(find . ! -name '00?.gql')
|
@ -577,7 +577,7 @@ func (com *Compiler) compileArgID(sel *Select, arg *Arg) error {
|
||||
case nodeVar:
|
||||
ex.Type = ValVar
|
||||
default:
|
||||
fmt.Errorf("expecting a string, int, float or variable")
|
||||
return fmt.Errorf("expecting a string, int, float or variable")
|
||||
}
|
||||
|
||||
sel.Where = ex
|
||||
|
@ -71,7 +71,7 @@ func initAllowList(cpath string) {
|
||||
}
|
||||
|
||||
if len(_allowList.filepath) == 0 {
|
||||
if conf.UseAllowList {
|
||||
if conf.Production {
|
||||
logger.Fatal().Msg("allow.list not found")
|
||||
}
|
||||
|
||||
|
@ -124,7 +124,7 @@ func cmdDBNew(cmd *cobra.Command, args []string) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
mname := fmt.Sprintf("%03d_%s.sql", len(m)+100, name)
|
||||
mname := fmt.Sprintf("%03d_%s.sql", (len(m) + 1), name)
|
||||
|
||||
// Write new migration
|
||||
mpath := filepath.Join(conf.MigrationsPath, mname)
|
||||
|
@ -21,7 +21,7 @@ func cmdDBSeed(cmd *cobra.Command, args []string) {
|
||||
logger.Fatal().Err(err).Msg("failed to read config")
|
||||
}
|
||||
|
||||
conf.UseAllowList = false
|
||||
conf.Production = false
|
||||
|
||||
db, err = initDBPool(conf)
|
||||
if err != nil {
|
||||
|
@ -23,6 +23,7 @@ type config struct {
|
||||
LogLevel string `mapstructure:"log_level"`
|
||||
EnableTracing bool `mapstructure:"enable_tracing"`
|
||||
UseAllowList bool `mapstructure:"use_allow_list"`
|
||||
Production bool
|
||||
WatchAndReload bool `mapstructure:"reload_on_config_change"`
|
||||
AuthFailBlock bool `mapstructure:"auth_fail_block"`
|
||||
SeedFile string `mapstructure:"seed_file"`
|
||||
@ -142,9 +143,10 @@ type configRoleTable struct {
|
||||
}
|
||||
|
||||
type configRole struct {
|
||||
Name string
|
||||
Match string
|
||||
Tables []configRoleTable
|
||||
Name string
|
||||
Match string
|
||||
Tables []configRoleTable
|
||||
tablesMap map[string]*configRoleTable
|
||||
}
|
||||
|
||||
func newConfig(name string) *viper.Viper {
|
||||
@ -195,6 +197,10 @@ func (c *config) Init(vi *viper.Viper) error {
|
||||
c.Tables = c.DB.Tables
|
||||
}
|
||||
|
||||
if c.UseAllowList {
|
||||
c.Production = true
|
||||
}
|
||||
|
||||
for k, v := range c.Inflections {
|
||||
flect.AddPlural(k, v)
|
||||
}
|
||||
@ -219,13 +225,19 @@ func (c *config) Init(vi *viper.Viper) error {
|
||||
rolesMap := make(map[string]struct{})
|
||||
|
||||
for i := range c.Roles {
|
||||
role := c.Roles[i]
|
||||
role := &c.Roles[i]
|
||||
|
||||
if _, ok := rolesMap[role.Name]; ok {
|
||||
logger.Fatal().Msgf("duplicate role '%s' found", role.Name)
|
||||
}
|
||||
role.Name = sanitize(role.Name)
|
||||
role.Match = sanitize(role.Match)
|
||||
role.tablesMap = make(map[string]*configRoleTable)
|
||||
|
||||
for n, table := range role.Tables {
|
||||
role.tablesMap[table.Name] = &role.Tables[n]
|
||||
}
|
||||
|
||||
rolesMap[role.Name] = struct{}{}
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ func (c *coreContext) execQuery() ([]byte, error) {
|
||||
|
||||
logger.Debug().Str("role", c.req.role).Msg(c.req.Query)
|
||||
|
||||
if conf.UseAllowList {
|
||||
if conf.Production {
|
||||
var ps *preparedItem
|
||||
|
||||
data, ps, err = c.resolvePreparedSQL()
|
||||
@ -256,7 +256,7 @@ func (c *coreContext) resolveSQL() ([]byte, uint32, error) {
|
||||
stime)
|
||||
}
|
||||
|
||||
if conf.UseAllowList == false {
|
||||
if conf.Production == false {
|
||||
_allowList.add(&c.req)
|
||||
}
|
||||
|
||||
@ -325,7 +325,7 @@ func (c *coreContext) resolveRemote(
|
||||
ob.WriteString("null")
|
||||
}
|
||||
|
||||
to[0] = jsn.Field{[]byte(s.FieldName), ob.Bytes()}
|
||||
to[0] = jsn.Field{Key: []byte(s.FieldName), Value: ob.Bytes()}
|
||||
return to, nil
|
||||
}
|
||||
|
||||
@ -402,7 +402,7 @@ func (c *coreContext) resolveRemotes(
|
||||
ob.WriteString("null")
|
||||
}
|
||||
|
||||
to[n] = jsn.Field{[]byte(s.FieldName), ob.Bytes()}
|
||||
to[n] = jsn.Field{Key: []byte(s.FieldName), Value: ob.Bytes()}
|
||||
}(i, id, s)
|
||||
}
|
||||
wg.Wait()
|
||||
|
@ -41,17 +41,22 @@ func (c *coreContext) buildStmt() ([]stmt, error) {
|
||||
mutation := (qc.Type != qcode.QTQuery)
|
||||
w := &bytes.Buffer{}
|
||||
|
||||
for i := range conf.Roles {
|
||||
for i := 1; i < len(conf.Roles); i++ {
|
||||
role := &conf.Roles[i]
|
||||
|
||||
// For mutations only render sql for a single role from the request
|
||||
if mutation && len(c.req.role) != 0 && role.Name != c.req.role {
|
||||
continue
|
||||
}
|
||||
|
||||
if i > 0 {
|
||||
qc, err = qcompile.Compile(gql, role.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
qc, err = qcompile.Compile(gql, role.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if conf.Production && role.Name == "anon" {
|
||||
if _, ok := role.tablesMap[qc.Selects[0].Table]; !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,7 +108,7 @@ func Do(log func(string, ...interface{}), additional ...dir) error {
|
||||
// Ensure that we use the correct events, as they are not uniform across
|
||||
// platforms. See https://github.com/fsnotify/fsnotify/issues/74
|
||||
|
||||
if conf.UseAllowList == false && strings.HasSuffix(event.Name, "/allow.list") {
|
||||
if conf.Production == false && strings.HasSuffix(event.Name, "/allow.list") {
|
||||
continue
|
||||
}
|
||||
|
||||
|
16
tmpl/dev.yml
16
tmpl/dev.yml
@ -1,15 +1,15 @@
|
||||
app_name: "{{app_name}} Development"
|
||||
app_name: "{% app_name %} Development"
|
||||
host_port: 0.0.0.0:8080
|
||||
web_ui: true
|
||||
|
||||
# debug, info, warn, error, fatal, panic
|
||||
log_level: "debug"
|
||||
|
||||
# Disable this in development to get a list of
|
||||
# queries used. When enabled super graph
|
||||
# will only allow queries from this list
|
||||
# List saved to ./config/allow.list
|
||||
use_allow_list: false
|
||||
# When production mode is 'true' only queries
|
||||
# from the allow list are permitted.
|
||||
# When it's 'false' all queries are saved to the
|
||||
# the allow list in ./config/allow.list
|
||||
production: false
|
||||
|
||||
# Throw a 401 on auth failure for queries that need auth
|
||||
auth_fail_block: false
|
||||
@ -48,7 +48,7 @@ migrations_path: ./config/migrations
|
||||
auth:
|
||||
# Can be 'rails' or 'jwt'
|
||||
type: rails
|
||||
cookie: _{{app_name_slug}}_session
|
||||
cookie: _{% app_name_slug %}_session
|
||||
|
||||
# Comment this out if you want to disable setting
|
||||
# the user_id via a header for testing.
|
||||
@ -84,7 +84,7 @@ database:
|
||||
type: postgres
|
||||
host: db
|
||||
port: 5432
|
||||
dbname: {{app_name_slug}}_development
|
||||
dbname: {% app_name_slug %}_development
|
||||
user: postgres
|
||||
password: ''
|
||||
|
||||
|
@ -2,18 +2,17 @@
|
||||
# so I only need to overwrite some values
|
||||
inherits: dev
|
||||
|
||||
app_name: "{{app_name}} Production"
|
||||
app_name: "{% app_name %} Production"
|
||||
host_port: 0.0.0.0:8080
|
||||
web_ui: false
|
||||
|
||||
# debug, info, warn, error, fatal, panic, disable
|
||||
log_level: "info"
|
||||
|
||||
# Disable this in development to get a list of
|
||||
# queries used. When enabled super graph
|
||||
# will only allow queries from this list
|
||||
# List saved to ./config/allow.list
|
||||
use_allow_list: true
|
||||
# When production mode is 'true' only queries
|
||||
# from the allow list are permitted.
|
||||
# When it's 'false' all queries are saved to the
|
||||
# the allow list in ./config/allow.list
|
||||
production: true
|
||||
|
||||
# Throw a 401 on auth failure for queries that need auth
|
||||
auth_fail_block: true
|
||||
@ -48,7 +47,7 @@ enable_tracing: true
|
||||
auth:
|
||||
# Can be 'rails' or 'jwt'
|
||||
type: rails
|
||||
cookie: _{{app_name_slug}}_session
|
||||
cookie: _{% app_name_slug %}_session
|
||||
|
||||
rails:
|
||||
# Rails version this is used for reading the
|
||||
@ -79,7 +78,7 @@ database:
|
||||
type: postgres
|
||||
host: db
|
||||
port: 5432
|
||||
dbname: {{app_name_slug}}_development
|
||||
dbname: {% app_name_slug %}_development
|
||||
user: postgres
|
||||
password: ''
|
||||
#pool_size: 10
|
||||
|
Reference in New Issue
Block a user