Add initialization command to setup new apps

This commit is contained in:
Vikram Rangnekar 2019-09-27 02:19:24 -04:00
parent 5e86b445c5
commit ecec2968e9
30 changed files with 1021 additions and 70 deletions

3
.gitignore vendored
View File

@ -26,4 +26,5 @@
.vscode
main
.DS_Store
.swp
.swp
main

View File

@ -12,7 +12,9 @@ RUN apk update && \
apk add --no-cache upx=3.95-r2
RUN go get -u github.com/dosco/esc && \
go get -u github.com/pilu/fresh
go get -u github.com/shanzi/wu && \
go install github.com/shanzi/wu && \
go get github.com/GeertJohan/go.rice/rice
WORKDIR /app
COPY . /app
@ -22,10 +24,13 @@ COPY --from=react-build /web/build/ ./web/build/
ENV GO111MODULE=on
RUN go mod vendor
# RUN go generate ./... && \
# CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o super-graph && \
# upx --ultra-brute -qq super-graph && \
# upx -t super-graph
RUN go generate ./... && \
CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o super-graph && \
upx --ultra-brute -qq super-graph && \
upx -t super-graph
CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o super-graph
# stage: 3
FROM alpine:latest

View File

@ -80,7 +80,7 @@ database:
type: postgres
host: db
port: 5432
dbname: app_development
dbname: {{app_name_slug}}_development
user: postgres
password: ''
#pool_size: 10

View File

@ -11,16 +11,16 @@ services:
# ports:
# - "6379:6379"
rails_app:
build: rails-app/.
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
volumes:
- ./rails-app:/app
- /app/tmp
ports:
- "3000:3000"
depends_on:
- db
# rails_app:
# build: rails-app/.
# command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
# volumes:
# - ./rails-app:/app
# - /app/tmp
# ports:
# - "3000:3000"
# depends_on:
# - db
super_graph:
build:
@ -34,9 +34,9 @@ services:
volumes:
- .:/app
working_dir: /app
command: fresh -c fresh.conf
command: wu -pattern="*.go" go run main.go serv
depends_on:
- db
- rails_app
#- rails_app
# - redis

View File

@ -1,14 +0,0 @@
root: .
tmp_path: ./tmp
build_name: runner-build
build_log: runner-build-errors.log
valid_ext: .go, .tpl, .tmpl, .html, .yml, *.list
no_rebuild_ext: .tpl, .tmpl, .html
ignored: web, tmp, vendor, rails-app, docs, slides, bench, corpus
build_delay: 600
colors: 1
log_color_main: cyan
log_color_build: yellow
log_color_runner: green
log_color_watcher: magenta
log_color_app:

4
go.mod
View File

@ -1,6 +1,7 @@
module github.com/dosco/super-graph
require (
github.com/GeertJohan/go.rice v1.0.0
github.com/Masterminds/semver v1.4.2
github.com/OneOfOne/xxhash v1.2.5 // indirect
github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3
@ -35,6 +36,9 @@ require (
github.com/valyala/fasttemplate v1.0.1
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127
mellium.im/sasl v0.2.1 // indirect
)
go 1.13

9
go.sum
View File

@ -4,6 +4,9 @@ cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7h
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
github.com/GeertJohan/go.rice v1.0.0 h1:KkI6O9uMaQU3VEKaj01ulavtF7o1fWT7+pk/4voiMLQ=
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
@ -15,6 +18,7 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3 h1:+qz9Ga6l6lKw6fgvk5RMV5HQznSLvI8Zxajwdj4FhFg=
github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3/go.mod h1:FlkD11RtgMTYjVuBnb7cxoHmQGqvPpCsr2atC88nl/M=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
@ -51,6 +55,8 @@ github.com/cznic/ql v1.2.0/go.mod h1:FbpzhyZrqr0PVlK6ury+PoW3T0ODUV22OeWIxcaOrSE
github.com/cznic/sortutil v0.0.0-20150617083342-4c7342852e65/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ=
github.com/cznic/strutil v0.0.0-20171016134553-529a34b1c186/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc=
github.com/cznic/zappy v0.0.0-20160723133515-2533cb5b45cc/go.mod h1:Y1SNZ4dRUOKXshKUbwUapqNncRrho4mkjQebgEHZLj8=
github.com/daaku/go.zipexe v1.0.0 h1:VSOgZtH418pH9L16hC/JrgSNJbbAL26pj7lmD1+CGdY=
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -171,6 +177,7 @@ github.com/jackc/puddle v1.0.0 h1:rbjAshlgKscNa7j0jAM0uNQflis5o2XUogPMVAwtcsM=
github.com/jackc/puddle v1.0.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/tern v1.8.2 h1:+d9eK83fRS0dbf6nt+2tjILYF4FKG1O5xTFB8Lzc66U=
github.com/jackc/tern v1.8.2/go.mod h1:AMppp2oyCT6rYnJHLLMmPWwahfFvdIVi6mr9gH81Nxs=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a h1:eeaG9XMUvRBYXJi4pg1ZKM7nxc5AfXfojeLLW7O5J3k=
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
@ -209,6 +216,7 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA=
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
@ -332,6 +340,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=

152
hello/config/dev.yml Normal file
View File

@ -0,0 +1,152 @@
app_name: "Hello 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
# Throw a 401 on auth failure for queries that need auth
# valid values: always, per_query, never
auth_fail_block: never
# Latency tracing for database queries and remote joins
# the resulting latency information is returned with the
# response
enable_tracing: true
# Watch the config folder and reload Super Graph
# with the new configs when a change is detected
reload_on_config_change: true
# File that points to the database seeding script
# seed_file: seed.js
# Path pointing to where the migrations can be found
migrations_path: ./config/migrations
# Postgres related environment Variables
# SG_DATABASE_HOST
# SG_DATABASE_PORT
# SG_DATABASE_USER
# SG_DATABASE_PASSWORD
# Auth related environment Variables
# SG_AUTH_RAILS_COOKIE_SECRET_KEY_BASE
# SG_AUTH_RAILS_REDIS_URL
# SG_AUTH_RAILS_REDIS_PASSWORD
# SG_AUTH_JWT_PUBLIC_KEY_FILE
# inflections:
# person: people
# sheep: sheep
auth:
# Can be 'rails' or 'jwt'
type: rails
cookie: _app_session
# Comment this out if you want to disable setting
# the user_id via a header. Good for testing
header: X-User-ID
rails:
# Rails version this is used for reading the
# various cookies formats.
version: 5.2
# Found in 'Rails.application.config.secret_key_base'
secret_key_base: 0a248500a64c01184edb4d7ad3a805488f8097ac761b76aaa6c17c01dcb7af03a2f18ba61b2868134b9c7b79a122bc0dadff4367414a2d173297bfea92be5566
# Remote cookie store. (memcache or redis)
# url: redis://redis:6379
# password: ""
# max_idle: 80
# max_active: 12000
# In most cases you don't need these
# salt: "encrypted cookie"
# sign_salt: "signed encrypted cookie"
# auth_salt: "authenticated encrypted cookie"
# jwt:
# provider: auth0
# secret: abc335bfcfdb04e50db5bb0a4d67ab9
# public_key_file: /secrets/public_key.pem
# public_key_type: ecdsa #rsa
database:
type: postgres
host: db
port: 5432
dbname: hello_database
user: postgres
password: ''
#schema: "public"
#pool_size: 10
#max_retries: 0
#log_level: "debug"
# Define variables here that you want to use in filters
# sub-queries must be wrapped in ()
variables:
account_id: "(select account_id from users where id = $user_id)"
# Define defaults to for the field key and values below
defaults:
# filter: ["{ user_id: { eq: $user_id } }"]
# Field and table names that you wish to block
blocklist:
- ar_internal_metadata
- schema_migrations
- secret
- password
- encrypted
- token
tables:
- name: users
# This filter will overwrite defaults.filter
# filter: ["{ id: { eq: $user_id } }"]
# - name: products
# # Multiple filters are AND'd together
# filter: [
# "{ price: { gt: 0 } }",
# "{ price: { lt: 8 } }"
# ]
- name: customers
# No filter is used for this field not
# even defaults.filter
filter: none
remotes:
- name: payments
id: stripe_id
url: http://rails_app:3000/stripe/$id
path: data
# debug: true
pass_headers:
- cookie
set_headers:
- name: Host
value: 0.0.0.0
# - name: Authorization
# value: Bearer <stripe_api_key>
- # You can create new fields that have a
# real db table backing them
name: me
table: users
filter: ["{ id: { eq: $user_id } }"]
# - name: posts
# filter: ["{ account_id: { _eq: $account_id } }"]

View File

@ -0,0 +1,21 @@
-- Write your migrate up statements here
CREATE DATABASE hello_database
-- CREATE TABLE public.users (
-- id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
-- full_name text
-- email text UNIQUE NOT NULL CHECK (length(email) < 255),
-- encrypted_password text,
-- created_at timestamptz NOT NULL NOT NULL DEFAULT NOW(),
-- updated_at timestamptz NOT NULL NOT NULL DEFAULT NOW()
-- );
---- create above / drop below ----
-- Write your migrate down statements here. If this migration is irreversible
-- Then delete the separator line above.
-- DROP TABLE public.users
DROP DATABASE IF EXISTS hello_database

144
hello/config/prod.yml Normal file
View File

@ -0,0 +1,144 @@
app_name: "Hello 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
# Throw a 401 on auth failure for queries that need auth
# valid values: always, per_query, never
auth_fail_block: always
# Latency tracing for database queries and remote joins
# the resulting latency information is returned with the
# response
enable_tracing: true
# File that points to the database seeding script
# seed_file: seed.js
# Path pointing to where the migrations can be found
# migrations_path: migrations
# Postgres related environment Variables
# SG_DATABASE_HOST
# SG_DATABASE_PORT
# SG_DATABASE_USER
# SG_DATABASE_PASSWORD
# Auth related environment Variables
# SG_AUTH_RAILS_COOKIE_SECRET_KEY_BASE
# SG_AUTH_RAILS_REDIS_URL
# SG_AUTH_RAILS_REDIS_PASSWORD
# SG_AUTH_JWT_PUBLIC_KEY_FILE
# inflections:
# person: people
# sheep: sheep
auth:
# Can be 'rails' or 'jwt'
type: rails
cookie: _app_session
# Comment this out if you want to disable setting
# the user_id via a header. Good for testing
header: X-User-ID
rails:
# Rails version this is used for reading the
# various cookies formats.
version: 5.2
# Found in 'Rails.application.config.secret_key_base'
secret_key_base: 0a248500a64c01184edb4d7ad3a805488f8097ac761b76aaa6c17c01dcb7af03a2f18ba61b2868134b9c7b79a122bc0dadff4367414a2d173297bfea92be5566
# Remote cookie store. (memcache or redis)
# url: redis://127.0.0.1:6379
# password: test
# max_idle: 80,
# max_active: 12000,
# In most cases you don't need these
# salt: "encrypted cookie"
# sign_salt: "signed encrypted cookie"
# auth_salt: "authenticated encrypted cookie"
# jwt:
# provider: auth0
# secret: abc335bfcfdb04e50db5bb0a4d67ab9
# public_key_file: /secrets/public_key.pem
# public_key_type: ecdsa #rsa
database:
type: postgres
host: db
port: 5432
dbname: hello_database
user: postgres
password: ''
#pool_size: 10
#max_retries: 0
#log_level: "debug"
# Define variables here that you want to use in filters
# sub-queries must be wrapped in ()
variables:
account_id: "(select account_id from users where id = $user_id)"
# Define defaults to for the field key and values below
defaults:
filter: ["{ user_id: { eq: $user_id } }"]
# Field and table names that you wish to block
blocklist:
- ar_internal_metadata
- schema_migrations
- secret
- password
- encrypted
- token
tables:
- name: users
# This filter will overwrite defaults.filter
filter: ["{ id: { eq: $user_id } }"]
- name: products
# Multiple filters are AND'd together
filter: [
"{ price: { gt: 0 } }",
"{ price: { lt: 8 } }"
]
- name: customers
# No filter is used for this field not
# even defaults.filter
filter: none
# remotes:
# - name: payments
# id: stripe_id
# url: http://rails_app:3000/stripe/$id
# path: data
# # pass_headers:
# # - cookie
# # - host
# set_headers:
# - name: Authorization
# value: Bearer <stripe_api_key>
- # You can create new fields that have a
# real db table backing them
name: me
table: users
filter: ["{ id: { eq: $user_id } }"]
# - name: posts
# filter: ["{ account_id: { _eq: $account_id } }"]

17
hello/docker-compose.yml Normal file
View File

@ -0,0 +1,17 @@
version: '3'
services:
db:
image: postgres
ports:
- "5432:5432"
hello_api:
image: dosco/super-graph:latest
environment:
GO_ENV: "development"
volumes:
- ./config:/config
ports:
- "8080:8080"
depends_on:
- db

17
hello/seed.js Normal file
View File

@ -0,0 +1,17 @@
version: '3'
services:
db:
image: postgres
ports:
- "5432:5432"
hello_api:
image: dosco/super-graph:latest
environment:
GO_ENV: "development"
volumes:
- ./config:/config
ports:
- "8080:8080"
depends_on:
- db

View File

@ -337,21 +337,21 @@ func (c *compilerContext) renderJoinClose(sel *qcode.Select) error {
return nil
}
func (c *compilerContext) renderJoinTable(sel *qcode.Select) {
func (c *compilerContext) renderJoinTable(sel *qcode.Select) error {
parent := &c.s[sel.ParentID]
rel, err := c.schema.GetRel(sel.Table, parent.Table)
if err != nil {
panic(err)
return err
}
if rel.Type != RelOneToManyThrough {
return
return err
}
pt, err := c.schema.GetTable(parent.Table)
if err != nil {
return
return err
}
//fmt.Fprintf(w, ` LEFT OUTER JOIN "%s" ON (("%s"."%s") = ("%s_%d"."%s"))`,
@ -363,6 +363,8 @@ func (c *compilerContext) renderJoinTable(sel *qcode.Select) {
c.w.WriteString(`) = (`)
colWithTableID(c.w, pt.Name, parent.ID, rel.Col1)
c.w.WriteString(`))`)
return nil
}
func (c *compilerContext) renderColumns(sel *qcode.Select) {
@ -537,10 +539,15 @@ func (c *compilerContext) renderBaseSelect(sel *qcode.Select, ti *DBTableInfo,
}
if !isRoot {
c.renderJoinTable(sel)
if err := c.renderJoinTable(sel); err != nil {
return err
}
c.w.WriteString(` WHERE (`)
c.renderRelationship(sel)
if err := c.renderRelationship(sel); err != nil {
return err
}
if isFil {
c.w.WriteString(` AND `)
@ -610,12 +617,12 @@ func (c *compilerContext) renderOrderByColumns(sel *qcode.Select) {
}
}
func (c *compilerContext) renderRelationship(sel *qcode.Select) {
func (c *compilerContext) renderRelationship(sel *qcode.Select) error {
parent := c.s[sel.ParentID]
rel, err := c.schema.GetRel(sel.Table, parent.Table)
if err != nil {
panic(err)
return err
}
switch rel.Type {
@ -646,6 +653,8 @@ func (c *compilerContext) renderRelationship(sel *qcode.Select) {
colWithTable(c.w, rel.Through, rel.Col2)
c.w.WriteString(`))`)
}
return nil
}
func (c *compilerContext) renderWhere(sel *qcode.Select, ti *DBTableInfo) error {

View File

@ -7,6 +7,7 @@ import (
"io/ioutil"
"log"
"os"
"path"
"sort"
"strings"
)
@ -31,20 +32,20 @@ type allowList struct {
active bool
}
func initAllowList(path string) {
func initAllowList(cpath string) {
_allowList = allowList{
list: make(map[string]*allowItem),
saveChan: make(chan *allowItem),
active: true,
}
if len(path) != 0 {
fp := fmt.Sprintf("%s/allow.list", path)
if len(cpath) != 0 {
fp := path.Join(cpath, "allow.list")
if _, err := os.Stat(fp); err == nil {
_allowList.filepath = fp
} else if !os.IsNotExist(err) {
panic(err)
logger.Fatal().Err(err).Send()
}
}
@ -54,7 +55,7 @@ func initAllowList(path string) {
if _, err := os.Stat(fp); err == nil {
_allowList.filepath = fp
} else if !os.IsNotExist(err) {
panic(err)
logger.Fatal().Err(err).Send()
}
}
@ -64,15 +65,25 @@ func initAllowList(path string) {
if _, err := os.Stat(fp); err == nil {
_allowList.filepath = fp
} else if !os.IsNotExist(err) {
panic(err)
logger.Fatal().Err(err).Send()
}
}
if len(_allowList.filepath) == 0 {
panic("allow.list not found")
}
if conf.UseAllowList {
logger.Fatal().Msg("allow.list not found")
}
_allowList.load()
if len(cpath) == 0 {
_allowList.filepath = "./config/allow.list"
} else {
_allowList.filepath = path.Join(cpath, "allow.list")
}
logger.Warn().Msg("allow.list not found")
} else {
_allowList.load()
}
go func() {
for v := range _allowList.saveChan {
@ -182,7 +193,7 @@ func (al *allowList) save(item *allowItem) {
f, err := os.Create(al.filepath)
if err != nil {
logger.Warn().Err(err).Msg("Failed to write allow list to file")
logger.Warn().Err(err).Msgf("Failed to write allow list: %s", al.filepath)
return
}

View File

@ -35,7 +35,7 @@ func jwtHandler(next http.HandlerFunc) http.HandlerFunc {
case len(publicKeyFile) != 0:
kd, err := ioutil.ReadFile(publicKeyFile)
if err != nil {
panic(err)
logger.Fatal().Err(err).Send()
}
switch conf.Auth.JWT.PubKeyType {
@ -51,7 +51,7 @@ func jwtHandler(next http.HandlerFunc) http.HandlerFunc {
}
if err != nil {
panic(err)
logger.Fatal().Err(err).Send()
}
}

View File

@ -28,13 +28,13 @@ func railsRedisHandler(next http.HandlerFunc) http.HandlerFunc {
Dial: func() (redis.Conn, error) {
c, err := redis.DialURL(conf.Auth.Rails.URL)
if err != nil {
panic(err)
logger.Fatal().Err(err).Send()
}
pwd := conf.Auth.Rails.Password
if len(pwd) != 0 {
if _, err := c.Do("AUTH", pwd); err != nil {
panic(err)
logger.Fatal().Err(err).Send()
}
}
return c, err

View File

@ -17,8 +17,6 @@ import (
"github.com/spf13/viper"
)
//go:generate esc -o static.go -ignore \\.DS_Store -prefix ../web/build -private -pkg serv ../web/build
const (
serverName = "Super Graph"
@ -42,11 +40,10 @@ var (
migrateCmd *cobra.Command
statusCmd *cobra.Command
newMigrationCmd *cobra.Command
initCmd *cobra.Command
)
func Init() {
var err error
rootCmd = &cobra.Command{
Use: "super-graph",
Short: "An instant high-performance GraphQL API. No code needed. https://supergraph.dev",
@ -110,6 +107,13 @@ e.g. tern migrate -d last
Run: cmdNewMigration,
}
initCmd = &cobra.Command{
Use: "init APP-NAME",
Short: "Initialize a new application",
Long: "Generate all the required files to start on a new Super Graph app",
Run: cmdInit,
}
logger = initLog()
rootCmd.Flags().StringVar(&confPath,
@ -118,16 +122,13 @@ e.g. tern migrate -d last
//cmdMigrate.Flags().StringVarP(&cliOptions.destinationVersion,
// "destination", "d", "last", "destination migration version")
rootCmd.AddCommand(initCmd)
rootCmd.AddCommand(servCmd)
rootCmd.AddCommand(seedCmd)
rootCmd.AddCommand(migrateCmd)
rootCmd.AddCommand(statusCmd)
rootCmd.AddCommand(newMigrationCmd)
if conf, err = initConf(); err != nil {
logger.Fatal().Err(err).Msg("failed to read config")
}
if err := rootCmd.Execute(); err != nil {
logger.Fatal().Err(err).Send()
}

132
serv/cmd_init.go Normal file
View File

@ -0,0 +1,132 @@
package serv
import (
"bytes"
"io/ioutil"
"os"
"path"
"strings"
"text/template"
rice "github.com/GeertJohan/go.rice"
"github.com/spf13/cobra"
)
func cmdInit(cmd *cobra.Command, args []string) {
if len(args) != 1 {
cmd.Help()
os.Exit(1)
}
tmpl := newTempl(map[string]string{
"app_name": strings.Title(strings.Join(args, " ")),
"app_name_slug": strings.ToLower(strings.Join(args, "_")),
})
// Create app folder and add relevant files
name := args[0]
appPath := path.Join("./", name)
ifNotExists(appPath, func(p string) error {
return os.Mkdir(p, os.ModePerm)
})
ifNotExists(path.Join(appPath, "seed.js"), func(p string) error {
if v, err := tmpl.get("docker-compose.yml"); err == nil {
return ioutil.WriteFile(p, v, 0644)
} else {
return err
}
})
ifNotExists(path.Join(appPath, "docker-compose.yml"), func(p string) error {
if v, err := tmpl.get("docker-compose.yml"); err == nil {
return ioutil.WriteFile(p, v, 0644)
} else {
return err
}
})
// Create app config folder and add relevant files
appConfigPath := path.Join(appPath, "config")
ifNotExists(appConfigPath, func(p string) error {
return os.Mkdir(p, os.ModePerm)
})
ifNotExists(path.Join(appConfigPath, "dev.yml"), func(p string) error {
if v, err := tmpl.get("dev.yml"); err == nil {
return ioutil.WriteFile(p, v, 0644)
} else {
return err
}
})
ifNotExists(path.Join(appConfigPath, "prod.yml"), func(p string) error {
if v, err := tmpl.get("prod.yml"); err == nil {
return ioutil.WriteFile(p, v, 0644)
} else {
return err
}
})
// Create app migrations folder and add relevant files
appMigrationsPath := path.Join(appConfigPath, "migrations")
ifNotExists(appMigrationsPath, func(p string) error {
return os.Mkdir(p, os.ModePerm)
})
ifNotExists(path.Join(appMigrationsPath, "100_init.sql"), func(p string) error {
if v, err := tmpl.get("100_init.sql"); err == nil {
return ioutil.WriteFile(p, v, 0644)
} else {
return err
}
})
logger.Info().Msgf("app '%s' initialized", name)
}
type Templ struct {
*rice.Box
data map[string]string
}
func newTempl(data map[string]string) *Templ {
return &Templ{rice.MustFindBox("../tmpl"), data}
}
func (t *Templ) get(name string) ([]byte, error) {
v := t.MustString(name)
b := bytes.Buffer{}
tm := template.Must(template.New(name).Parse(v))
if err := tm.Execute(&b, t.data); err != nil {
return nil, err
}
return b.Bytes(), nil
}
func ifNotExists(filePath string, doFn func(string) error) {
_, err := os.Stat(filePath)
if err == nil {
logger.Info().Err(err).Msgf("create skipped '%s' exists", filePath)
return
}
if os.IsNotExist(err) == false {
logger.Fatal().Err(err).Msgf("unable to check if '%s' exists", filePath)
}
err = doFn(filePath)
if err != nil {
logger.Fatal().Err(err).Msgf("unable to create '%s'", filePath)
}
logger.Info().Msgf("created '%s'", filePath)
}

View File

@ -16,6 +16,11 @@ import (
func cmdSeed(cmd *cobra.Command, args []string) {
var err error
if conf, err = initConf(); err != nil {
logger.Fatal().Err(err).Msg("failed to read config")
}
conf.UseAllowList = false
db, err = initDBPool(conf)

View File

@ -7,6 +7,10 @@ import (
func cmdServ(cmd *cobra.Command, args []string) {
var err error
if conf, err = initConf(); err != nil {
logger.Fatal().Err(err).Msg("failed to read config")
}
db, err = initDBPool(conf)
if err != nil {
logger.Fatal().Err(err).Msg("failed to connect to database")

View File

@ -40,6 +40,12 @@ func cmdNewMigration(cmd *cobra.Command, args []string) {
os.Exit(1)
}
var err error
if conf, err = initConf(); err != nil {
logger.Fatal().Err(err).Msg("failed to read config")
}
name := args[0]
m, err := migrate.FindMigrations(conf.MigrationsPath)
@ -64,10 +70,16 @@ func cmdNewMigration(cmd *cobra.Command, args []string) {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
logger.Info().Msgf("created migration '%s'\n", mpath)
logger.Info().Msgf("created migration '%s'", mpath)
}
func cmdMigrate(cmd *cobra.Command, args []string) {
var err error
if conf, err = initConf(); err != nil {
logger.Fatal().Err(err).Msg("failed to read config")
}
conn, err := initDB(conf)
if err != nil {
logger.Fatal().Err(err).Msg("failed to connect to database")
@ -157,6 +169,12 @@ func cmdMigrate(cmd *cobra.Command, args []string) {
}
func cmdStatus(cmd *cobra.Command, args []string) {
var err error
if conf, err = initConf(); err != nil {
logger.Fatal().Err(err).Msg("failed to read config")
}
conn, err := initDB(conf)
if err != nil {
logger.Fatal().Err(err).Msg("failed to connect to database")

View File

@ -30,7 +30,7 @@ func initPreparedList() {
for k, v := range _allowList.list {
err := prepareStmt(k, v.gql, v.vars)
if err != nil {
panic(err)
logger.Fatal().Err(err).Send()
}
}
}

View File

@ -161,7 +161,7 @@ func Do(log func(string, ...interface{}), additional ...dir) error {
func ReExec() {
err := syscall.Exec(binSelf, append([]string{binSelf}, os.Args[1:]...), os.Environ())
if err != nil {
panic(fmt.Sprintf("cannot restart: %v", err))
logger.Fatal().Err(err).Msg("cannot restart")
}
}

View File

@ -9,6 +9,7 @@ import (
"strings"
"time"
rice "github.com/GeertJohan/go.rice"
"github.com/dosco/super-graph/psql"
"github.com/dosco/super-graph/qcode"
)
@ -38,22 +39,22 @@ func initCompilers(c *config) (*qcode.Compiler, *psql.Compiler, error) {
return qc, pc, nil
}
func initWatcher(path string) {
func initWatcher(cpath string) {
if conf.WatchAndReload == false {
return
}
var d dir
if len(path) == 0 || path == "./" {
if len(cpath) == 0 || cpath == "./" {
d = Dir("./config", ReExec)
} else {
d = Dir(path, ReExec)
d = Dir(cpath, ReExec)
}
go func() {
err := Do(logger.Printf, d)
if err != nil {
panic(err)
logger.Fatal().Err(err).Send()
}
}()
}
@ -109,7 +110,7 @@ func routeHandler() http.Handler {
mux.Handle("/api/v1/graphql", withAuth(apiv1Http))
if conf.WebUI {
mux.Handle("/", http.FileServer(_escFS(false)))
mux.Handle("/", http.FileServer(rice.MustFindBox("../web/build").HTTPBox()))
}
fn := func(w http.ResponseWriter, r *http.Request) {

57
test.yml Normal file
View File

@ -0,0 +1,57 @@
app_name: Test App
auth:
cookie: _app_session
header: X-User-ID
rails:
max_active: 12000
max_idle: 80
secret_key_base: 0a248500a64c01184edb4d7ad3a805488f8097ac761b76aaa6c17c01dcb7af03a2f18ba61b2868134b9c7b79a122bc0dadff4367414a2d173297bfea92be5566
version: 5.2
type: rails
auth_fail_block: never
database:
dbname: app_development
defaults:
blocklist:
- ar_internal_metadata
- schema_migrations
- secret
- password
- encrypted
- token
host: db
password: ""
port: 5432
schema: public
tables:
- name: users
- filter: none
name: customers
remotes:
- id: stripe_id
name: payments
pass_headers:
- cookie
path: data
set_headers:
- name: Host
value: 0.0.0.0
url: http://rails_app:3000/stripe/$id
- filter:
- '{ id: { eq: $user_id } }'
name: me
table: users
type: postgres
user: postgres
variables:
account_id: (select account_id from users where id = $user_id)
enable_tracing: true
env: development
host_port: 0.0.0.0:8080
log_level: debug
migrations_path: ./config/migrations
port: "8080"
reload_on_config_change: true
seed_file: seed.js
use_allow_list: false
web_ui: true

21
tmpl/100_init.sql Normal file
View File

@ -0,0 +1,21 @@
-- Write your migrate up statements here
CREATE DATABASE {{ .app_name_slug }}_database
-- CREATE TABLE public.users (
-- id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
-- full_name text
-- email text UNIQUE NOT NULL CHECK (length(email) < 255),
-- encrypted_password text,
-- created_at timestamptz NOT NULL NOT NULL DEFAULT NOW(),
-- updated_at timestamptz NOT NULL NOT NULL DEFAULT NOW()
-- );
---- create above / drop below ----
-- Write your migrate down statements here. If this migration is irreversible
-- Then delete the separator line above.
-- DROP TABLE public.users
DROP DATABASE IF EXISTS {{ .app_name_slug }}_database

152
tmpl/dev.yml Normal file
View File

@ -0,0 +1,152 @@
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
# Throw a 401 on auth failure for queries that need auth
# valid values: always, per_query, never
auth_fail_block: never
# Latency tracing for database queries and remote joins
# the resulting latency information is returned with the
# response
enable_tracing: true
# Watch the config folder and reload Super Graph
# with the new configs when a change is detected
reload_on_config_change: true
# File that points to the database seeding script
# seed_file: seed.js
# Path pointing to where the migrations can be found
migrations_path: ./config/migrations
# Postgres related environment Variables
# SG_DATABASE_HOST
# SG_DATABASE_PORT
# SG_DATABASE_USER
# SG_DATABASE_PASSWORD
# Auth related environment Variables
# SG_AUTH_RAILS_COOKIE_SECRET_KEY_BASE
# SG_AUTH_RAILS_REDIS_URL
# SG_AUTH_RAILS_REDIS_PASSWORD
# SG_AUTH_JWT_PUBLIC_KEY_FILE
# inflections:
# person: people
# sheep: sheep
auth:
# Can be 'rails' or 'jwt'
type: rails
cookie: _app_session
# Comment this out if you want to disable setting
# the user_id via a header. Good for testing
header: X-User-ID
rails:
# Rails version this is used for reading the
# various cookies formats.
version: 5.2
# Found in 'Rails.application.config.secret_key_base'
secret_key_base: 0a248500a64c01184edb4d7ad3a805488f8097ac761b76aaa6c17c01dcb7af03a2f18ba61b2868134b9c7b79a122bc0dadff4367414a2d173297bfea92be5566
# Remote cookie store. (memcache or redis)
# url: redis://redis:6379
# password: ""
# max_idle: 80
# max_active: 12000
# In most cases you don't need these
# salt: "encrypted cookie"
# sign_salt: "signed encrypted cookie"
# auth_salt: "authenticated encrypted cookie"
# jwt:
# provider: auth0
# secret: abc335bfcfdb04e50db5bb0a4d67ab9
# public_key_file: /secrets/public_key.pem
# public_key_type: ecdsa #rsa
database:
type: postgres
host: db
port: 5432
dbname: {{ .app_name_slug }}_database
user: postgres
password: ''
#schema: "public"
#pool_size: 10
#max_retries: 0
#log_level: "debug"
# Define variables here that you want to use in filters
# sub-queries must be wrapped in ()
variables:
account_id: "(select account_id from users where id = $user_id)"
# Define defaults to for the field key and values below
defaults:
# filter: ["{ user_id: { eq: $user_id } }"]
# Field and table names that you wish to block
blocklist:
- ar_internal_metadata
- schema_migrations
- secret
- password
- encrypted
- token
tables:
- name: users
# This filter will overwrite defaults.filter
# filter: ["{ id: { eq: $user_id } }"]
# - name: products
# # Multiple filters are AND'd together
# filter: [
# "{ price: { gt: 0 } }",
# "{ price: { lt: 8 } }"
# ]
- name: customers
# No filter is used for this field not
# even defaults.filter
filter: none
remotes:
- name: payments
id: stripe_id
url: http://rails_app:3000/stripe/$id
path: data
# debug: true
pass_headers:
- cookie
set_headers:
- name: Host
value: 0.0.0.0
# - name: Authorization
# value: Bearer <stripe_api_key>
- # You can create new fields that have a
# real db table backing them
name: me
table: users
filter: ["{ id: { eq: $user_id } }"]
# - name: posts
# filter: ["{ account_id: { _eq: $account_id } }"]

17
tmpl/docker-compose.yml Normal file
View File

@ -0,0 +1,17 @@
version: '3'
services:
db:
image: postgres
ports:
- "5432:5432"
{{ .app_name_slug }}_api:
image: dosco/super-graph:latest
environment:
GO_ENV: "development"
volumes:
- ./config:/config
ports:
- "8080:8080"
depends_on:
- db

144
tmpl/prod.yml Normal file
View File

@ -0,0 +1,144 @@
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
# Throw a 401 on auth failure for queries that need auth
# valid values: always, per_query, never
auth_fail_block: always
# Latency tracing for database queries and remote joins
# the resulting latency information is returned with the
# response
enable_tracing: true
# File that points to the database seeding script
# seed_file: seed.js
# Path pointing to where the migrations can be found
# migrations_path: migrations
# Postgres related environment Variables
# SG_DATABASE_HOST
# SG_DATABASE_PORT
# SG_DATABASE_USER
# SG_DATABASE_PASSWORD
# Auth related environment Variables
# SG_AUTH_RAILS_COOKIE_SECRET_KEY_BASE
# SG_AUTH_RAILS_REDIS_URL
# SG_AUTH_RAILS_REDIS_PASSWORD
# SG_AUTH_JWT_PUBLIC_KEY_FILE
# inflections:
# person: people
# sheep: sheep
auth:
# Can be 'rails' or 'jwt'
type: rails
cookie: _app_session
# Comment this out if you want to disable setting
# the user_id via a header. Good for testing
header: X-User-ID
rails:
# Rails version this is used for reading the
# various cookies formats.
version: 5.2
# Found in 'Rails.application.config.secret_key_base'
secret_key_base: 0a248500a64c01184edb4d7ad3a805488f8097ac761b76aaa6c17c01dcb7af03a2f18ba61b2868134b9c7b79a122bc0dadff4367414a2d173297bfea92be5566
# Remote cookie store. (memcache or redis)
# url: redis://127.0.0.1:6379
# password: test
# max_idle: 80,
# max_active: 12000,
# In most cases you don't need these
# salt: "encrypted cookie"
# sign_salt: "signed encrypted cookie"
# auth_salt: "authenticated encrypted cookie"
# jwt:
# provider: auth0
# secret: abc335bfcfdb04e50db5bb0a4d67ab9
# public_key_file: /secrets/public_key.pem
# public_key_type: ecdsa #rsa
database:
type: postgres
host: db
port: 5432
dbname: {{ .app_name_slug }}_database
user: postgres
password: ''
#pool_size: 10
#max_retries: 0
#log_level: "debug"
# Define variables here that you want to use in filters
# sub-queries must be wrapped in ()
variables:
account_id: "(select account_id from users where id = $user_id)"
# Define defaults to for the field key and values below
defaults:
filter: ["{ user_id: { eq: $user_id } }"]
# Field and table names that you wish to block
blocklist:
- ar_internal_metadata
- schema_migrations
- secret
- password
- encrypted
- token
tables:
- name: users
# This filter will overwrite defaults.filter
filter: ["{ id: { eq: $user_id } }"]
- name: products
# Multiple filters are AND'd together
filter: [
"{ price: { gt: 0 } }",
"{ price: { lt: 8 } }"
]
- name: customers
# No filter is used for this field not
# even defaults.filter
filter: none
# remotes:
# - name: payments
# id: stripe_id
# url: http://rails_app:3000/stripe/$id
# path: data
# # pass_headers:
# # - cookie
# # - host
# set_headers:
# - name: Authorization
# value: Bearer <stripe_api_key>
- # You can create new fields that have a
# real db table backing them
name: me
table: users
filter: ["{ id: { eq: $user_id } }"]
# - name: posts
# filter: ["{ account_id: { _eq: $account_id } }"]

23
tmpl/seed.js Normal file
View File

@ -0,0 +1,23 @@
// Example script to seed database
var users = [];
for (i = 0; i < 10; i++) {
var pwd = fake.password()
var data = {
first_name: fake.first_name(),
last_name: fake.last_name(),
email: fake.email(),
password: pwd,
password_confirmation: pwd
}
var res = graphql(" \
mutation { \
user(insert: $data) { \
id \
} \
}", { data: data })
users.push(res.user)
}