Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
c7557f761f | |||
09d6460a13 | |||
40c99e9ef3 | |||
75ff5510d4 | |||
1370d24985 | |||
ef50c1957b | |||
41ea6ef6f5 | |||
a266517d17 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -29,10 +29,10 @@
|
|||||||
.release
|
.release
|
||||||
main
|
main
|
||||||
super-graph
|
super-graph
|
||||||
supergraph
|
|
||||||
*-fuzz.zip
|
*-fuzz.zip
|
||||||
crashers
|
crashers
|
||||||
suppressions
|
suppressions
|
||||||
release
|
release
|
||||||
.gofuzz
|
.gofuzz
|
||||||
*-fuzz.zip
|
*-fuzz.zip
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ rules:
|
|||||||
- name: run
|
- name: run
|
||||||
match: \.go$
|
match: \.go$
|
||||||
ignore: web|examples|docs|_test\.go$
|
ignore: web|examples|docs|_test\.go$
|
||||||
command: go run cmd/main.go serv
|
command: go run main.go serv
|
||||||
- name: test
|
- name: test
|
||||||
match: _test\.go$
|
match: _test\.go$
|
||||||
command: go test -cover {PKG}
|
command: go test -cover {PKG}
|
12
Dockerfile
12
Dockerfile
@ -1,10 +1,12 @@
|
|||||||
# stage: 1
|
# stage: 1
|
||||||
FROM node:10 as react-build
|
FROM node:10 as react-build
|
||||||
WORKDIR /web
|
WORKDIR /web
|
||||||
COPY /cmd/internal/serv/web/ ./
|
COPY /internal/serv/web/ ./
|
||||||
RUN yarn
|
RUN yarn
|
||||||
RUN yarn build
|
RUN yarn build
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# stage: 2
|
# stage: 2
|
||||||
FROM golang:1.14-alpine as go-build
|
FROM golang:1.14-alpine as go-build
|
||||||
RUN apk update && \
|
RUN apk update && \
|
||||||
@ -22,8 +24,8 @@ RUN chmod 755 /usr/local/bin/sops
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . /app
|
COPY . /app
|
||||||
|
|
||||||
RUN mkdir -p /app/cmd/internal/serv/web/build
|
RUN mkdir -p /app/internal/serv/web/build
|
||||||
COPY --from=react-build /web/build/ ./cmd/internal/serv/web/build
|
COPY --from=react-build /web/build/ ./internal/serv/web/build
|
||||||
|
|
||||||
RUN go mod vendor
|
RUN go mod vendor
|
||||||
RUN make build
|
RUN make build
|
||||||
@ -31,6 +33,8 @@ RUN echo "Compressing binary, will take a bit of time..." && \
|
|||||||
upx --ultra-brute -qq super-graph && \
|
upx --ultra-brute -qq super-graph && \
|
||||||
upx -t super-graph
|
upx -t super-graph
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# stage: 3
|
# stage: 3
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
@ -41,7 +45,7 @@ RUN mkdir -p /config
|
|||||||
COPY --from=go-build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
COPY --from=go-build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
COPY --from=go-build /app/config/* /config/
|
COPY --from=go-build /app/config/* /config/
|
||||||
COPY --from=go-build /app/super-graph .
|
COPY --from=go-build /app/super-graph .
|
||||||
COPY --from=go-build /app/cmd/scripts/start.sh .
|
COPY --from=go-build /app/internal/scripts/start.sh .
|
||||||
COPY --from=go-build /usr/local/bin/sops .
|
COPY --from=go-build /usr/local/bin/sops .
|
||||||
|
|
||||||
RUN chmod +x /super-graph
|
RUN chmod +x /super-graph
|
||||||
|
23
Makefile
23
Makefile
@ -12,10 +12,10 @@ endif
|
|||||||
export GO111MODULE := on
|
export GO111MODULE := on
|
||||||
|
|
||||||
# Build-time Go variables
|
# Build-time Go variables
|
||||||
version = github.com/dosco/super-graph/serv.version
|
version = github.com/dosco/super-graph/internal/serv.version
|
||||||
gitBranch = github.com/dosco/super-graph/serv.gitBranch
|
gitBranch = github.com/dosco/super-graph/internal/serv.gitBranch
|
||||||
lastCommitSHA = github.com/dosco/super-graph/serv.lastCommitSHA
|
lastCommitSHA = github.com/dosco/super-graph/internal/serv.lastCommitSHA
|
||||||
lastCommitTime = github.com/dosco/super-graph/serv.lastCommitTime
|
lastCommitTime = github.com/dosco/super-graph/internal/serv.lastCommitTime
|
||||||
|
|
||||||
BUILD_FLAGS ?= -ldflags '-s -w -X ${lastCommitSHA}=${BUILD} -X "${lastCommitTime}=${BUILD_DATE}" -X "${version}=${BUILD_VERSION}" -X ${gitBranch}=${BUILD_BRANCH}'
|
BUILD_FLAGS ?= -ldflags '-s -w -X ${lastCommitSHA}=${BUILD} -X "${lastCommitTime}=${BUILD_DATE}" -X "${version}=${BUILD_VERSION}" -X ${gitBranch}=${BUILD_BRANCH}'
|
||||||
|
|
||||||
@ -28,18 +28,18 @@ BIN_DIR := $(GOPATH)/bin
|
|||||||
GORICE := $(BIN_DIR)/rice
|
GORICE := $(BIN_DIR)/rice
|
||||||
GOLANGCILINT := $(BIN_DIR)/golangci-lint
|
GOLANGCILINT := $(BIN_DIR)/golangci-lint
|
||||||
GITCHGLOG := $(BIN_DIR)/git-chglog
|
GITCHGLOG := $(BIN_DIR)/git-chglog
|
||||||
WEB_BUILD_DIR := ./cmd/internal/serv/web/build/manifest.json
|
WEB_BUILD_DIR := ./internal/serv/web/build/manifest.json
|
||||||
|
|
||||||
$(GORICE):
|
$(GORICE):
|
||||||
@GO111MODULE=off go get -u github.com/GeertJohan/go.rice/rice
|
@GO111MODULE=off go get -u github.com/GeertJohan/go.rice/rice
|
||||||
|
|
||||||
$(WEB_BUILD_DIR):
|
$(WEB_BUILD_DIR):
|
||||||
@echo "First install Yarn and create a build of the web UI then re-run make install"
|
@echo "First install Yarn and create a build of the web UI then re-run make install"
|
||||||
@echo "Run this command: yarn --cwd cmd/internal/serv/web/ build"
|
@echo "Run this command: yarn --cwd internal/serv/web/ build"
|
||||||
@exit 1
|
@exit 1
|
||||||
|
|
||||||
$(GITCHGLOG):
|
$(GITCHGLOG):
|
||||||
@GO111MODULE=off go get -u github.com/git-chglog/git-chglog/cmd/git-chglog
|
@GO111MODULE=off go get -u github.com/git-chglog/git-chglog/git-chglog
|
||||||
|
|
||||||
changelog: $(GITCHGLOG)
|
changelog: $(GITCHGLOG)
|
||||||
@git-chglog $(ARGS)
|
@git-chglog $(ARGS)
|
||||||
@ -57,7 +57,7 @@ os = $(word 1, $@)
|
|||||||
|
|
||||||
$(PLATFORMS): lint test
|
$(PLATFORMS): lint test
|
||||||
@mkdir -p release
|
@mkdir -p release
|
||||||
@GOOS=$(os) GOARCH=amd64 go build $(BUILD_FLAGS) -o release/$(BINARY)-$(BUILD_VERSION)-$(os)-amd64 cmd/main.go
|
@GOOS=$(os) GOARCH=amd64 go build $(BUILD_FLAGS) -o release/$(BINARY)-$(BUILD_VERSION)-$(os)-amd64 main.go
|
||||||
|
|
||||||
release: windows linux darwin
|
release: windows linux darwin
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ gen: $(GORICE) $(WEB_BUILD_DIR)
|
|||||||
@go generate ./...
|
@go generate ./...
|
||||||
|
|
||||||
$(BINARY): clean
|
$(BINARY): clean
|
||||||
@go build $(BUILD_FLAGS) -o $(BINARY) cmd/main.go
|
@go build $(BUILD_FLAGS) -o $(BINARY) main.go
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@rm -f $(BINARY)
|
@rm -f $(BINARY)
|
||||||
@ -77,11 +77,10 @@ clean:
|
|||||||
run: clean
|
run: clean
|
||||||
@go run $(BUILD_FLAGS) main.go $(ARGS)
|
@go run $(BUILD_FLAGS) main.go $(ARGS)
|
||||||
|
|
||||||
install: gen
|
install: clean build
|
||||||
@echo $(GOPATH)
|
|
||||||
@echo "Commit Hash: `git rev-parse HEAD`"
|
@echo "Commit Hash: `git rev-parse HEAD`"
|
||||||
@echo "Old Hash: `shasum $(GOPATH)/bin/$(BINARY) 2>/dev/null | cut -c -32`"
|
@echo "Old Hash: `shasum $(GOPATH)/bin/$(BINARY) 2>/dev/null | cut -c -32`"
|
||||||
@go install $(BUILD_FLAGS) cmd
|
@mv $(BINARY) $(GOPATH)/bin/$(BINARY)
|
||||||
@echo "New Hash:" `shasum $(GOPATH)/bin/$(BINARY) 2>/dev/null | cut -c -32`
|
@echo "New Hash:" `shasum $(GOPATH)/bin/$(BINARY) 2>/dev/null | cut -c -32`
|
||||||
|
|
||||||
uninstall: clean
|
uninstall: clean
|
||||||
|
82
README.md
82
README.md
@ -1,26 +1,74 @@
|
|||||||
<!-- <a href="https://supergraph.dev"><img src="https://supergraph.dev/hologram.svg" width="100" height="100" align="right" /></a> -->
|
<img src="docs/guide/.vuepress/public/super-graph.png" width="250" />
|
||||||
|
|
||||||
<img src="docs/.vuepress/public/super-graph.png" width="250" />
|
|
||||||
|
|
||||||
### Build web products faster. Secure high performance GraphQL
|
### Build web products faster. Secure high performance GraphQL
|
||||||
|
|
||||||

|
[](https://pkg.go.dev/github.com/dosco/super-graph/core?tab=doc)
|
||||||

|

|
||||||

|

|
||||||
|

|
||||||
[](https://discord.gg/6pSWCTZ)
|
[](https://discord.gg/6pSWCTZ)
|
||||||
|
|
||||||
|
## What's Super Graph?
|
||||||
|
|
||||||
## What is Super Graph
|
Designed to 100x your developer productivity. Super Graph will instantly and without you writing code provide you a high performance GraphQL API for Postgres DB. GraphQL queries are compiled into a single fast SQL query. Super Graph is a GO library and a service, use it in your own code or run it as a seperate service.
|
||||||
|
|
||||||
Is designed to 100x your developer productivity. Super Graph will instantly and without you writing code provide you a high performance and secure GraphQL API for Postgres DB. GraphQL queries are translated into a single fast SQL query. No more writing API code as you develop
|
## Using it as a service
|
||||||
your web frontend 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, built-in DB mutations and seeding, and a lot more.
|
```console
|
||||||
|
git clone https://github.com/dosco/super-graph
|
||||||
|
cd ./super-graph
|
||||||
|
make install
|
||||||
|
|
||||||

|
super-graph new <app_name>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using it in your own code
|
||||||
|
|
||||||
## The story of Super Graph?
|
```golang
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
"github.com/dosco/super-graph/core"
|
||||||
|
_ "github.com/jackc/pgx/v4/stdlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
db, err := sql.Open("pgx", "postgres://postgrs:@localhost:5432/example_db")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conf, err := core.ReadInConfig("./config/dev.yml")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sg, err = core.NewSuperGraph(conf, db)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
query := `
|
||||||
|
query {
|
||||||
|
posts {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
res, err := sg.GraphQL(context.Background(), query, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(string(res.Data))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## About Super Graph
|
||||||
|
|
||||||
After working on several products through my career I find that we spend way too much time on building API backends. Most APIs also require constant updating, this costs real time and money.
|
After working on several products through my career I find that we spend way too much time on building API backends. Most APIs also require constant updating, this costs real time and money.
|
||||||
|
|
||||||
@ -37,6 +85,7 @@ This compiler is what sits at the heart of Super Graph with layers of useful fun
|
|||||||
- Complex nested queries and mutations
|
- Complex nested queries and mutations
|
||||||
- Auto learns database tables and relationships
|
- Auto learns database tables and relationships
|
||||||
- Role and Attribute based access control
|
- Role and Attribute based access control
|
||||||
|
- Opaque cursor based efficient pagination
|
||||||
- Full text search and aggregations
|
- Full text search and aggregations
|
||||||
- JWT tokens supported (Auth0, etc)
|
- JWT tokens supported (Auth0, etc)
|
||||||
- Join database queries with remote REST APIs
|
- Join database queries with remote REST APIs
|
||||||
@ -50,15 +99,6 @@ This compiler is what sits at the heart of Super Graph with layers of useful fun
|
|||||||
- Database seeding tool
|
- Database seeding tool
|
||||||
- Works with Postgres and YugabyteDB
|
- Works with Postgres and YugabyteDB
|
||||||
|
|
||||||
## Get started
|
|
||||||
|
|
||||||
```
|
|
||||||
git clone https://github.com/dosco/super-graph
|
|
||||||
cd ./super-graph
|
|
||||||
make install
|
|
||||||
|
|
||||||
super-graph new <app_name>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
package serv
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/dosco/super-graph/config"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func cmdConfDump(cmd *cobra.Command, args []string) {
|
|
||||||
if len(args) != 1 {
|
|
||||||
cmd.Help() //nolint: errcheck
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fname := fmt.Sprintf("%s.%s", config.GetConfigName(), args[0])
|
|
||||||
|
|
||||||
conf, err := initConf()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("ERR failed to read config: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := conf.WriteConfigAs(fname); err != nil {
|
|
||||||
log.Fatalf("ERR failed to write config: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("INF config dumped to ./%s", fname)
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
package serv
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/dosco/super-graph/config"
|
|
||||||
_ "github.com/jackc/pgx/v4/stdlib"
|
|
||||||
)
|
|
||||||
|
|
||||||
func initConf() (*config.Config, error) {
|
|
||||||
return config.NewConfigWithLogger(confPath, log)
|
|
||||||
}
|
|
||||||
|
|
||||||
func initDB(c *config.Config) (*sql.DB, error) {
|
|
||||||
var db *sql.DB
|
|
||||||
var err error
|
|
||||||
|
|
||||||
cs := fmt.Sprintf("postgres://%s:%s@%s:%d/%s",
|
|
||||||
c.DB.User, c.DB.Password,
|
|
||||||
c.DB.Host, c.DB.Port, c.DB.DBName)
|
|
||||||
|
|
||||||
for i := 1; i < 10; i++ {
|
|
||||||
db, err = sql.Open("pgx", cs)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(time.Duration(i*100) * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return db, nil
|
|
||||||
|
|
||||||
// config, _ := pgxpool.ParseConfig("")
|
|
||||||
// config.ConnConfig.Host = c.DB.Host
|
|
||||||
// config.ConnConfig.Port = c.DB.Port
|
|
||||||
// config.ConnConfig.Database = c.DB.DBName
|
|
||||||
// config.ConnConfig.User = c.DB.User
|
|
||||||
// config.ConnConfig.Password = c.DB.Password
|
|
||||||
// config.ConnConfig.RuntimeParams = map[string]string{
|
|
||||||
// "application_name": c.AppName,
|
|
||||||
// "search_path": c.DB.Schema,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// switch c.LogLevel {
|
|
||||||
// case "debug":
|
|
||||||
// config.ConnConfig.LogLevel = pgx.LogLevelDebug
|
|
||||||
// case "info":
|
|
||||||
// config.ConnConfig.LogLevel = pgx.LogLevelInfo
|
|
||||||
// case "warn":
|
|
||||||
// config.ConnConfig.LogLevel = pgx.LogLevelWarn
|
|
||||||
// case "error":
|
|
||||||
// config.ConnConfig.LogLevel = pgx.LogLevelError
|
|
||||||
// default:
|
|
||||||
// config.ConnConfig.LogLevel = pgx.LogLevelNone
|
|
||||||
// }
|
|
||||||
|
|
||||||
// config.ConnConfig.Logger = NewSQLLogger(logger)
|
|
||||||
|
|
||||||
// // if c.DB.MaxRetries != 0 {
|
|
||||||
// // opt.MaxRetries = c.DB.MaxRetries
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// if c.DB.PoolSize != 0 {
|
|
||||||
// config.MaxConns = conf.DB.PoolSize
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var db *pgxpool.Pool
|
|
||||||
// var err error
|
|
||||||
|
|
||||||
// for i := 1; i < 10; i++ {
|
|
||||||
// db, err = pgxpool.ConnectConfig(context.Background(), config)
|
|
||||||
// if err == nil {
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// time.Sleep(time.Duration(i*100) * time.Millisecond)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return db, nil
|
|
||||||
}
|
|
755
config/allow.list
Normal file
755
config/allow.list
Normal file
@ -0,0 +1,755 @@
|
|||||||
|
# http://localhost:8080/
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "Protect Ya Neck",
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Enter the Wu-Tang",
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
products(insert: $data) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"update": {
|
||||||
|
"name": "Wu-Tang",
|
||||||
|
"description": "No description needed"
|
||||||
|
},
|
||||||
|
"product_id": 1
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
products(id: $product_id, update: $update) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query {
|
||||||
|
users {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
picture: avatar
|
||||||
|
products(limit: 2, where: {price: {gt: 10}}) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "Gumbo1",
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Gumbo2",
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
products(id: 199, delete: true) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query {
|
||||||
|
products {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
user {
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"product_id": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
products(id: $product_id, delete: true) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query {
|
||||||
|
products {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
price
|
||||||
|
users {
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"email": "gfk@myspace.com",
|
||||||
|
"full_name": "Ghostface Killah",
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
user(insert: $data) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"update": {
|
||||||
|
"name": "Helloo",
|
||||||
|
"description": "World \u003c\u003e"
|
||||||
|
},
|
||||||
|
"user": 123
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
products(id: 5, update: $update) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"name": "WOOO",
|
||||||
|
"price": 50.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
products(insert: $data) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query getProducts {
|
||||||
|
products {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
price
|
||||||
|
description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query {
|
||||||
|
deals {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
price
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"beer": "smoke"
|
||||||
|
}
|
||||||
|
|
||||||
|
query beerSearch {
|
||||||
|
products(search: $beer) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
search_rank
|
||||||
|
search_headline_description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query {
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
full_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"email": "goo1@rug.com",
|
||||||
|
"full_name": "The Dude",
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now",
|
||||||
|
"product": {
|
||||||
|
"name": "Apple",
|
||||||
|
"price": 1.25,
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
user(insert: $data) {
|
||||||
|
id
|
||||||
|
full_name
|
||||||
|
email
|
||||||
|
product {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
price
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"email": "goo12@rug.com",
|
||||||
|
"full_name": "The Dude",
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now",
|
||||||
|
"product": [
|
||||||
|
{
|
||||||
|
"name": "Banana 1",
|
||||||
|
"price": 1.1,
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Banana 2",
|
||||||
|
"price": 2.2,
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
user(insert: $data) {
|
||||||
|
id
|
||||||
|
full_name
|
||||||
|
email
|
||||||
|
products {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
price
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"name": "Banana 3",
|
||||||
|
"price": 1.1,
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now",
|
||||||
|
"user": {
|
||||||
|
"email": "a2@a.com",
|
||||||
|
"full_name": "The Dude",
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
products(insert: $data) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
price
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
full_name
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"update": {
|
||||||
|
"name": "my_name",
|
||||||
|
"description": "my_desc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
product(id: 15, update: $update, where: {id: {eq: 1}}) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"update": {
|
||||||
|
"name": "my_name",
|
||||||
|
"description": "my_desc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
product(update: $update, where: {id: {eq: 1}}) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"update": {
|
||||||
|
"name": "my_name 2",
|
||||||
|
"description": "my_desc 2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
product(update: $update, where: {id: {eq: 1}}) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"sale_type": "tuutuu",
|
||||||
|
"quantity": 5,
|
||||||
|
"due_date": "now",
|
||||||
|
"customer": {
|
||||||
|
"email": "thedude1@rug.com",
|
||||||
|
"full_name": "The Dude"
|
||||||
|
},
|
||||||
|
"product": {
|
||||||
|
"name": "Apple",
|
||||||
|
"price": 1.25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
purchase(update: $data, id: 5) {
|
||||||
|
sale_type
|
||||||
|
quantity
|
||||||
|
due_date
|
||||||
|
customer {
|
||||||
|
id
|
||||||
|
full_name
|
||||||
|
email
|
||||||
|
}
|
||||||
|
product {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
price
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"email": "thedude@rug.com",
|
||||||
|
"full_name": "The Dude",
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now",
|
||||||
|
"product": {
|
||||||
|
"where": {
|
||||||
|
"id": 2
|
||||||
|
},
|
||||||
|
"name": "Apple",
|
||||||
|
"price": 1.25,
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
user(update: $data, where: {id: {eq: 8}}) {
|
||||||
|
id
|
||||||
|
full_name
|
||||||
|
email
|
||||||
|
product {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
price
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"email": "thedude@rug.com",
|
||||||
|
"full_name": "The Dude",
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now",
|
||||||
|
"product": {
|
||||||
|
"where": {
|
||||||
|
"id": 2
|
||||||
|
},
|
||||||
|
"name": "Apple",
|
||||||
|
"price": 1.25,
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query {
|
||||||
|
user(where: {id: {eq: 8}}) {
|
||||||
|
id
|
||||||
|
product {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
price
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"name": "Apple",
|
||||||
|
"price": 1.25,
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now",
|
||||||
|
"user": {
|
||||||
|
"email": "thedude@rug.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query {
|
||||||
|
user {
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"name": "Apple",
|
||||||
|
"price": 1.25,
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now",
|
||||||
|
"user": {
|
||||||
|
"email": "booboo@demo.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
product(update: $data, id: 6) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
full_name
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"name": "Apple",
|
||||||
|
"price": 1.25,
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now",
|
||||||
|
"user": {
|
||||||
|
"email": "booboo@demo.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query {
|
||||||
|
product(id: 6) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
full_name
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"email": "thedude123@rug.com",
|
||||||
|
"full_name": "The Dude",
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now",
|
||||||
|
"product": {
|
||||||
|
"connect": {
|
||||||
|
"id": 7
|
||||||
|
},
|
||||||
|
"disconnect": {
|
||||||
|
"id": 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
user(update: $data, id: 6) {
|
||||||
|
id
|
||||||
|
full_name
|
||||||
|
email
|
||||||
|
product {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
price
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"name": "Apple",
|
||||||
|
"price": 1.25,
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now",
|
||||||
|
"user": {
|
||||||
|
"connect": {
|
||||||
|
"id": 5,
|
||||||
|
"email": "test@test.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
product(update: $data, id: 9) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
full_name
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"email": "thed44ude@rug.com",
|
||||||
|
"full_name": "The Dude",
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now",
|
||||||
|
"product": {
|
||||||
|
"connect": {
|
||||||
|
"id": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
user(insert: $data) {
|
||||||
|
id
|
||||||
|
full_name
|
||||||
|
email
|
||||||
|
product {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
price
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"name": "Apple",
|
||||||
|
"price": 1.25,
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now",
|
||||||
|
"user": {
|
||||||
|
"connect": {
|
||||||
|
"id": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
product(insert: $data) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
full_name
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "Apple",
|
||||||
|
"price": 1.25,
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now",
|
||||||
|
"user": {
|
||||||
|
"connect": {
|
||||||
|
"id": 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Coconut",
|
||||||
|
"price": 2.25,
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now",
|
||||||
|
"user": {
|
||||||
|
"connect": {
|
||||||
|
"id": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
products(insert: $data) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
full_name
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "Apple",
|
||||||
|
"price": 1.25,
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Coconut",
|
||||||
|
"price": 2.25,
|
||||||
|
"created_at": "now",
|
||||||
|
"updated_at": "now"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
products(insert: $data) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
full_name
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"name": "Apple",
|
||||||
|
"price": 1.25,
|
||||||
|
"user": {
|
||||||
|
"connect": {
|
||||||
|
"id": 5,
|
||||||
|
"email": "test@test.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
product(update: $data, id: 9) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
full_name
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"name": "Apple",
|
||||||
|
"price": 1.25,
|
||||||
|
"user": {
|
||||||
|
"connect": {
|
||||||
|
"id": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
product(update: $data, id: 9) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
full_name
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"name": "Apple",
|
||||||
|
"price": 1.25,
|
||||||
|
"user": {
|
||||||
|
"disconnect": {
|
||||||
|
"id": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
product(update: $data, id: 9) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
user_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
variables {
|
||||||
|
"data": {
|
||||||
|
"name": "Apple",
|
||||||
|
"price": 1.25,
|
||||||
|
"user": {
|
||||||
|
"disconnect": {
|
||||||
|
"id": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation {
|
||||||
|
product(update: $data, id: 2) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
user_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
505
config/config.go
505
config/config.go
@ -1,505 +0,0 @@
|
|||||||
// Package config provides the config values needed for Super Graph
|
|
||||||
// For detailed documentation visit https://supergraph.dev
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gobuffalo/flect"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
LogLevelNone int = iota
|
|
||||||
LogLevelInfo
|
|
||||||
LogLevelWarn
|
|
||||||
LogLevelError
|
|
||||||
LogLevelDebug
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config struct holds the Super Graph config values
|
|
||||||
type Config struct {
|
|
||||||
Core `mapstructure:",squash"`
|
|
||||||
Serv `mapstructure:",squash"`
|
|
||||||
|
|
||||||
vi *viper.Viper
|
|
||||||
log *log.Logger
|
|
||||||
logLevel int
|
|
||||||
roles map[string]*Role
|
|
||||||
abacEnabled bool
|
|
||||||
valid bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Core struct contains core specific config value
|
|
||||||
type Core struct {
|
|
||||||
Env string
|
|
||||||
Production bool
|
|
||||||
LogLevel string `mapstructure:"log_level"`
|
|
||||||
SecretKey string `mapstructure:"secret_key"`
|
|
||||||
SetUserID bool `mapstructure:"set_user_id"`
|
|
||||||
Vars map[string]string `mapstructure:"variables"`
|
|
||||||
Blocklist []string
|
|
||||||
Tables []Table
|
|
||||||
RolesQuery string `mapstructure:"roles_query"`
|
|
||||||
Roles []Role
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serv struct contains config values used by the Super Graph service
|
|
||||||
type Serv struct {
|
|
||||||
AppName string `mapstructure:"app_name"`
|
|
||||||
HostPort string `mapstructure:"host_port"`
|
|
||||||
Host string
|
|
||||||
Port string
|
|
||||||
HTTPGZip bool `mapstructure:"http_compress"`
|
|
||||||
WebUI bool `mapstructure:"web_ui"`
|
|
||||||
EnableTracing bool `mapstructure:"enable_tracing"`
|
|
||||||
UseAllowList bool `mapstructure:"use_allow_list"`
|
|
||||||
WatchAndReload bool `mapstructure:"reload_on_config_change"`
|
|
||||||
AuthFailBlock bool `mapstructure:"auth_fail_block"`
|
|
||||||
SeedFile string `mapstructure:"seed_file"`
|
|
||||||
MigrationsPath string `mapstructure:"migrations_path"`
|
|
||||||
AllowedOrigins []string `mapstructure:"cors_allowed_origins"`
|
|
||||||
DebugCORS bool `mapstructure:"cors_debug"`
|
|
||||||
|
|
||||||
Inflections map[string]string
|
|
||||||
|
|
||||||
Auth Auth
|
|
||||||
Auths []Auth
|
|
||||||
|
|
||||||
DB struct {
|
|
||||||
Type string
|
|
||||||
Host string
|
|
||||||
Port uint16
|
|
||||||
DBName string
|
|
||||||
User string
|
|
||||||
Password string
|
|
||||||
Schema string
|
|
||||||
PoolSize int32 `mapstructure:"pool_size"`
|
|
||||||
MaxRetries int `mapstructure:"max_retries"`
|
|
||||||
PingTimeout time.Duration `mapstructure:"ping_timeout"`
|
|
||||||
} `mapstructure:"database"`
|
|
||||||
|
|
||||||
Actions []Action
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auth struct contains authentication related config values used by the Super Graph service
|
|
||||||
type Auth struct {
|
|
||||||
Name string
|
|
||||||
Type string
|
|
||||||
Cookie string
|
|
||||||
CredsInHeader bool `mapstructure:"creds_in_header"`
|
|
||||||
|
|
||||||
Rails struct {
|
|
||||||
Version string
|
|
||||||
SecretKeyBase string `mapstructure:"secret_key_base"`
|
|
||||||
URL string
|
|
||||||
Password string
|
|
||||||
MaxIdle int `mapstructure:"max_idle"`
|
|
||||||
MaxActive int `mapstructure:"max_active"`
|
|
||||||
Salt string
|
|
||||||
SignSalt string `mapstructure:"sign_salt"`
|
|
||||||
AuthSalt string `mapstructure:"auth_salt"`
|
|
||||||
}
|
|
||||||
|
|
||||||
JWT struct {
|
|
||||||
Provider string
|
|
||||||
Secret string
|
|
||||||
PubKeyFile string `mapstructure:"public_key_file"`
|
|
||||||
PubKeyType string `mapstructure:"public_key_type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
Header struct {
|
|
||||||
Name string
|
|
||||||
Value string
|
|
||||||
Exists bool
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Column struct defines a database column
|
|
||||||
type Column struct {
|
|
||||||
Name string
|
|
||||||
Type string
|
|
||||||
ForeignKey string `mapstructure:"related_to"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Table struct defines a database table
|
|
||||||
type Table struct {
|
|
||||||
Name string
|
|
||||||
Table string
|
|
||||||
Blocklist []string
|
|
||||||
Remotes []Remote
|
|
||||||
Columns []Column
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remote struct defines a remote API endpoint
|
|
||||||
type Remote struct {
|
|
||||||
Name string
|
|
||||||
ID string
|
|
||||||
Path string
|
|
||||||
URL string
|
|
||||||
Debug bool
|
|
||||||
PassHeaders []string `mapstructure:"pass_headers"`
|
|
||||||
SetHeaders []struct {
|
|
||||||
Name string
|
|
||||||
Value string
|
|
||||||
} `mapstructure:"set_headers"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query struct contains access control values for query operations
|
|
||||||
type Query struct {
|
|
||||||
Limit int
|
|
||||||
Filters []string
|
|
||||||
Columns []string
|
|
||||||
DisableFunctions bool `mapstructure:"disable_functions"`
|
|
||||||
Block bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert struct contains access control values for insert operations
|
|
||||||
type Insert struct {
|
|
||||||
Filters []string
|
|
||||||
Columns []string
|
|
||||||
Presets map[string]string
|
|
||||||
Block bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert struct contains access control values for update operations
|
|
||||||
type Update struct {
|
|
||||||
Filters []string
|
|
||||||
Columns []string
|
|
||||||
Presets map[string]string
|
|
||||||
Block bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete struct contains access control values for delete operations
|
|
||||||
type Delete struct {
|
|
||||||
Filters []string
|
|
||||||
Columns []string
|
|
||||||
Block bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoleTable struct contains role specific access control values for a database table
|
|
||||||
type RoleTable struct {
|
|
||||||
Name string
|
|
||||||
|
|
||||||
Query Query
|
|
||||||
Insert Insert
|
|
||||||
Update Update
|
|
||||||
Delete Delete
|
|
||||||
}
|
|
||||||
|
|
||||||
// Role struct contains role specific access control values for for all database tables
|
|
||||||
type Role struct {
|
|
||||||
Name string
|
|
||||||
Match string
|
|
||||||
Tables []RoleTable
|
|
||||||
tablesMap map[string]*RoleTable
|
|
||||||
}
|
|
||||||
|
|
||||||
// Action struct contains config values for a Super Graph service action
|
|
||||||
type Action struct {
|
|
||||||
Name string
|
|
||||||
SQL string
|
|
||||||
AuthName string `mapstructure:"auth_name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConfig 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 NewConfig(path string) (*Config, error) {
|
|
||||||
return NewConfigWithLogger(path, log.New(os.Stdout, "", 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConfigWithLogger 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 NewConfigWithLogger(path string, logger *log.Logger) (*Config, error) {
|
|
||||||
vi := newViper(path, GetConfigName())
|
|
||||||
|
|
||||||
if err := vi.ReadInConfig(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
inherits := vi.GetString("inherits")
|
|
||||||
|
|
||||||
if len(inherits) != 0 {
|
|
||||||
vi = newViper(path, inherits)
|
|
||||||
|
|
||||||
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"))
|
|
||||||
}
|
|
||||||
|
|
||||||
vi.SetConfigName(GetConfigName())
|
|
||||||
|
|
||||||
if err := vi.MergeInConfig(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &Config{log: logger, vi: vi}
|
|
||||||
|
|
||||||
if err := vi.Unmarshal(&c); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode config, %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.init(); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to initialize config: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConfigFrom function initializes a Config struct that you manually created
|
|
||||||
// so it can be used by Super Graph
|
|
||||||
func NewConfigFrom(c *Config, configPath string, logger *log.Logger) (*Config, error) {
|
|
||||||
c.vi = newViper(configPath, GetConfigName())
|
|
||||||
c.log = logger
|
|
||||||
if err := c.init(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newViper(configPath, filename string) *viper.Viper {
|
|
||||||
vi := viper.New()
|
|
||||||
|
|
||||||
vi.SetEnvPrefix("SG")
|
|
||||||
vi.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
|
||||||
vi.AutomaticEnv()
|
|
||||||
|
|
||||||
vi.SetConfigName(filename)
|
|
||||||
vi.AddConfigPath(configPath)
|
|
||||||
vi.AddConfigPath("./config")
|
|
||||||
|
|
||||||
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("seed_file", "seed.js")
|
|
||||||
|
|
||||||
vi.SetDefault("database.type", "postgres")
|
|
||||||
vi.SetDefault("database.host", "localhost")
|
|
||||||
vi.SetDefault("database.port", 5432)
|
|
||||||
vi.SetDefault("database.user", "postgres")
|
|
||||||
vi.SetDefault("database.schema", "public")
|
|
||||||
|
|
||||||
vi.SetDefault("env", "development")
|
|
||||||
|
|
||||||
vi.BindEnv("env", "GO_ENV") //nolint: errcheck
|
|
||||||
vi.BindEnv("host", "HOST") //nolint: errcheck
|
|
||||||
vi.BindEnv("port", "PORT") //nolint: errcheck
|
|
||||||
|
|
||||||
vi.SetDefault("auth.rails.max_idle", 80)
|
|
||||||
vi.SetDefault("auth.rails.max_active", 12000)
|
|
||||||
|
|
||||||
return vi
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) init() error {
|
|
||||||
switch c.Core.LogLevel {
|
|
||||||
case "debug":
|
|
||||||
c.logLevel = LogLevelDebug
|
|
||||||
case "error":
|
|
||||||
c.logLevel = LogLevelError
|
|
||||||
case "warn":
|
|
||||||
c.logLevel = LogLevelWarn
|
|
||||||
case "info":
|
|
||||||
c.logLevel = LogLevelInfo
|
|
||||||
default:
|
|
||||||
c.logLevel = LogLevelNone
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.UseAllowList {
|
|
||||||
c.Production = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range c.Inflections {
|
|
||||||
flect.AddPlural(k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tables: Validate and sanitize
|
|
||||||
tm := make(map[string]struct{})
|
|
||||||
|
|
||||||
for i := 0; i < len(c.Tables); i++ {
|
|
||||||
t := &c.Tables[i]
|
|
||||||
t.Name = flect.Pluralize(strings.ToLower(t.Name))
|
|
||||||
|
|
||||||
if _, ok := tm[t.Name]; ok {
|
|
||||||
c.Tables = append(c.Tables[:i], c.Tables[i+1:]...)
|
|
||||||
c.log.Printf("WRN duplicate table found: %s", t.Name)
|
|
||||||
}
|
|
||||||
tm[t.Name] = struct{}{}
|
|
||||||
|
|
||||||
t.Table = flect.Pluralize(strings.ToLower(t.Table))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variables: Validate and sanitize
|
|
||||||
for k, v := range c.Vars {
|
|
||||||
c.Vars[k] = sanitize(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Roles: validate and sanitize
|
|
||||||
c.RolesQuery = sanitize(c.RolesQuery)
|
|
||||||
c.roles = make(map[string]*Role)
|
|
||||||
|
|
||||||
for i := 0; i < len(c.Roles); i++ {
|
|
||||||
r := &c.Roles[i]
|
|
||||||
r.Name = strings.ToLower(r.Name)
|
|
||||||
|
|
||||||
if _, ok := c.roles[r.Name]; ok {
|
|
||||||
c.Roles = append(c.Roles[:i], c.Roles[i+1:]...)
|
|
||||||
c.log.Printf("WRN duplicate role found: %s", r.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Match = sanitize(r.Match)
|
|
||||||
r.tablesMap = make(map[string]*RoleTable)
|
|
||||||
|
|
||||||
for n, table := range r.Tables {
|
|
||||||
r.tablesMap[table.Name] = &r.Tables[n]
|
|
||||||
}
|
|
||||||
|
|
||||||
c.roles[r.Name] = r
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := c.roles["user"]; !ok {
|
|
||||||
u := Role{Name: "user"}
|
|
||||||
c.Roles = append(c.Roles, u)
|
|
||||||
c.roles["user"] = &u
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := c.roles["anon"]; !ok {
|
|
||||||
c.log.Printf("WRN unauthenticated requests will be blocked. no role 'anon' defined")
|
|
||||||
c.AuthFailBlock = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.RolesQuery) == 0 {
|
|
||||||
c.log.Printf("WRN roles_query not defined: attribute based access control disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.RolesQuery) == 0 {
|
|
||||||
c.abacEnabled = false
|
|
||||||
} else {
|
|
||||||
switch len(c.Roles) {
|
|
||||||
case 0, 1:
|
|
||||||
c.abacEnabled = false
|
|
||||||
case 2:
|
|
||||||
_, ok1 := c.roles["anon"]
|
|
||||||
_, ok2 := c.roles["user"]
|
|
||||||
c.abacEnabled = !(ok1 && ok2)
|
|
||||||
default:
|
|
||||||
c.abacEnabled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auths: validate and sanitize
|
|
||||||
am := make(map[string]struct{})
|
|
||||||
|
|
||||||
for i := 0; i < len(c.Auths); i++ {
|
|
||||||
a := &c.Auths[i]
|
|
||||||
a.Name = strings.ToLower(a.Name)
|
|
||||||
|
|
||||||
if _, ok := am[a.Name]; ok {
|
|
||||||
c.Auths = append(c.Auths[:i], c.Auths[i+1:]...)
|
|
||||||
c.log.Printf("WRN duplicate auth found: %s", a.Name)
|
|
||||||
}
|
|
||||||
am[a.Name] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Actions: validate and sanitize
|
|
||||||
axm := make(map[string]struct{})
|
|
||||||
|
|
||||||
for i := 0; i < len(c.Actions); i++ {
|
|
||||||
a := &c.Actions[i]
|
|
||||||
a.Name = strings.ToLower(a.Name)
|
|
||||||
a.AuthName = strings.ToLower(a.AuthName)
|
|
||||||
|
|
||||||
if _, ok := axm[a.Name]; ok {
|
|
||||||
c.Actions = append(c.Actions[:i], c.Actions[i+1:]...)
|
|
||||||
c.log.Printf("WRN duplicate action found: %s", a.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := am[a.AuthName]; !ok {
|
|
||||||
c.Actions = append(c.Actions[:i], c.Actions[i+1:]...)
|
|
||||||
c.log.Printf("WRN invalid auth_name '%s' for auth: %s", a.AuthName, a.Name)
|
|
||||||
}
|
|
||||||
axm[a.Name] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.valid = true
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDBTableAliases function returns a map with database tables as keys
|
|
||||||
// and a list of aliases as values
|
|
||||||
func (c *Config) GetDBTableAliases() map[string][]string {
|
|
||||||
m := make(map[string][]string, len(c.Tables))
|
|
||||||
|
|
||||||
for i := range c.Tables {
|
|
||||||
t := c.Tables[i]
|
|
||||||
|
|
||||||
if len(t.Table) == 0 || len(t.Columns) != 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
m[t.Table] = append(m[t.Table], t.Name)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsABACEnabled function returns true if attribute based access control is enabled
|
|
||||||
func (c *Config) IsABACEnabled() bool {
|
|
||||||
return c.abacEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAnonRoleDefined function returns true if the config has configuration for the `anon` role
|
|
||||||
func (c *Config) IsAnonRoleDefined() bool {
|
|
||||||
_, ok := c.roles["anon"]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRole function returns returns the Role struct by name
|
|
||||||
func (c *Config) GetRole(name string) *Role {
|
|
||||||
role := c.roles[name]
|
|
||||||
return role
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConfigPathUsed function returns the path to the current config file (excluding filename)
|
|
||||||
func (c *Config) ConfigPathUsed() string {
|
|
||||||
return path.Dir(c.vi.ConfigFileUsed())
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteConfigAs function writes the config to a file
|
|
||||||
// Format defined by extension (eg: .yml, .json)
|
|
||||||
func (c *Config) WriteConfigAs(fname string) error {
|
|
||||||
return c.vi.WriteConfigAs(fname)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log function returns the logger
|
|
||||||
func (c *Config) Log() *log.Logger {
|
|
||||||
return c.log
|
|
||||||
}
|
|
||||||
|
|
||||||
// LogLevel function returns the log level
|
|
||||||
func (c *Config) LogLevel() int {
|
|
||||||
return c.logLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsValid function returns true if the Config struct is initialized and valid
|
|
||||||
func (c *Config) IsValid() bool {
|
|
||||||
return c.valid
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTable function returns the RoleTable struct for a Role by table name
|
|
||||||
func (r *Role) GetTable(name string) *RoleTable {
|
|
||||||
table := r.tablesMap[name]
|
|
||||||
return table
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInitConf(t *testing.T) {
|
|
||||||
_, err := NewConfig("../examples/rails-app/config/supergraph")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
226
config/dev.yml
Normal file
226
config/dev.yml
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
app_name: "Super Graph Development"
|
||||||
|
host_port: 0.0.0.0:8080
|
||||||
|
web_ui: true
|
||||||
|
|
||||||
|
# debug, error, warn, info, none
|
||||||
|
log_level: "debug"
|
||||||
|
|
||||||
|
# enable or disable http compression (uses gzip)
|
||||||
|
http_compress: 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: false
|
||||||
|
|
||||||
|
# Throw a 401 on auth failure for queries that need auth
|
||||||
|
auth_fail_block: false
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Secret key for general encryption operations like
|
||||||
|
# encrypting the cursor data
|
||||||
|
secret_key: supercalifajalistics
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
# An origin may contain a wildcard (*) to replace 0 or more
|
||||||
|
# characters (i.e.: http://*.domain.com).
|
||||||
|
cors_allowed_origins: ["*"]
|
||||||
|
|
||||||
|
# Debug Cross Origin Resource Sharing requests
|
||||||
|
cors_debug: true
|
||||||
|
|
||||||
|
# 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 for testing.
|
||||||
|
# Disable in production
|
||||||
|
creds_in_header: true
|
||||||
|
|
||||||
|
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_development
|
||||||
|
user: postgres
|
||||||
|
password: postgres
|
||||||
|
|
||||||
|
#schema: "public"
|
||||||
|
#pool_size: 10
|
||||||
|
#max_retries: 0
|
||||||
|
#log_level: "debug"
|
||||||
|
|
||||||
|
# Set session variable "user.id" to the user id
|
||||||
|
# Enable this if you need the user id in triggers, etc
|
||||||
|
set_user_id: false
|
||||||
|
|
||||||
|
# database ping timeout is used for db health checking
|
||||||
|
ping_timeout: 1m
|
||||||
|
|
||||||
|
# Define additional variables here to be used with filters
|
||||||
|
variables:
|
||||||
|
admin_account_id: "5"
|
||||||
|
|
||||||
|
# Field and table names that you wish to block
|
||||||
|
blocklist:
|
||||||
|
- ar_internal_metadata
|
||||||
|
- schema_migrations
|
||||||
|
- secret
|
||||||
|
- password
|
||||||
|
- encrypted
|
||||||
|
- token
|
||||||
|
|
||||||
|
tables:
|
||||||
|
- name: customers
|
||||||
|
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
|
||||||
|
|
||||||
|
- name: deals
|
||||||
|
table: products
|
||||||
|
|
||||||
|
- name: users
|
||||||
|
columns:
|
||||||
|
- name: email
|
||||||
|
related_to: products.name
|
||||||
|
|
||||||
|
|
||||||
|
roles_query: "SELECT * FROM users WHERE id = $user_id"
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- name: anon
|
||||||
|
tables:
|
||||||
|
- name: products
|
||||||
|
query:
|
||||||
|
limit: 10
|
||||||
|
columns: ["id", "name", "description" ]
|
||||||
|
aggregation: false
|
||||||
|
|
||||||
|
insert:
|
||||||
|
block: false
|
||||||
|
|
||||||
|
update:
|
||||||
|
block: false
|
||||||
|
|
||||||
|
delete:
|
||||||
|
block: false
|
||||||
|
|
||||||
|
- name: deals
|
||||||
|
query:
|
||||||
|
limit: 3
|
||||||
|
aggregation: false
|
||||||
|
|
||||||
|
- name: purchases
|
||||||
|
query:
|
||||||
|
limit: 3
|
||||||
|
aggregation: false
|
||||||
|
|
||||||
|
- name: user
|
||||||
|
tables:
|
||||||
|
- name: users
|
||||||
|
query:
|
||||||
|
filters: ["{ id: { _eq: $user_id } }"]
|
||||||
|
|
||||||
|
- name: products
|
||||||
|
query:
|
||||||
|
limit: 50
|
||||||
|
filters: ["{ user_id: { eq: $user_id } }"]
|
||||||
|
disable_functions: false
|
||||||
|
|
||||||
|
insert:
|
||||||
|
filters: ["{ user_id: { eq: $user_id } }"]
|
||||||
|
presets:
|
||||||
|
- user_id: "$user_id"
|
||||||
|
- created_at: "now"
|
||||||
|
- updated_at: "now"
|
||||||
|
|
||||||
|
update:
|
||||||
|
filters: ["{ user_id: { eq: $user_id } }"]
|
||||||
|
columns:
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
presets:
|
||||||
|
- updated_at: "now"
|
||||||
|
|
||||||
|
delete:
|
||||||
|
block: true
|
||||||
|
|
||||||
|
- name: admin
|
||||||
|
match: id = 1000
|
||||||
|
tables:
|
||||||
|
- name: users
|
||||||
|
filters: []
|
67
config/prod.yml
Normal file
67
config/prod.yml
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# Inherit config from this other config file
|
||||||
|
# so I only need to overwrite some values
|
||||||
|
inherits: dev
|
||||||
|
|
||||||
|
app_name: "Super Graph Production"
|
||||||
|
host_port: 0.0.0.0:8080
|
||||||
|
web_ui: false
|
||||||
|
|
||||||
|
# debug, error, warn, info, none
|
||||||
|
log_level: "info"
|
||||||
|
|
||||||
|
# enable or disable http compression (uses gzip)
|
||||||
|
http_compress: 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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Secret key for general encryption operations like
|
||||||
|
# encrypting the cursor data
|
||||||
|
# secret_key: supercalifajalistics
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
database:
|
||||||
|
type: postgres
|
||||||
|
host: db
|
||||||
|
port: 5432
|
||||||
|
dbname: app_production
|
||||||
|
user: postgres
|
||||||
|
password: postgres
|
||||||
|
#pool_size: 10
|
||||||
|
#max_retries: 0
|
||||||
|
#log_level: "debug"
|
||||||
|
|
||||||
|
# Set session variable "user.id" to the user id
|
||||||
|
# Enable this if you need the user id in triggers, etc
|
||||||
|
set_user_id: false
|
||||||
|
|
||||||
|
# database ping timeout is used for db health checking
|
||||||
|
ping_timeout: 5m
|
116
config/seed.js
Normal file
116
config/seed.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
var user_count = 10
|
||||||
|
customer_count = 100
|
||||||
|
product_count = 50
|
||||||
|
purchase_count = 100
|
||||||
|
|
||||||
|
var users = []
|
||||||
|
customers = []
|
||||||
|
products = []
|
||||||
|
|
||||||
|
for (i = 0; i < user_count; i++) {
|
||||||
|
var pwd = fake.password()
|
||||||
|
var data = {
|
||||||
|
full_name: fake.name(),
|
||||||
|
avatar: fake.avatar_url(200),
|
||||||
|
phone: fake.phone(),
|
||||||
|
email: fake.email(),
|
||||||
|
password: pwd,
|
||||||
|
password_confirmation: pwd,
|
||||||
|
created_at: "now",
|
||||||
|
updated_at: "now"
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = graphql(" \
|
||||||
|
mutation { \
|
||||||
|
user(insert: $data) { \
|
||||||
|
id \
|
||||||
|
} \
|
||||||
|
}", { data: data })
|
||||||
|
|
||||||
|
users.push(res.user)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < product_count; i++) {
|
||||||
|
var n = Math.floor(Math.random() * users.length)
|
||||||
|
var user = users[n]
|
||||||
|
|
||||||
|
var desc = [
|
||||||
|
fake.beer_style(),
|
||||||
|
fake.beer_hop(),
|
||||||
|
fake.beer_yeast(),
|
||||||
|
fake.beer_ibu(),
|
||||||
|
fake.beer_alcohol(),
|
||||||
|
fake.beer_blg(),
|
||||||
|
].join(", ")
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
name: fake.beer_name(),
|
||||||
|
description: desc,
|
||||||
|
price: fake.price()
|
||||||
|
//user_id: user.id,
|
||||||
|
//created_at: "now",
|
||||||
|
//updated_at: "now"
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = graphql(" \
|
||||||
|
mutation { \
|
||||||
|
product(insert: $data) { \
|
||||||
|
id \
|
||||||
|
} \
|
||||||
|
}", { data: data }, {
|
||||||
|
user_id: 5
|
||||||
|
})
|
||||||
|
products.push(res.product)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < customer_count; i++) {
|
||||||
|
var pwd = fake.password()
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
stripe_id: "CUS-" + fake.uuid(),
|
||||||
|
full_name: fake.name(),
|
||||||
|
phone: fake.phone(),
|
||||||
|
email: fake.email(),
|
||||||
|
password: pwd,
|
||||||
|
password_confirmation: pwd,
|
||||||
|
created_at: "now",
|
||||||
|
updated_at: "now"
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = graphql(" \
|
||||||
|
mutation { \
|
||||||
|
customer(insert: $data) { \
|
||||||
|
id \
|
||||||
|
} \
|
||||||
|
}", { data: data })
|
||||||
|
customers.push(res.customer)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < purchase_count; i++) {
|
||||||
|
var sale_type = fake.rand_string(["rented", "bought"])
|
||||||
|
|
||||||
|
if (sale_type === "rented") {
|
||||||
|
var due_date = fake.date()
|
||||||
|
var returned = fake.date()
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
customer_id: customers[Math.floor(Math.random() * customer_count)].id,
|
||||||
|
product_id: products[Math.floor(Math.random() * product_count)].id,
|
||||||
|
sale_type: sale_type,
|
||||||
|
quantity: Math.floor(Math.random() * 10),
|
||||||
|
due_date: due_date,
|
||||||
|
returned: returned,
|
||||||
|
created_at: "now",
|
||||||
|
updated_at: "now"
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = graphql(" \
|
||||||
|
mutation { \
|
||||||
|
purchase(insert: $data) { \
|
||||||
|
id \
|
||||||
|
} \
|
||||||
|
}", { data: data })
|
||||||
|
|
||||||
|
console.log(res)
|
||||||
|
}
|
@ -1,52 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
varRe1 = regexp.MustCompile(`(?mi)\$([a-zA-Z0-9_.]+)`)
|
|
||||||
varRe2 = regexp.MustCompile(`\{\{([a-zA-Z0-9_.]+)\}\}`)
|
|
||||||
)
|
|
||||||
|
|
||||||
func sanitize(s string) string {
|
|
||||||
s0 := varRe1.ReplaceAllString(s, `{{$1}}`)
|
|
||||||
|
|
||||||
s1 := strings.Map(func(r rune) rune {
|
|
||||||
if unicode.IsSpace(r) {
|
|
||||||
return ' '
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}, s0)
|
|
||||||
|
|
||||||
return varRe2.ReplaceAllStringFunc(s1, func(m string) string {
|
|
||||||
return strings.ToLower(m)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetConfigName() string {
|
|
||||||
if len(os.Getenv("GO_ENV")) == 0 {
|
|
||||||
return "dev"
|
|
||||||
}
|
|
||||||
|
|
||||||
ge := strings.ToLower(os.Getenv("GO_ENV"))
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(ge, "pro"):
|
|
||||||
return "prod"
|
|
||||||
|
|
||||||
case strings.HasPrefix(ge, "sta"):
|
|
||||||
return "stage"
|
|
||||||
|
|
||||||
case strings.HasPrefix(ge, "tes"):
|
|
||||||
return "test"
|
|
||||||
|
|
||||||
case strings.HasPrefix(ge, "dev"):
|
|
||||||
return "dev"
|
|
||||||
}
|
|
||||||
|
|
||||||
return ge
|
|
||||||
}
|
|
55
core/api.go
55
core/api.go
@ -9,7 +9,6 @@
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
"github.com/dosco/super-graph/config"
|
|
||||||
"github.com/dosco/super-graph/core"
|
"github.com/dosco/super-graph/core"
|
||||||
_ "github.com/jackc/pgx/v4/stdlib"
|
_ "github.com/jackc/pgx/v4/stdlib"
|
||||||
)
|
)
|
||||||
@ -20,7 +19,7 @@
|
|||||||
log.Fatalf(err)
|
log.Fatalf(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
conf, err := config.NewConfig("./config")
|
conf, err := core.ReadInConfig("./config/dev.yml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf(err)
|
log.Fatalf(err)
|
||||||
}
|
}
|
||||||
@ -53,10 +52,9 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
_log "log"
|
||||||
"log"
|
"os"
|
||||||
|
|
||||||
"github.com/dosco/super-graph/config"
|
|
||||||
"github.com/dosco/super-graph/core/internal/allow"
|
"github.com/dosco/super-graph/core/internal/allow"
|
||||||
"github.com/dosco/super-graph/core/internal/crypto"
|
"github.com/dosco/super-graph/core/internal/crypto"
|
||||||
"github.com/dosco/super-graph/core/internal/psql"
|
"github.com/dosco/super-graph/core/internal/psql"
|
||||||
@ -80,36 +78,33 @@ const (
|
|||||||
// SuperGraph struct is an instance of the Super Graph engine it holds all the required information like
|
// SuperGraph struct is an instance of the Super Graph engine it holds all the required information like
|
||||||
// datase schemas, relationships, etc that the GraphQL to SQL compiler would need to do it's job.
|
// datase schemas, relationships, etc that the GraphQL to SQL compiler would need to do it's job.
|
||||||
type SuperGraph struct {
|
type SuperGraph struct {
|
||||||
conf *config.Config
|
conf *Config
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
schema *psql.DBSchema
|
log *_log.Logger
|
||||||
allowList *allow.List
|
schema *psql.DBSchema
|
||||||
encKey [32]byte
|
allowList *allow.List
|
||||||
prepared map[string]*preparedItem
|
encKey [32]byte
|
||||||
getRole *sql.Stmt
|
prepared map[string]*preparedItem
|
||||||
qc *qcode.Compiler
|
roles map[string]*Role
|
||||||
pc *psql.Compiler
|
getRole *sql.Stmt
|
||||||
}
|
rmap map[uint64]*resolvFn
|
||||||
|
abacEnabled bool
|
||||||
// NewConfig functions initializes config using a config.Core struct
|
anonExists bool
|
||||||
func NewConfig(core config.Core, configPath string, logger *log.Logger) (*config.Config, error) {
|
qc *qcode.Compiler
|
||||||
c, err := config.NewConfigFrom(&config.Config{Core: core}, configPath, logger)
|
pc *psql.Compiler
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSuperGraph creates the SuperGraph struct, this involves querying the database to learn its
|
// NewSuperGraph creates the SuperGraph struct, this involves querying the database to learn its
|
||||||
// schemas and relationships
|
// schemas and relationships
|
||||||
func NewSuperGraph(conf *config.Config, db *sql.DB) (*SuperGraph, error) {
|
func NewSuperGraph(conf *Config, db *sql.DB) (*SuperGraph, error) {
|
||||||
if !conf.IsValid() {
|
|
||||||
return nil, fmt.Errorf("invalid config")
|
|
||||||
}
|
|
||||||
|
|
||||||
sg := &SuperGraph{
|
sg := &SuperGraph{
|
||||||
conf: conf,
|
conf: conf,
|
||||||
db: db,
|
db: db,
|
||||||
|
log: _log.New(os.Stdout, "", 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sg.initConfig(); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := sg.initCompilers(); err != nil {
|
if err := sg.initCompilers(); err != nil {
|
||||||
@ -124,6 +119,10 @@ func NewSuperGraph(conf *config.Config, db *sql.DB) (*SuperGraph, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := sg.initResolvers(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if len(conf.SecretKey) != 0 {
|
if len(conf.SecretKey) != 0 {
|
||||||
sk := sha256.Sum256([]byte(conf.SecretKey))
|
sk := sha256.Sum256([]byte(conf.SecretKey))
|
||||||
conf.SecretKey = ""
|
conf.SecretKey = ""
|
||||||
|
@ -7,13 +7,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/dosco/super-graph/config"
|
|
||||||
"github.com/dosco/super-graph/core/internal/psql"
|
"github.com/dosco/super-graph/core/internal/psql"
|
||||||
"github.com/dosco/super-graph/core/internal/qcode"
|
"github.com/dosco/super-graph/core/internal/qcode"
|
||||||
)
|
)
|
||||||
|
|
||||||
type stmt struct {
|
type stmt struct {
|
||||||
role *config.Role
|
role *Role
|
||||||
qc *qcode.QCode
|
qc *qcode.QCode
|
||||||
skipped uint32
|
skipped uint32
|
||||||
sql string
|
sql string
|
||||||
@ -29,7 +28,7 @@ func (sg *SuperGraph) buildStmt(qt qcode.QType, query, vars []byte, role string)
|
|||||||
return sg.buildRoleStmt(query, vars, "anon")
|
return sg.buildRoleStmt(query, vars, "anon")
|
||||||
}
|
}
|
||||||
|
|
||||||
if sg.conf.IsABACEnabled() {
|
if sg.abacEnabled {
|
||||||
return sg.buildMultiStmt(query, vars)
|
return sg.buildMultiStmt(query, vars)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,8 +40,8 @@ func (sg *SuperGraph) buildStmt(qt qcode.QType, query, vars []byte, role string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sg *SuperGraph) buildRoleStmt(query, vars []byte, role string) ([]stmt, error) {
|
func (sg *SuperGraph) buildRoleStmt(query, vars []byte, role string) ([]stmt, error) {
|
||||||
ro := sg.conf.GetRole(role)
|
ro, ok := sg.roles[role]
|
||||||
if ro == nil {
|
if !ok {
|
||||||
return nil, fmt.Errorf(`roles '%s' not defined in c.sg.config`, role)
|
return nil, fmt.Errorf(`roles '%s' not defined in c.sg.config`, role)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +167,7 @@ func (sg *SuperGraph) renderUserQuery(stmts []stmt) (string, error) {
|
|||||||
return w.String(), nil
|
return w.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sg *SuperGraph) hasTablesWithConfig(qc *qcode.QCode, role *config.Role) bool {
|
func (sg *SuperGraph) hasTablesWithConfig(qc *qcode.QCode, role *Role) bool {
|
||||||
for _, id := range qc.Roots {
|
for _, id := range qc.Roots {
|
||||||
t, err := sg.schema.GetTable(qc.Selects[id].Name)
|
t, err := sg.schema.GetTable(qc.Selects[id].Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
286
core/config.go
286
core/config.go
@ -2,164 +2,162 @@ package core
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/dosco/super-graph/config"
|
"github.com/spf13/viper"
|
||||||
"github.com/dosco/super-graph/core/internal/psql"
|
|
||||||
"github.com/dosco/super-graph/core/internal/qcode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func addTables(c *config.Config, di *psql.DBInfo) error {
|
// Core struct contains core specific config value
|
||||||
for _, t := range c.Tables {
|
type Config struct {
|
||||||
if len(t.Table) == 0 || len(t.Columns) == 0 {
|
SecretKey string `mapstructure:"secret_key"`
|
||||||
continue
|
UseAllowList bool `mapstructure:"use_allow_list"`
|
||||||
|
AllowListFile string `mapstructure:"allow_list_file"`
|
||||||
|
SetUserID bool `mapstructure:"set_user_id"`
|
||||||
|
Vars map[string]string `mapstructure:"variables"`
|
||||||
|
Blocklist []string
|
||||||
|
Tables []Table
|
||||||
|
RolesQuery string `mapstructure:"roles_query"`
|
||||||
|
Roles []Role
|
||||||
|
Inflections map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Table struct defines a database table
|
||||||
|
type Table struct {
|
||||||
|
Name string
|
||||||
|
Table string
|
||||||
|
Blocklist []string
|
||||||
|
Remotes []Remote
|
||||||
|
Columns []Column
|
||||||
|
}
|
||||||
|
|
||||||
|
// Column struct defines a database column
|
||||||
|
type Column struct {
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
ForeignKey string `mapstructure:"related_to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remote struct defines a remote API endpoint
|
||||||
|
type Remote struct {
|
||||||
|
Name string
|
||||||
|
ID string
|
||||||
|
Path string
|
||||||
|
URL string
|
||||||
|
Debug bool
|
||||||
|
PassHeaders []string `mapstructure:"pass_headers"`
|
||||||
|
SetHeaders []struct {
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
} `mapstructure:"set_headers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Role struct contains role specific access control values for for all database tables
|
||||||
|
type Role struct {
|
||||||
|
Name string
|
||||||
|
Match string
|
||||||
|
Tables []RoleTable
|
||||||
|
tm map[string]*RoleTable
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoleTable struct contains role specific access control values for a database table
|
||||||
|
type RoleTable struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Query Query
|
||||||
|
Insert Insert
|
||||||
|
Update Update
|
||||||
|
Delete Delete
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query struct contains access control values for query operations
|
||||||
|
type Query struct {
|
||||||
|
Limit int
|
||||||
|
Filters []string
|
||||||
|
Columns []string
|
||||||
|
DisableFunctions bool `mapstructure:"disable_functions"`
|
||||||
|
Block bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert struct contains access control values for insert operations
|
||||||
|
type Insert struct {
|
||||||
|
Filters []string
|
||||||
|
Columns []string
|
||||||
|
Presets map[string]string
|
||||||
|
Block bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert struct contains access control values for update operations
|
||||||
|
type Update struct {
|
||||||
|
Filters []string
|
||||||
|
Columns []string
|
||||||
|
Presets map[string]string
|
||||||
|
Block bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete struct contains access control values for delete operations
|
||||||
|
type Delete struct {
|
||||||
|
Filters []string
|
||||||
|
Columns []string
|
||||||
|
Block bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
if err := vi.ReadInConfig(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
inherits := vi.GetString("inherits")
|
||||||
|
|
||||||
|
if len(inherits) != 0 {
|
||||||
|
vi = newViper(cpath, inherits)
|
||||||
|
|
||||||
|
if err := vi.ReadInConfig(); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := addTable(di, t.Columns, t); err != nil {
|
|
||||||
return err
|
if vi.IsSet("inherits") {
|
||||||
|
return nil, fmt.Errorf("inherited config (%s) cannot itself inherit (%s)",
|
||||||
|
inherits,
|
||||||
|
vi.GetString("inherits"))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addTable(di *psql.DBInfo, cols []config.Column, t config.Table) error {
|
vi.SetConfigName(cfile)
|
||||||
bc, ok := di.GetColumn(t.Table, t.Name)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"Column '%s' not found on table '%s'",
|
|
||||||
t.Name, t.Table)
|
|
||||||
}
|
|
||||||
|
|
||||||
if bc.Type != "json" && bc.Type != "jsonb" {
|
if err := vi.MergeInConfig(); err != nil {
|
||||||
return fmt.Errorf(
|
return nil, err
|
||||||
"Column '%s' in table '%s' is of type '%s'. Only JSON or JSONB is valid",
|
|
||||||
t.Name, t.Table, bc.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
table := psql.DBTable{
|
|
||||||
Name: t.Name,
|
|
||||||
Key: strings.ToLower(t.Name),
|
|
||||||
Type: bc.Type,
|
|
||||||
}
|
|
||||||
|
|
||||||
columns := make([]psql.DBColumn, 0, len(cols))
|
|
||||||
|
|
||||||
for i := range cols {
|
|
||||||
c := cols[i]
|
|
||||||
columns = append(columns, psql.DBColumn{
|
|
||||||
Name: c.Name,
|
|
||||||
Key: strings.ToLower(c.Name),
|
|
||||||
Type: c.Type,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
di.AddTable(table, columns)
|
|
||||||
bc.FKeyTable = t.Name
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addForeignKeys(c *config.Config, di *psql.DBInfo) error {
|
|
||||||
for _, t := range c.Tables {
|
|
||||||
for _, c := range t.Columns {
|
|
||||||
if len(c.ForeignKey) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := addForeignKey(di, c, t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addForeignKey(di *psql.DBInfo, c config.Column, t config.Table) error {
|
|
||||||
c1, ok := di.GetColumn(t.Name, c.Name)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"Invalid table '%s' or column '%s' in config.Config",
|
|
||||||
t.Name, c.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
v := strings.SplitN(c.ForeignKey, ".", 2)
|
|
||||||
if len(v) != 2 {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"Invalid foreign_key in config.Config for table '%s' and column '%s",
|
|
||||||
t.Name, c.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fkt, fkc := v[0], v[1]
|
|
||||||
c2, ok := di.GetColumn(fkt, fkc)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"Invalid foreign_key in config.Config for table '%s' and column '%s",
|
|
||||||
t.Name, c.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
c1.FKeyTable = fkt
|
|
||||||
c1.FKeyColID = []int16{c2.ID}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addRoles(c *config.Config, qc *qcode.Compiler) error {
|
|
||||||
for _, r := range c.Roles {
|
|
||||||
for _, t := range r.Tables {
|
|
||||||
if err := addRole(qc, r, t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
c := &Config{}
|
||||||
|
|
||||||
|
if err := vi.Unmarshal(&c); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode config, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.AllowListFile) == 0 {
|
||||||
|
c.AllowListFile = path.Join(cpath, "allow.list")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addRole(qc *qcode.Compiler, r config.Role, t config.RoleTable) error {
|
func newViper(configPath, configFile string) *viper.Viper {
|
||||||
blockFilter := []string{"false"}
|
vi := viper.New()
|
||||||
|
|
||||||
query := qcode.QueryConfig{
|
vi.SetEnvPrefix("SG")
|
||||||
Limit: t.Query.Limit,
|
vi.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
Filters: t.Query.Filters,
|
vi.AutomaticEnv()
|
||||||
Columns: t.Query.Columns,
|
|
||||||
DisableFunctions: t.Query.DisableFunctions,
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.Query.Block {
|
vi.SetConfigName(configFile)
|
||||||
query.Filters = blockFilter
|
vi.AddConfigPath(configPath)
|
||||||
}
|
vi.AddConfigPath("./config")
|
||||||
|
|
||||||
insert := qcode.InsertConfig{
|
return vi
|
||||||
Filters: t.Insert.Filters,
|
|
||||||
Columns: t.Insert.Columns,
|
|
||||||
Presets: t.Insert.Presets,
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.Insert.Block {
|
|
||||||
insert.Filters = blockFilter
|
|
||||||
}
|
|
||||||
|
|
||||||
update := qcode.UpdateConfig{
|
|
||||||
Filters: t.Update.Filters,
|
|
||||||
Columns: t.Update.Columns,
|
|
||||||
Presets: t.Update.Presets,
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.Update.Block {
|
|
||||||
update.Filters = blockFilter
|
|
||||||
}
|
|
||||||
|
|
||||||
delete := qcode.DeleteConfig{
|
|
||||||
Filters: t.Delete.Filters,
|
|
||||||
Columns: t.Delete.Columns,
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.Delete.Block {
|
|
||||||
delete.Filters = blockFilter
|
|
||||||
}
|
|
||||||
|
|
||||||
return qc.AddRole(r.Name, t.Name, qcode.TRConfig{
|
|
||||||
Query: query,
|
|
||||||
Insert: insert,
|
|
||||||
Update: update,
|
|
||||||
Delete: delete,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
23
core/core.go
23
core/core.go
@ -63,7 +63,7 @@ func (sg *SuperGraph) initCompilers() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sg.schema, err = psql.NewDBSchema(di, sg.conf.GetDBTableAliases())
|
sg.schema, err = psql.NewDBSchema(di, getDBTableAliases(sg.conf))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -89,25 +89,28 @@ func (sg *SuperGraph) initCompilers() error {
|
|||||||
|
|
||||||
func (c *scontext) execQuery() ([]byte, error) {
|
func (c *scontext) execQuery() ([]byte, error) {
|
||||||
var data []byte
|
var data []byte
|
||||||
// var st *stmt
|
var st *stmt
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if c.sg.conf.Production {
|
if c.sg.conf.UseAllowList {
|
||||||
data, _, err = c.resolvePreparedSQL()
|
data, st, err = c.resolvePreparedSQL()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
data, _, err = c.resolveSQL()
|
data, st, err = c.resolveSQL()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
if len(data) == 0 || st.skipped == 0 {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
//return execRemoteJoin(st, data, c.req.hdr)
|
// return c.sg.execRemoteJoin(st, data, c.req.hdr)
|
||||||
|
return c.sg.execRemoteJoin(st, data, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *scontext) resolvePreparedSQL() ([]byte, *stmt, error) {
|
func (c *scontext) resolvePreparedSQL() ([]byte, *stmt, error) {
|
||||||
@ -115,7 +118,7 @@ func (c *scontext) resolvePreparedSQL() ([]byte, *stmt, error) {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
mutation := (c.res.op == qcode.QTMutation)
|
mutation := (c.res.op == qcode.QTMutation)
|
||||||
useRoleQuery := c.sg.conf.IsABACEnabled() && mutation
|
useRoleQuery := c.sg.abacEnabled && mutation
|
||||||
useTx := useRoleQuery || c.sg.conf.SetUserID
|
useTx := useRoleQuery || c.sg.conf.SetUserID
|
||||||
|
|
||||||
if useTx {
|
if useTx {
|
||||||
@ -148,7 +151,7 @@ func (c *scontext) resolvePreparedSQL() ([]byte, *stmt, error) {
|
|||||||
|
|
||||||
c.res.role = role
|
c.res.role = role
|
||||||
|
|
||||||
ps, ok := prepared[stmtHash(c.res.name, role)]
|
ps, ok := c.sg.prepared[stmtHash(c.res.name, role)]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil, errNotFound
|
return nil, nil, errNotFound
|
||||||
}
|
}
|
||||||
@ -198,7 +201,7 @@ func (c *scontext) resolveSQL() ([]byte, *stmt, error) {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
mutation := (c.res.op == qcode.QTMutation)
|
mutation := (c.res.op == qcode.QTMutation)
|
||||||
useRoleQuery := c.sg.conf.IsABACEnabled() && mutation
|
useRoleQuery := c.sg.abacEnabled && mutation
|
||||||
useTx := useRoleQuery || c.sg.conf.SetUserID
|
useTx := useRoleQuery || c.sg.conf.SetUserID
|
||||||
|
|
||||||
if useTx {
|
if useTx {
|
||||||
|
284
core/init.go
Normal file
284
core/init.go
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/dosco/super-graph/core/internal/psql"
|
||||||
|
"github.com/dosco/super-graph/core/internal/qcode"
|
||||||
|
"github.com/gobuffalo/flect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (sg *SuperGraph) initConfig() error {
|
||||||
|
c := sg.conf
|
||||||
|
|
||||||
|
for k, v := range c.Inflections {
|
||||||
|
flect.AddPlural(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variables: Validate and sanitize
|
||||||
|
for k, v := range c.Vars {
|
||||||
|
c.Vars[k] = sanitizeVars(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tables: Validate and sanitize
|
||||||
|
tm := make(map[string]struct{})
|
||||||
|
|
||||||
|
for i := 0; i < len(c.Tables); i++ {
|
||||||
|
t := &c.Tables[i]
|
||||||
|
t.Name = flect.Pluralize(strings.ToLower(t.Name))
|
||||||
|
|
||||||
|
if _, ok := tm[t.Name]; ok {
|
||||||
|
sg.conf.Tables = append(c.Tables[:i], c.Tables[i+1:]...)
|
||||||
|
sg.log.Printf("WRN duplicate table found: %s", t.Name)
|
||||||
|
}
|
||||||
|
tm[t.Name] = struct{}{}
|
||||||
|
|
||||||
|
t.Table = flect.Pluralize(strings.ToLower(t.Table))
|
||||||
|
}
|
||||||
|
|
||||||
|
sg.roles = make(map[string]*Role)
|
||||||
|
|
||||||
|
for i := 0; i < len(c.Roles); i++ {
|
||||||
|
role := &c.Roles[i]
|
||||||
|
role.Name = sanitize(role.Name)
|
||||||
|
|
||||||
|
if _, ok := sg.roles[role.Name]; ok {
|
||||||
|
c.Roles = append(c.Roles[:i], c.Roles[i+1:]...)
|
||||||
|
sg.log.Printf("WRN duplicate role found: %s", role.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
role.Match = sanitize(role.Match)
|
||||||
|
role.tm = make(map[string]*RoleTable)
|
||||||
|
|
||||||
|
for n, table := range role.Tables {
|
||||||
|
role.tm[table.Name] = &role.Tables[n]
|
||||||
|
}
|
||||||
|
|
||||||
|
sg.roles[role.Name] = role
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user role not defined then create it
|
||||||
|
if _, ok := sg.roles["user"]; !ok {
|
||||||
|
ur := Role{
|
||||||
|
Name: "user",
|
||||||
|
tm: make(map[string]*RoleTable),
|
||||||
|
}
|
||||||
|
c.Roles = append(c.Roles, ur)
|
||||||
|
sg.roles["user"] = &ur
|
||||||
|
}
|
||||||
|
|
||||||
|
// Roles: validate and sanitize
|
||||||
|
c.RolesQuery = sanitize(c.RolesQuery)
|
||||||
|
|
||||||
|
if len(c.RolesQuery) == 0 {
|
||||||
|
sg.log.Printf("WRN roles_query not defined: attribute based access control disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, userExists := sg.roles["user"]
|
||||||
|
_, sg.anonExists = sg.roles["anon"]
|
||||||
|
|
||||||
|
sg.abacEnabled = userExists && len(c.RolesQuery) != 0
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDBTableAliases(c *Config) map[string][]string {
|
||||||
|
m := make(map[string][]string, len(c.Tables))
|
||||||
|
|
||||||
|
for i := range c.Tables {
|
||||||
|
t := c.Tables[i]
|
||||||
|
|
||||||
|
if len(t.Table) == 0 || len(t.Columns) != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
m[t.Table] = append(m[t.Table], t.Name)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTables(c *Config, di *psql.DBInfo) error {
|
||||||
|
for _, t := range c.Tables {
|
||||||
|
if len(t.Table) == 0 || len(t.Columns) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := addTable(di, t.Columns, t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTable(di *psql.DBInfo, cols []Column, t Table) error {
|
||||||
|
bc, ok := di.GetColumn(t.Table, t.Name)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Column '%s' not found on table '%s'",
|
||||||
|
t.Name, t.Table)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bc.Type != "json" && bc.Type != "jsonb" {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Column '%s' in table '%s' is of type '%s'. Only JSON or JSONB is valid",
|
||||||
|
t.Name, t.Table, bc.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
table := psql.DBTable{
|
||||||
|
Name: t.Name,
|
||||||
|
Key: strings.ToLower(t.Name),
|
||||||
|
Type: bc.Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
columns := make([]psql.DBColumn, 0, len(cols))
|
||||||
|
|
||||||
|
for i := range cols {
|
||||||
|
c := cols[i]
|
||||||
|
columns = append(columns, psql.DBColumn{
|
||||||
|
Name: c.Name,
|
||||||
|
Key: strings.ToLower(c.Name),
|
||||||
|
Type: c.Type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
di.AddTable(table, columns)
|
||||||
|
bc.FKeyTable = t.Name
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addForeignKeys(c *Config, di *psql.DBInfo) error {
|
||||||
|
for _, t := range c.Tables {
|
||||||
|
for _, c := range t.Columns {
|
||||||
|
if len(c.ForeignKey) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := addForeignKey(di, c, t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addForeignKey(di *psql.DBInfo, c Column, t Table) error {
|
||||||
|
c1, ok := di.GetColumn(t.Name, c.Name)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Invalid table '%s' or column '%s' in Config",
|
||||||
|
t.Name, c.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
v := strings.SplitN(c.ForeignKey, ".", 2)
|
||||||
|
if len(v) != 2 {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Invalid foreign_key in Config for table '%s' and column '%s",
|
||||||
|
t.Name, c.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fkt, fkc := v[0], v[1]
|
||||||
|
c2, ok := di.GetColumn(fkt, fkc)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Invalid foreign_key in Config for table '%s' and column '%s",
|
||||||
|
t.Name, c.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
c1.FKeyTable = fkt
|
||||||
|
c1.FKeyColID = []int16{c2.ID}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addRoles(c *Config, qc *qcode.Compiler) error {
|
||||||
|
for _, r := range c.Roles {
|
||||||
|
for _, t := range r.Tables {
|
||||||
|
if err := addRole(qc, r, t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addRole(qc *qcode.Compiler, r Role, t RoleTable) error {
|
||||||
|
blockFilter := []string{"false"}
|
||||||
|
|
||||||
|
query := qcode.QueryConfig{
|
||||||
|
Limit: t.Query.Limit,
|
||||||
|
Filters: t.Query.Filters,
|
||||||
|
Columns: t.Query.Columns,
|
||||||
|
DisableFunctions: t.Query.DisableFunctions,
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Query.Block {
|
||||||
|
query.Filters = blockFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
insert := qcode.InsertConfig{
|
||||||
|
Filters: t.Insert.Filters,
|
||||||
|
Columns: t.Insert.Columns,
|
||||||
|
Presets: t.Insert.Presets,
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Insert.Block {
|
||||||
|
insert.Filters = blockFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
update := qcode.UpdateConfig{
|
||||||
|
Filters: t.Update.Filters,
|
||||||
|
Columns: t.Update.Columns,
|
||||||
|
Presets: t.Update.Presets,
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Update.Block {
|
||||||
|
update.Filters = blockFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
delete := qcode.DeleteConfig{
|
||||||
|
Filters: t.Delete.Filters,
|
||||||
|
Columns: t.Delete.Columns,
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Delete.Block {
|
||||||
|
delete.Filters = blockFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
return qc.AddRole(r.Name, t.Name, qcode.TRConfig{
|
||||||
|
Query: query,
|
||||||
|
Insert: insert,
|
||||||
|
Update: update,
|
||||||
|
Delete: delete,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Role) GetTable(name string) *RoleTable {
|
||||||
|
return r.tm[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
func sanitize(value string) string {
|
||||||
|
return strings.ToLower(strings.TrimSpace(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
varRe1 = regexp.MustCompile(`(?mi)\$([a-zA-Z0-9_.]+)`)
|
||||||
|
varRe2 = regexp.MustCompile(`\{\{([a-zA-Z0-9_.]+)\}\}`)
|
||||||
|
)
|
||||||
|
|
||||||
|
func sanitizeVars(s string) string {
|
||||||
|
s0 := varRe1.ReplaceAllString(s, `{{$1}}`)
|
||||||
|
|
||||||
|
s1 := strings.Map(func(r rune) rune {
|
||||||
|
if unicode.IsSpace(r) {
|
||||||
|
return ' '
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}, s0)
|
||||||
|
|
||||||
|
return varRe2.ReplaceAllStringFunc(s1, func(m string) string {
|
||||||
|
return strings.ToLower(m)
|
||||||
|
})
|
||||||
|
}
|
@ -7,7 +7,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -35,11 +34,11 @@ type Config struct {
|
|||||||
Persist bool
|
Persist bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cpath string, conf Config) (*List, error) {
|
func New(filename string, conf Config) (*List, error) {
|
||||||
al := List{}
|
al := List{}
|
||||||
|
|
||||||
if len(cpath) != 0 {
|
if len(filename) != 0 {
|
||||||
fp := path.Join(cpath, "allow.list")
|
fp := filename
|
||||||
|
|
||||||
if _, err := os.Stat(fp); err == nil {
|
if _, err := os.Stat(fp); err == nil {
|
||||||
al.filepath = fp
|
al.filepath = fp
|
||||||
@ -73,10 +72,10 @@ func New(cpath string, conf Config) (*List, error) {
|
|||||||
return nil, errors.New("allow.list not found")
|
return nil, errors.New("allow.list not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cpath) == 0 {
|
if len(filename) == 0 {
|
||||||
al.filepath = "./config/allow.list"
|
al.filepath = "./config/allow.list"
|
||||||
} else {
|
} else {
|
||||||
al.filepath = path.Join(cpath, "allow.list")
|
al.filepath = filename
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
package qcode
|
package qcode
|
||||||
|
|
||||||
func GetQType(gql string) QType {
|
func GetQType(gql string) QType {
|
||||||
|
ic := false
|
||||||
for i := range gql {
|
for i := range gql {
|
||||||
b := gql[i]
|
b := gql[i]
|
||||||
if b == '{' {
|
switch {
|
||||||
|
case b == '#':
|
||||||
|
ic = true
|
||||||
|
case b == '\n':
|
||||||
|
ic = false
|
||||||
|
case !ic && b == '{':
|
||||||
return QTQuery
|
return QTQuery
|
||||||
}
|
case !ic && al(b):
|
||||||
if al(b) {
|
|
||||||
switch b {
|
switch b {
|
||||||
case 'm', 'M':
|
case 'm', 'M':
|
||||||
return QTMutation
|
return QTMutation
|
||||||
|
50
core/internal/qcode/utils_test.go
Normal file
50
core/internal/qcode/utils_test.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package qcode
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestGetQType(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
gql string
|
||||||
|
}
|
||||||
|
type ts struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want QType
|
||||||
|
}
|
||||||
|
tests := []ts{
|
||||||
|
ts{
|
||||||
|
name: "query",
|
||||||
|
args: args{gql: " query {"},
|
||||||
|
want: QTQuery,
|
||||||
|
},
|
||||||
|
ts{
|
||||||
|
name: "mutation",
|
||||||
|
args: args{gql: " mutation {"},
|
||||||
|
want: QTMutation,
|
||||||
|
},
|
||||||
|
ts{
|
||||||
|
name: "default query",
|
||||||
|
args: args{gql: " {"},
|
||||||
|
want: QTQuery,
|
||||||
|
},
|
||||||
|
ts{
|
||||||
|
name: "default query with comment",
|
||||||
|
args: args{gql: `# query is good
|
||||||
|
{`},
|
||||||
|
want: QTQuery,
|
||||||
|
},
|
||||||
|
ts{
|
||||||
|
name: "failed query with comment",
|
||||||
|
args: args{gql: `# query is good query {`},
|
||||||
|
want: -1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := GetQType(tt.args.gql); got != tt.want {
|
||||||
|
t.Errorf("GetQType() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -23,17 +23,13 @@ type preparedItem struct {
|
|||||||
roleArg bool
|
roleArg bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
prepared map[string]*preparedItem
|
|
||||||
)
|
|
||||||
|
|
||||||
func (sg *SuperGraph) initPrepared() error {
|
func (sg *SuperGraph) initPrepared() error {
|
||||||
ct := context.Background()
|
ct := context.Background()
|
||||||
|
|
||||||
if sg.allowList.IsPersist() {
|
if sg.allowList.IsPersist() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
prepared = make(map[string]*preparedItem)
|
sg.prepared = make(map[string]*preparedItem)
|
||||||
|
|
||||||
tx, err := sg.db.BeginTx(ct, nil)
|
tx, err := sg.db.BeginTx(ct, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -100,7 +96,7 @@ func (sg *SuperGraph) prepareStmt(item allow.Item) error {
|
|||||||
var stmts1 []stmt
|
var stmts1 []stmt
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if sg.conf.IsABACEnabled() {
|
if sg.abacEnabled {
|
||||||
stmts1, err = sg.buildMultiStmt(qb, vars)
|
stmts1, err = sg.buildMultiStmt(qb, vars)
|
||||||
} else {
|
} else {
|
||||||
stmts1, err = sg.buildRoleStmt(qb, vars, "user")
|
stmts1, err = sg.buildRoleStmt(qb, vars, "user")
|
||||||
@ -117,7 +113,7 @@ func (sg *SuperGraph) prepareStmt(item allow.Item) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if sg.conf.IsAnonRoleDefined() {
|
if sg.anonExists {
|
||||||
// logger.Debug().Msgf("Prepared statement 'query %s' (anon)", item.Name)
|
// logger.Debug().Msgf("Prepared statement 'query %s' (anon)", item.Name)
|
||||||
|
|
||||||
stmts2, err := sg.buildRoleStmt(qb, vars, "anon")
|
stmts2, err := sg.buildRoleStmt(qb, vars, "anon")
|
||||||
@ -184,7 +180,7 @@ func (sg *SuperGraph) prepare(ct context.Context, tx *sql.Tx, st []stmt, key str
|
|||||||
func (sg *SuperGraph) prepareRoleStmt(tx *sql.Tx) error {
|
func (sg *SuperGraph) prepareRoleStmt(tx *sql.Tx) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !sg.conf.IsABACEnabled() {
|
if !sg.abacEnabled {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,11 +251,16 @@ func (sg *SuperGraph) initAllowList() error {
|
|||||||
var ac allow.Config
|
var ac allow.Config
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !sg.conf.Production {
|
if len(sg.conf.AllowListFile) == 0 {
|
||||||
|
sg.conf.UseAllowList = false
|
||||||
|
sg.log.Printf("WRN allow list disabled no file specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sg.conf.UseAllowList {
|
||||||
ac = allow.Config{CreateIfNotExists: true, Persist: true}
|
ac = allow.Config{CreateIfNotExists: true, Persist: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
sg.allowList, err = allow.New(sg.conf.ConfigPathUsed(), ac)
|
sg.allowList, err = allow.New(sg.conf.AllowListFile, ac)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to initialize allow list: %w", err)
|
return fmt.Errorf("failed to initialize allow list: %w", err)
|
||||||
}
|
}
|
||||||
|
382
core/remote.go
382
core/remote.go
@ -1,253 +1,249 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
// import (
|
import (
|
||||||
// "bytes"
|
"bytes"
|
||||||
// "errors"
|
"errors"
|
||||||
// "fmt"
|
"fmt"
|
||||||
// "net/http"
|
"net/http"
|
||||||
// "sync"
|
"sync"
|
||||||
|
|
||||||
// "github.com/cespare/xxhash/v2"
|
"github.com/cespare/xxhash/v2"
|
||||||
// "github.com/dosco/super-graph/jsn"
|
"github.com/dosco/super-graph/core/internal/qcode"
|
||||||
// "github.com/dosco/super-graph/core/internal/qcode"
|
"github.com/dosco/super-graph/jsn"
|
||||||
// )
|
)
|
||||||
|
|
||||||
// func execRemoteJoin(st *stmt, data []byte, hdr http.Header) ([]byte, error) {
|
func (sg *SuperGraph) execRemoteJoin(st *stmt, data []byte, hdr http.Header) ([]byte, error) {
|
||||||
// var err error
|
var err error
|
||||||
|
|
||||||
// if len(data) == 0 || st.skipped == 0 {
|
sel := st.qc.Selects
|
||||||
// return data, nil
|
h := xxhash.New()
|
||||||
// }
|
|
||||||
|
|
||||||
// sel := st.qc.Selects
|
// fetch the field name used within the db response json
|
||||||
// h := xxhash.New()
|
// that are used to mark insertion points and the mapping between
|
||||||
|
// those field names and their select objects
|
||||||
|
fids, sfmap := sg.parentFieldIds(h, sel, st.skipped)
|
||||||
|
|
||||||
// // fetch the field name used within the db response json
|
// fetch the field values of the marked insertion points
|
||||||
// // that are used to mark insertion points and the mapping between
|
// these values contain the id to be used with fetching remote data
|
||||||
// // those field names and their select objects
|
from := jsn.Get(data, fids)
|
||||||
// fids, sfmap := parentFieldIds(h, sel, st.skipped)
|
var to []jsn.Field
|
||||||
|
|
||||||
// // fetch the field values of the marked insertion points
|
switch {
|
||||||
// // these values contain the id to be used with fetching remote data
|
case len(from) == 1:
|
||||||
// from := jsn.Get(data, fids)
|
to, err = sg.resolveRemote(hdr, h, from[0], sel, sfmap)
|
||||||
// var to []jsn.Field
|
|
||||||
|
|
||||||
// switch {
|
case len(from) > 1:
|
||||||
// case len(from) == 1:
|
to, err = sg.resolveRemotes(hdr, h, from, sel, sfmap)
|
||||||
// to, err = resolveRemote(hdr, h, from[0], sel, sfmap)
|
|
||||||
|
|
||||||
// case len(from) > 1:
|
default:
|
||||||
// to, err = resolveRemotes(hdr, h, from, sel, sfmap)
|
return nil, errors.New("something wrong no remote ids found in db response")
|
||||||
|
}
|
||||||
|
|
||||||
// default:
|
if err != nil {
|
||||||
// return nil, errors.New("something wrong no remote ids found in db response")
|
return nil, err
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if err != nil {
|
var ob bytes.Buffer
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var ob bytes.Buffer
|
err = jsn.Replace(&ob, data, from, to)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// err = jsn.Replace(&ob, data, from, to)
|
return ob.Bytes(), nil
|
||||||
// if err != nil {
|
}
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return ob.Bytes(), nil
|
func (sg *SuperGraph) resolveRemote(
|
||||||
// }
|
hdr http.Header,
|
||||||
|
h *xxhash.Digest,
|
||||||
|
field jsn.Field,
|
||||||
|
sel []qcode.Select,
|
||||||
|
sfmap map[uint64]*qcode.Select) ([]jsn.Field, error) {
|
||||||
|
|
||||||
// func resolveRemote(
|
// replacement data for the marked insertion points
|
||||||
// hdr http.Header,
|
// key and value will be replaced by whats below
|
||||||
// h *xxhash.Digest,
|
toA := [1]jsn.Field{}
|
||||||
// field jsn.Field,
|
to := toA[:1]
|
||||||
// sel []qcode.Select,
|
|
||||||
// sfmap map[uint64]*qcode.Select) ([]jsn.Field, error) {
|
|
||||||
|
|
||||||
// // replacement data for the marked insertion points
|
// use the json key to find the related Select object
|
||||||
// // key and value will be replaced by whats below
|
k1 := xxhash.Sum64(field.Key)
|
||||||
// toA := [1]jsn.Field{}
|
|
||||||
// to := toA[:1]
|
|
||||||
|
|
||||||
// // use the json key to find the related Select object
|
s, ok := sfmap[k1]
|
||||||
// k1 := xxhash.Sum64(field.Key)
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
p := sel[s.ParentID]
|
||||||
|
|
||||||
// s, ok := sfmap[k1]
|
// then use the Table nme in the Select and it's parent
|
||||||
// if !ok {
|
// to find the resolver to use for this relationship
|
||||||
// return nil, nil
|
k2 := mkkey(h, s.Name, p.Name)
|
||||||
// }
|
|
||||||
// p := sel[s.ParentID]
|
|
||||||
|
|
||||||
// // then use the Table nme in the Select and it's parent
|
r, ok := sg.rmap[k2]
|
||||||
// // to find the resolver to use for this relationship
|
if !ok {
|
||||||
// k2 := mkkey(h, s.Name, p.Name)
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// r, ok := rmap[k2]
|
id := jsn.Value(field.Value)
|
||||||
// if !ok {
|
if len(id) == 0 {
|
||||||
// return nil, nil
|
return nil, nil
|
||||||
// }
|
}
|
||||||
|
|
||||||
// id := jsn.Value(field.Value)
|
//st := time.Now()
|
||||||
// if len(id) == 0 {
|
|
||||||
// return nil, nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //st := time.Now()
|
b, err := r.Fn(hdr, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// b, err := r.Fn(hdr, id)
|
if len(r.Path) != 0 {
|
||||||
// if err != nil {
|
b = jsn.Strip(b, r.Path)
|
||||||
// return nil, err
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// if len(r.Path) != 0 {
|
var ob bytes.Buffer
|
||||||
// b = jsn.Strip(b, r.Path)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var ob bytes.Buffer
|
if len(s.Cols) != 0 {
|
||||||
|
err = jsn.Filter(&ob, b, colsToList(s.Cols))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// if len(s.Cols) != 0 {
|
} else {
|
||||||
// err = jsn.Filter(&ob, b, colsToList(s.Cols))
|
ob.WriteString("null")
|
||||||
// if err != nil {
|
}
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// } else {
|
to[0] = jsn.Field{Key: []byte(s.FieldName), Value: ob.Bytes()}
|
||||||
// ob.WriteString("null")
|
return to, nil
|
||||||
// }
|
}
|
||||||
|
|
||||||
// to[0] = jsn.Field{Key: []byte(s.FieldName), Value: ob.Bytes()}
|
func (sg *SuperGraph) resolveRemotes(
|
||||||
// return to, nil
|
hdr http.Header,
|
||||||
// }
|
h *xxhash.Digest,
|
||||||
|
from []jsn.Field,
|
||||||
|
sel []qcode.Select,
|
||||||
|
sfmap map[uint64]*qcode.Select) ([]jsn.Field, error) {
|
||||||
|
|
||||||
// func resolveRemotes(
|
// replacement data for the marked insertion points
|
||||||
// hdr http.Header,
|
// key and value will be replaced by whats below
|
||||||
// h *xxhash.Digest,
|
to := make([]jsn.Field, len(from))
|
||||||
// from []jsn.Field,
|
|
||||||
// sel []qcode.Select,
|
|
||||||
// sfmap map[uint64]*qcode.Select) ([]jsn.Field, error) {
|
|
||||||
|
|
||||||
// // replacement data for the marked insertion points
|
var wg sync.WaitGroup
|
||||||
// // key and value will be replaced by whats below
|
wg.Add(len(from))
|
||||||
// to := make([]jsn.Field, len(from))
|
|
||||||
|
|
||||||
// var wg sync.WaitGroup
|
var cerr error
|
||||||
// wg.Add(len(from))
|
|
||||||
|
|
||||||
// var cerr error
|
for i, id := range from {
|
||||||
|
|
||||||
// for i, id := range from {
|
// use the json key to find the related Select object
|
||||||
|
k1 := xxhash.Sum64(id.Key)
|
||||||
|
|
||||||
// // use the json key to find the related Select object
|
s, ok := sfmap[k1]
|
||||||
// k1 := xxhash.Sum64(id.Key)
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
p := sel[s.ParentID]
|
||||||
|
|
||||||
// s, ok := sfmap[k1]
|
// then use the Table nme in the Select and it's parent
|
||||||
// if !ok {
|
// to find the resolver to use for this relationship
|
||||||
// return nil, nil
|
k2 := mkkey(h, s.Name, p.Name)
|
||||||
// }
|
|
||||||
// p := sel[s.ParentID]
|
|
||||||
|
|
||||||
// // then use the Table nme in the Select and it's parent
|
r, ok := sg.rmap[k2]
|
||||||
// // to find the resolver to use for this relationship
|
if !ok {
|
||||||
// k2 := mkkey(h, s.Name, p.Name)
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// r, ok := rmap[k2]
|
id := jsn.Value(id.Value)
|
||||||
// if !ok {
|
if len(id) == 0 {
|
||||||
// return nil, nil
|
return nil, nil
|
||||||
// }
|
}
|
||||||
|
|
||||||
// id := jsn.Value(id.Value)
|
go func(n int, id []byte, s *qcode.Select) {
|
||||||
// if len(id) == 0 {
|
defer wg.Done()
|
||||||
// return nil, nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// go func(n int, id []byte, s *qcode.Select) {
|
//st := time.Now()
|
||||||
// defer wg.Done()
|
|
||||||
|
|
||||||
// //st := time.Now()
|
b, err := r.Fn(hdr, id)
|
||||||
|
if err != nil {
|
||||||
|
cerr = fmt.Errorf("%s: %s", s.Name, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// b, err := r.Fn(hdr, id)
|
if len(r.Path) != 0 {
|
||||||
// if err != nil {
|
b = jsn.Strip(b, r.Path)
|
||||||
// cerr = fmt.Errorf("%s: %s", s.Name, err)
|
}
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if len(r.Path) != 0 {
|
var ob bytes.Buffer
|
||||||
// b = jsn.Strip(b, r.Path)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var ob bytes.Buffer
|
if len(s.Cols) != 0 {
|
||||||
|
err = jsn.Filter(&ob, b, colsToList(s.Cols))
|
||||||
|
if err != nil {
|
||||||
|
cerr = fmt.Errorf("%s: %s", s.Name, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// if len(s.Cols) != 0 {
|
} else {
|
||||||
// err = jsn.Filter(&ob, b, colsToList(s.Cols))
|
ob.WriteString("null")
|
||||||
// if err != nil {
|
}
|
||||||
// cerr = fmt.Errorf("%s: %s", s.Name, err)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// } else {
|
to[n] = jsn.Field{Key: []byte(s.FieldName), Value: ob.Bytes()}
|
||||||
// ob.WriteString("null")
|
}(i, id, s)
|
||||||
// }
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
// to[n] = jsn.Field{Key: []byte(s.FieldName), Value: ob.Bytes()}
|
return to, cerr
|
||||||
// }(i, id, s)
|
}
|
||||||
// }
|
|
||||||
// wg.Wait()
|
|
||||||
|
|
||||||
// return to, cerr
|
func (sg *SuperGraph) parentFieldIds(h *xxhash.Digest, sel []qcode.Select, skipped uint32) (
|
||||||
// }
|
[][]byte,
|
||||||
|
map[uint64]*qcode.Select) {
|
||||||
|
|
||||||
// func parentFieldIds(h *xxhash.Digest, sel []qcode.Select, skipped uint32) (
|
c := 0
|
||||||
// [][]byte,
|
for i := range sel {
|
||||||
// map[uint64]*qcode.Select) {
|
s := &sel[i]
|
||||||
|
if isSkipped(skipped, uint32(s.ID)) {
|
||||||
|
c++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// c := 0
|
// list of keys (and it's related value) to extract from
|
||||||
// for i := range sel {
|
// the db json response
|
||||||
// s := &sel[i]
|
fm := make([][]byte, c)
|
||||||
// if isSkipped(skipped, uint32(s.ID)) {
|
|
||||||
// c++
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // list of keys (and it's related value) to extract from
|
// mapping between the above extracted key and a Select
|
||||||
// // the db json response
|
// object
|
||||||
// fm := make([][]byte, c)
|
sm := make(map[uint64]*qcode.Select, c)
|
||||||
|
n := 0
|
||||||
|
|
||||||
// // mapping between the above extracted key and a Select
|
for i := range sel {
|
||||||
// // object
|
s := &sel[i]
|
||||||
// sm := make(map[uint64]*qcode.Select, c)
|
|
||||||
// n := 0
|
|
||||||
|
|
||||||
// for i := range sel {
|
if !isSkipped(skipped, uint32(s.ID)) {
|
||||||
// s := &sel[i]
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// if !isSkipped(skipped, uint32(s.ID)) {
|
p := sel[s.ParentID]
|
||||||
// continue
|
k := mkkey(h, s.Name, p.Name)
|
||||||
// }
|
|
||||||
|
|
||||||
// p := sel[s.ParentID]
|
if r, ok := sg.rmap[k]; ok {
|
||||||
// k := mkkey(h, s.Name, p.Name)
|
fm[n] = r.IDField
|
||||||
|
n++
|
||||||
|
|
||||||
// if r, ok := rmap[k]; ok {
|
k := xxhash.Sum64(r.IDField)
|
||||||
// fm[n] = r.IDField
|
sm[k] = s
|
||||||
// n++
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// k := xxhash.Sum64(r.IDField)
|
return fm, sm
|
||||||
// sm[k] = s
|
}
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return fm, sm
|
func isSkipped(n uint32, pos uint32) bool {
|
||||||
// }
|
return ((n & (1 << pos)) != 0)
|
||||||
|
}
|
||||||
|
|
||||||
// func isSkipped(n uint32, pos uint32) bool {
|
func colsToList(cols []qcode.Column) []string {
|
||||||
// return ((n & (1 << pos)) != 0)
|
var f []string
|
||||||
// }
|
|
||||||
|
|
||||||
// func colsToList(cols []qcode.Column) []string {
|
for i := range cols {
|
||||||
// var f []string
|
f = append(f, cols[i].Name)
|
||||||
|
}
|
||||||
// for i := range cols {
|
return f
|
||||||
// f = append(f, cols[i].Name)
|
}
|
||||||
// }
|
|
||||||
// return f
|
|
||||||
// }
|
|
||||||
|
130
core/resolve.go
130
core/resolve.go
@ -6,93 +6,92 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/dosco/super-graph/config"
|
"github.com/cespare/xxhash/v2"
|
||||||
|
"github.com/dosco/super-graph/core/internal/psql"
|
||||||
"github.com/dosco/super-graph/jsn"
|
"github.com/dosco/super-graph/jsn"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
rmap map[uint64]*resolvFn
|
|
||||||
)
|
|
||||||
|
|
||||||
type resolvFn struct {
|
type resolvFn struct {
|
||||||
IDField []byte
|
IDField []byte
|
||||||
Path [][]byte
|
Path [][]byte
|
||||||
Fn func(h http.Header, id []byte) ([]byte, error)
|
Fn func(h http.Header, id []byte) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// func initResolvers() {
|
func (sg *SuperGraph) initResolvers() error {
|
||||||
// var err error
|
var err error
|
||||||
// rmap = make(map[uint64]*resolvFn)
|
sg.rmap = make(map[uint64]*resolvFn)
|
||||||
|
|
||||||
// for _, t := range conf.Tables {
|
for _, t := range sg.conf.Tables {
|
||||||
// err = initRemotes(t)
|
err = sg.initRemotes(t)
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// break
|
break
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// errlog.Fatal().Err(err).Msg("failed to initialize resolvers")
|
return fmt.Errorf("failed to initialize resolvers: %v", err)
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// func initRemotes(t config.Table) error {
|
return nil
|
||||||
// h := xxhash.New()
|
}
|
||||||
|
|
||||||
// for _, r := range t.Remotes {
|
func (sg *SuperGraph) initRemotes(t Table) error {
|
||||||
// // defines the table column to be used as an id in the
|
h := xxhash.New()
|
||||||
// // remote request
|
|
||||||
// idcol := r.ID
|
|
||||||
|
|
||||||
// // if no table column specified in the config then
|
for _, r := range t.Remotes {
|
||||||
// // use the primary key of the table as the id
|
// defines the table column to be used as an id in the
|
||||||
// if len(idcol) == 0 {
|
// remote request
|
||||||
// pcol, err := pcompile.IDColumn(t.Name)
|
idcol := r.ID
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// idcol = pcol.Key
|
|
||||||
// }
|
|
||||||
// idk := fmt.Sprintf("__%s_%s", t.Name, idcol)
|
|
||||||
|
|
||||||
// // register a relationship between the remote data
|
// if no table column specified in the config then
|
||||||
// // and the database table
|
// use the primary key of the table as the id
|
||||||
|
if len(idcol) == 0 {
|
||||||
|
pcol, err := sg.pc.IDColumn(t.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
idcol = pcol.Key
|
||||||
|
}
|
||||||
|
idk := fmt.Sprintf("__%s_%s", t.Name, idcol)
|
||||||
|
|
||||||
// val := &psql.DBRel{Type: psql.RelRemote}
|
// register a relationship between the remote data
|
||||||
// val.Left.Col = idcol
|
// and the database table
|
||||||
// val.Right.Col = idk
|
|
||||||
|
|
||||||
// err := pcompile.AddRelationship(strings.ToLower(r.Name), t.Name, val)
|
val := &psql.DBRel{Type: psql.RelRemote}
|
||||||
// if err != nil {
|
val.Left.Col = idcol
|
||||||
// return err
|
val.Right.Col = idk
|
||||||
// }
|
|
||||||
|
|
||||||
// // the function thats called to resolve this remote
|
err := sg.pc.AddRelationship(sanitize(r.Name), t.Name, val)
|
||||||
// // data request
|
if err != nil {
|
||||||
// fn := buildFn(r)
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// path := [][]byte{}
|
// the function thats called to resolve this remote
|
||||||
// for _, p := range strings.Split(r.Path, ".") {
|
// data request
|
||||||
// path = append(path, []byte(p))
|
fn := buildFn(r)
|
||||||
// }
|
|
||||||
|
|
||||||
// rf := &resolvFn{
|
path := [][]byte{}
|
||||||
// IDField: []byte(idk),
|
for _, p := range strings.Split(r.Path, ".") {
|
||||||
// Path: path,
|
path = append(path, []byte(p))
|
||||||
// Fn: fn,
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// // index resolver obj by parent and child names
|
rf := &resolvFn{
|
||||||
// rmap[mkkey(h, r.Name, t.Name)] = rf
|
IDField: []byte(idk),
|
||||||
|
Path: path,
|
||||||
|
Fn: fn,
|
||||||
|
}
|
||||||
|
|
||||||
// // index resolver obj by IDField
|
// index resolver obj by parent and child names
|
||||||
// rmap[xxhash.Sum64(rf.IDField)] = rf
|
sg.rmap[mkkey(h, r.Name, t.Name)] = rf
|
||||||
// }
|
|
||||||
|
|
||||||
// return nil
|
// index resolver obj by IDField
|
||||||
// }
|
sg.rmap[xxhash.Sum64(rf.IDField)] = rf
|
||||||
|
}
|
||||||
|
|
||||||
func buildFn(r config.Remote) func(http.Header, []byte) ([]byte, error) {
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildFn(r Remote) func(http.Header, []byte) ([]byte, error) {
|
||||||
reqURL := strings.Replace(r.URL, "$id", "%s", 1)
|
reqURL := strings.Replace(r.URL, "$id", "%s", 1)
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
|
|
||||||
@ -115,12 +114,9 @@ func buildFn(r config.Remote) func(http.Header, []byte) ([]byte, error) {
|
|||||||
req.Header.Set(v, hdr.Get(v))
|
req.Header.Set(v, hdr.Get(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
// logger.Debug().Str("uri", uri).Msg("Remote Join")
|
|
||||||
|
|
||||||
res, err := client.Do(req)
|
res, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// errlog.Error().Err(err).Msgf("Failed to connect to: %s", uri)
|
return nil, fmt.Errorf("failed to connect to '%s': %v", uri, err)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
15
core/utils.go
Normal file
15
core/utils.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/cespare/xxhash/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nolint: errcheck
|
||||||
|
func mkkey(h *xxhash.Digest, k1 string, k2 string) uint64 {
|
||||||
|
h.WriteString(k1)
|
||||||
|
h.WriteString(k2)
|
||||||
|
v := h.Sum64()
|
||||||
|
h.Reset()
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="shadow bg-white p-4 flex items-start" :class="className">
|
<div class="shadow p-4 flex items-start" :class="className">
|
||||||
<slot name="image"></slot>
|
<slot name="image"></slot>
|
||||||
<div class="pl-4">
|
<div class="pl-4">
|
||||||
<h2 class="p-0">
|
<h2 class="p-0">
|
||||||
|
@ -2,33 +2,33 @@
|
|||||||
<div>
|
<div>
|
||||||
<main aria-labelledby="main-title" >
|
<main aria-labelledby="main-title" >
|
||||||
<Navbar />
|
<Navbar />
|
||||||
|
<div style="height: 3.6rem"></div>
|
||||||
|
|
||||||
<div class="container mx-auto mt-24">
|
<div class="container mx-auto pt-4">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="text-center text-4xl text-gray-800 leading-tight">
|
<div class="text-center text-3xl md:text-4xl text-black leading-tight font-semibold">
|
||||||
Fetch data without code
|
Fetch data without code
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NavLink
|
<NavLink
|
||||||
class="inline-block px-4 py-3 my-8 bg-blue-600 text-blue-100 font-bold rounded"
|
class="inline-block px-4 py-3 my-8 bg-blue-600 text-white font-bold rounded"
|
||||||
:item="actionLink"
|
:item="actionLink"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
class="px-4 py-3 my-8 border-2 border-gray-500 text-gray-600 font-bold rounded"
|
class="px-4 py-3 my-8 border-2 border-blue-600 text-blue-600 font-bold rounded"
|
||||||
href="https://github.com/dosco/super-graph"
|
href="https://github.com/dosco/super-graph"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>Github</a>
|
>Github</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container mx-auto mb-8">
|
|
||||||
<div class="flex flex-wrap">
|
|
||||||
<div class="w-100 md:w-1/2 bg-indigo-300 text-indigo-800 text-lg p-4">
|
|
||||||
<div class="text-center text-2xl font-bold pb-2">Before, struggle with SQL</div>
|
|
||||||
<pre>
|
|
||||||
|
|
||||||
|
<div class="container mx-auto mb-8 mt-0 md:mt-20 bg-green-100">
|
||||||
|
<div class="flex flex-wrap">
|
||||||
|
<div class="w-100 md:w-1/2 border border-green-500 text-gray-6 00 text-sm md:text-lg p-6">
|
||||||
|
<div class="text-xl font-bold pb-4">Before, struggle with SQL</div>
|
||||||
|
<pre>
|
||||||
type User struct {
|
type User struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
Profile Profile
|
Profile Profile
|
||||||
@ -50,8 +50,9 @@ db.Model(&user).
|
|||||||
and more ...
|
and more ...
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-100 md:w-1/2 bg-green-300 text-black text-lg p-4">
|
|
||||||
<div class="text-center text-2xl font-bold pb-2">With Super Graph, just ask.</div>
|
<div class="w-100 md:w-1/2 border border-l md:border-l-0 border-green-500 text-blue-900 text-sm md:text-lg p-6">
|
||||||
|
<div class="text-xl font-bold pb-4">With Super Graph, just ask.</div>
|
||||||
<pre>
|
<pre>
|
||||||
query {
|
query {
|
||||||
user(id: 5) {
|
user(id: 5) {
|
||||||
@ -59,26 +60,24 @@ query {
|
|||||||
first_name
|
first_name
|
||||||
last_name
|
last_name
|
||||||
picture_url
|
picture_url
|
||||||
}
|
posts(first: 20, order_by: { score: desc }) {
|
||||||
posts(first: 20, order_by: { score: desc }) {
|
slug
|
||||||
slug
|
title
|
||||||
title
|
created_at
|
||||||
created_at
|
votes_total
|
||||||
cached_votes_total
|
votes { created_at }
|
||||||
vote(where: { user_id: { eq: $user_id } }) {
|
author { id name }
|
||||||
id
|
tags { id name }
|
||||||
}
|
}
|
||||||
author { id name }
|
posts_cursor
|
||||||
tags { id name }
|
|
||||||
}
|
}
|
||||||
posts_cursor
|
|
||||||
}
|
}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="mt-0 md:mt-20">
|
||||||
<div
|
<div
|
||||||
class="flex flex-wrap mx-2 md:mx-20"
|
class="flex flex-wrap mx-2 md:mx-20"
|
||||||
v-if="data.features && data.features.length"
|
v-if="data.features && data.features.length"
|
||||||
@ -89,24 +88,21 @@ query {
|
|||||||
:key="index"
|
:key="index"
|
||||||
>
|
>
|
||||||
<div class="p-8">
|
<div class="p-8">
|
||||||
<h2 class="md:text-xl text-blue-800 font-medium border-0 mb-1">{{ feature.title }}</h2>
|
<h2 class="text-lg uppercase border-0">{{ feature.title }}</h2>
|
||||||
<p class="md:text-xl text-gray-700 leading-snug">{{ feature.details }}</p>
|
<div class="text-xl text-gray-900 leading-snug">{{ feature.details }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="bg-gray-100 mt-10">
|
<div class="pt-0 md:pt-20">
|
||||||
<div class="container mx-auto px-10 md:px-0 py-32">
|
<div class="container mx-auto p-10">
|
||||||
|
|
||||||
<div class="pb-8 hidden md:flex justify-center">
|
<div class="flex justify-center pb-20">
|
||||||
<img src="arch-basic.svg">
|
<img src="arch-basic.svg">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 class="uppercase font-semibold text-xl text-blue-800 text-center mb-4">
|
|
||||||
What is Super Graph?
|
|
||||||
</h1>
|
|
||||||
<div class="text-2xl md:text-3xl">
|
<div class="text-2xl md:text-3xl">
|
||||||
Super Graph is a library and service that fetches data from any Postgres database using just GraphQL. No more struggling with ORMs and SQL to wrangle data out of the database. No more having to figure out the right joins or making ineffiient queries. However complex the GraphQL, Super Graph will always generate just one single efficient SQL query. The goal is to save you time and money so you can focus on you're apps core value.
|
Super Graph is a library and service that fetches data from any Postgres database using just GraphQL. No more struggling with ORMs and SQL to wrangle data out of the database. No more having to figure out the right joins or making ineffiient queries. However complex the GraphQL, Super Graph will always generate just one single efficient SQL query. The goal is to save you time and money so you can focus on you're apps core value.
|
||||||
</div>
|
</div>
|
||||||
@ -114,17 +110,7 @@ query {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="container mx-auto flex flex-wrap">
|
<div class="pt-20">
|
||||||
<div class="md:w-1/2">
|
|
||||||
<img src="/graphql.png">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="md:w-1/2">
|
|
||||||
<img src="/json.png">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-10 py-10 md:py-20">
|
|
||||||
<div class="container mx-auto px-10 md:px-0">
|
<div class="container mx-auto px-10 md:px-0">
|
||||||
<h1 class="uppercase font-semibold text-2xl text-blue-800 text-center">
|
<h1 class="uppercase font-semibold text-2xl text-blue-800 text-center">
|
||||||
Try Super Graph
|
Try Super Graph
|
||||||
@ -133,20 +119,18 @@ query {
|
|||||||
<h1 class="uppercase font-semibold text-lg text-gray-800">
|
<h1 class="uppercase font-semibold text-lg text-gray-800">
|
||||||
Deploy as a service using docker
|
Deploy as a service using docker
|
||||||
</h1>
|
</h1>
|
||||||
<div class="bg-gray-800 text-indigo-300 p-4 rounded">
|
<div class="p-4 rounded bg-black text-white">
|
||||||
<pre>$ git clone https://github.com/dosco/super-graph && cd super-graph && make install</pre>
|
<pre>$ git clone https://github.com/dosco/super-graph && cd super-graph && make install</pre>
|
||||||
<pre>$ super-graph new blog; cd blog</pre>
|
<pre>$ super-graph new blog; cd blog</pre>
|
||||||
<pre>$ docker-compose run blog_api ./super-graph db:setup</pre>
|
<pre>$ docker-compose run blog_api ./super-graph db:setup</pre>
|
||||||
<pre>$ docker-compose up</pre>
|
<pre>$ docker-compose up</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="border-t mt-4 pb-4"></div>
|
|
||||||
|
|
||||||
<h1 class="uppercase font-semibold text-lg text-gray-800">
|
<h1 class="uppercase font-semibold text-lg text-gray-800">
|
||||||
Or use it with your own code
|
Or use it with your own code
|
||||||
</h1>
|
</h1>
|
||||||
<div class="text-md">
|
<div class="text-md">
|
||||||
<pre class="bg-gray-800 text-indigo-300 p-4 rounded">
|
<pre class="p-4 rounded bg-black text-white">
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -194,7 +178,7 @@ func main() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-gray-100 mt-10">
|
<div class="pt-0 md:pt-20">
|
||||||
<div class="container mx-auto px-10 md:px-0 py-32">
|
<div class="container mx-auto px-10 md:px-0 py-32">
|
||||||
<h1 class="uppercase font-semibold text-xl text-blue-800 mb-4">
|
<h1 class="uppercase font-semibold text-xl text-blue-800 mb-4">
|
||||||
The story of {{ data.heroText }}
|
The story of {{ data.heroText }}
|
||||||
@ -245,7 +229,7 @@ func main() {
|
|||||||
</div>
|
</div>
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<div class="border-t py-10">
|
<div class="pt-0 md:pt-20">
|
||||||
<div class="block md:hidden w-100">
|
<div class="block md:hidden w-100">
|
||||||
<iframe src='https://www.youtube.com/embed/MfPL2A-DAJk' frameborder='0' allowfullscreen style="width: 100%; height: 250px;">
|
<iframe src='https://www.youtube.com/embed/MfPL2A-DAJk' frameborder='0' allowfullscreen style="width: 100%; height: 250px;">
|
||||||
</iframe>
|
</iframe>
|
||||||
@ -267,7 +251,101 @@ func main() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-gray-200 mt-10">
|
<div class="container mx-auto pt-0 md:pt-20">
|
||||||
|
<div class="flex flex-wrap bg-green-100">
|
||||||
|
<div class="w-100 md:w-1/2 border border-green-500 text-gray-6 00 text-sm md:text-lg p-6">
|
||||||
|
<div class="text-xl font-bold pb-4">No more joins joins, json, orms, just use GraphQL. Fetch all the data want in the structure you need.</div>
|
||||||
|
<pre>
|
||||||
|
query {
|
||||||
|
thread {
|
||||||
|
slug
|
||||||
|
title
|
||||||
|
published
|
||||||
|
createdAt : created_at
|
||||||
|
totalVotes : cached_votes_total
|
||||||
|
totalPosts : cached_posts_total
|
||||||
|
vote : thread_vote(where: { user_id: { eq: $user_id } }) {
|
||||||
|
created_at
|
||||||
|
}
|
||||||
|
topics {
|
||||||
|
slug
|
||||||
|
name
|
||||||
|
}
|
||||||
|
author : me {
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
posts(first: 1, order_by: { score: desc }) {
|
||||||
|
slug
|
||||||
|
body
|
||||||
|
published
|
||||||
|
createdAt : created_at
|
||||||
|
totalVotes : cached_votes_total
|
||||||
|
totalComments : cached_comments_total
|
||||||
|
vote {
|
||||||
|
created_at
|
||||||
|
}
|
||||||
|
author : user {
|
||||||
|
slug
|
||||||
|
firstName : first_name
|
||||||
|
lastName : last_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
posts_cursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-100 md:w-1/2 border border-l md:border-l-0 border-green-500 text-blue-900 text-sm md:text-lg p-6">
|
||||||
|
<div class="text-xl font-bold pb-4">Instant results using a single highly optimized SQL. It's just that simple.</div>
|
||||||
|
<pre>
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"thread": {
|
||||||
|
"slug": "eveniet-ex-24",
|
||||||
|
"vote": null,
|
||||||
|
"posts": [
|
||||||
|
{
|
||||||
|
"body": "Dolor laborum harum sed sit est ducimus temporibus velit non nobis repudiandae nobis suscipit commodi voluptatem debitis sed voluptas sequi officia.",
|
||||||
|
"slug": "illum-in-voluptas-1418",
|
||||||
|
"vote": null,
|
||||||
|
"author": {
|
||||||
|
"slug": "sigurd-kemmer",
|
||||||
|
"lastName": "Effertz",
|
||||||
|
"firstName": "Brandt"
|
||||||
|
},
|
||||||
|
"createdAt": "2020-04-07T04:22:42.115874+00:00",
|
||||||
|
"published": true,
|
||||||
|
"totalVotes": 0,
|
||||||
|
"totalComments": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "In aut qui deleniti quia dolore quasi porro tenetur voluptatem ut adita alias fugit explicabo.",
|
||||||
|
"author": null,
|
||||||
|
"topics": [
|
||||||
|
{
|
||||||
|
"name": "CloudRun",
|
||||||
|
"slug": "cloud-run"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Postgres",
|
||||||
|
"slug": "postgres"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"createdAt": "2020-04-07T04:22:38.099482+00:00",
|
||||||
|
"published": true,
|
||||||
|
"totalPosts": 24,
|
||||||
|
"totalVotes": 0,
|
||||||
|
"posts_cursor": "mpeBl6L+QfJHc3cmLkLDj9pOdEZYTt5KQtLsazG3TLITB3hJhg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pt-0 md:pt-20">
|
||||||
<div class="container mx-auto px-10 md:px-0 py-32">
|
<div class="container mx-auto px-10 md:px-0 py-32">
|
||||||
<h1 class="uppercase font-semibold text-xl text-blue-800 mb-4">
|
<h1 class="uppercase font-semibold text-xl text-blue-800 mb-4">
|
||||||
Build Secure Apps
|
Build Secure Apps
|
||||||
@ -292,8 +370,8 @@ func main() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="">
|
<div class="pt-0 md:py -20">
|
||||||
<div class="container mx-auto px-10 md:px-0 py-32">
|
<div class="container mx-auto">
|
||||||
<h1 class="uppercase font-semibold text-xl text-blue-800 mb-4">
|
<h1 class="uppercase font-semibold text-xl text-blue-800 mb-4">
|
||||||
More Features
|
More Features
|
||||||
</h1>
|
</h1>
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
@tailwind base;
|
@tailwind base;
|
||||||
|
|
||||||
@css {
|
@css {
|
||||||
|
body, .navbar, .navbar .links {
|
||||||
|
@apply bg-white text-black border-0 !important;
|
||||||
|
}
|
||||||
h1 {
|
h1 {
|
||||||
@apply font-semibold text-3xl border-0 py-4
|
@apply font-semibold text-3xl border-0 py-4
|
||||||
}
|
}
|
||||||
|
@ -347,12 +347,10 @@ beer_style
|
|||||||
beer_yeast
|
beer_yeast
|
||||||
|
|
||||||
// Cars
|
// Cars
|
||||||
vehicle
|
car
|
||||||
vehicle_type
|
car_type
|
||||||
car_maker
|
car_maker
|
||||||
car_model
|
car_model
|
||||||
fuel_type
|
|
||||||
transmission_gear_type
|
|
||||||
|
|
||||||
// Text
|
// Text
|
||||||
word
|
word
|
||||||
@ -438,8 +436,8 @@ hipster_paragraph
|
|||||||
hipster_sentence
|
hipster_sentence
|
||||||
|
|
||||||
// File
|
// File
|
||||||
extension
|
file_extension
|
||||||
mine_type
|
file_mine_type
|
||||||
|
|
||||||
// Numbers
|
// Numbers
|
||||||
number
|
number
|
||||||
@ -463,7 +461,6 @@ mac_address
|
|||||||
digit
|
digit
|
||||||
letter
|
letter
|
||||||
lexify
|
lexify
|
||||||
rand_string
|
|
||||||
shuffle_strings
|
shuffle_strings
|
||||||
numerify
|
numerify
|
||||||
```
|
```
|
||||||
@ -1790,18 +1787,37 @@ database:
|
|||||||
# Enable this if you need the user id in triggers, etc
|
# Enable this if you need the user id in triggers, etc
|
||||||
set_user_id: false
|
set_user_id: false
|
||||||
|
|
||||||
# Define additional variables here to be used with filters
|
# database ping timeout is used for db health checking
|
||||||
variables:
|
ping_timeout: 1m
|
||||||
admin_account_id: "5"
|
|
||||||
|
|
||||||
# Field and table names that you wish to block
|
# Set up an secure tls encrypted db connection
|
||||||
blocklist:
|
enable_tls: false
|
||||||
- ar_internal_metadata
|
|
||||||
- schema_migrations
|
# Required for tls. For example with Google Cloud SQL it's
|
||||||
- secret
|
# <gcp-project-id>:<cloud-sql-instance>"
|
||||||
- password
|
# server_name: blah
|
||||||
- encrypted
|
|
||||||
- token
|
# Required for tls. Can be a file path or the contents of the pem file
|
||||||
|
# server_cert: ./server-ca.pem
|
||||||
|
|
||||||
|
# Required for tls. Can be a file path or the contents of the pem file
|
||||||
|
# client_cert: ./client-cert.pem
|
||||||
|
|
||||||
|
# Required for tls. Can be a file path or the contents of the pem file
|
||||||
|
# client_key: ./client-key.pem
|
||||||
|
|
||||||
|
# Define additional variables here to be used with filters
|
||||||
|
variables:
|
||||||
|
admin_account_id: "5"
|
||||||
|
|
||||||
|
# Field and table names that you wish to block
|
||||||
|
blocklist:
|
||||||
|
- ar_internal_metadata
|
||||||
|
- schema_migrations
|
||||||
|
- secret
|
||||||
|
- password
|
||||||
|
- encrypted
|
||||||
|
- token
|
||||||
|
|
||||||
# Create custom actions with their own api endpoints
|
# Create custom actions with their own api endpoints
|
||||||
# For example the below action will be available at /api/v1/actions/refresh_leaderboard_users
|
# For example the below action will be available at /api/v1/actions/refresh_leaderboard_users
|
||||||
|
37
go.mod
37
go.mod
@ -5,31 +5,34 @@ require (
|
|||||||
github.com/NYTimes/gziphandler v1.1.1
|
github.com/NYTimes/gziphandler v1.1.1
|
||||||
github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3
|
github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
|
||||||
github.com/brianvoe/gofakeit v3.18.0+incompatible
|
github.com/brianvoe/gofakeit/v5 v5.2.0
|
||||||
github.com/cespare/xxhash/v2 v2.1.0
|
github.com/cespare/xxhash/v2 v2.1.1
|
||||||
github.com/daaku/go.zipexe v1.0.1 // indirect
|
github.com/daaku/go.zipexe v1.0.1 // indirect
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
github.com/dlclark/regexp2 v1.2.0 // indirect
|
github.com/dlclark/regexp2 v1.2.0 // indirect
|
||||||
github.com/dop251/goja v0.0.0-20190912223329-aa89e6a4c733
|
github.com/dop251/goja v0.0.0-20200414142002-77e84ffb8c65
|
||||||
github.com/fsnotify/fsnotify v1.4.7
|
github.com/fsnotify/fsnotify v1.4.9
|
||||||
github.com/garyburd/redigo v1.6.0
|
github.com/garyburd/redigo v1.6.0
|
||||||
github.com/go-sourcemap/sourcemap v2.1.2+incompatible // indirect
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
||||||
github.com/gobuffalo/flect v0.1.6
|
github.com/gobuffalo/flect v0.2.1
|
||||||
github.com/jackc/pgtype v1.0.1
|
github.com/jackc/pgtype v1.3.0
|
||||||
github.com/jackc/pgx/v4 v4.0.1
|
github.com/jackc/pgx/v4 v4.6.0
|
||||||
github.com/magiconair/properties v1.8.1 // indirect
|
github.com/mitchellh/mapstructure v1.2.2 // indirect
|
||||||
github.com/pelletier/go-toml v1.4.0 // indirect
|
github.com/pelletier/go-toml v1.7.0 // indirect
|
||||||
github.com/pkg/errors v0.8.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/rs/cors v1.7.0
|
github.com/rs/cors v1.7.0
|
||||||
github.com/spf13/afero v1.2.2 // indirect
|
github.com/spf13/afero v1.2.2 // indirect
|
||||||
github.com/spf13/cobra v0.0.5
|
github.com/spf13/cast v1.3.1 // indirect
|
||||||
|
github.com/spf13/cobra v1.0.0
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/spf13/viper v1.4.0
|
github.com/spf13/viper v1.6.3
|
||||||
github.com/valyala/fasttemplate v1.0.1
|
github.com/valyala/fasttemplate v1.1.0
|
||||||
go.uber.org/zap v1.14.1
|
go.uber.org/zap v1.14.1
|
||||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad
|
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904
|
||||||
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 // indirect
|
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 // indirect
|
||||||
gopkg.in/yaml.v2 v2.2.7 // indirect
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.55.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
124
go.sum
124
go.sum
@ -19,24 +19,23 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
|
|||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||||
github.com/brianvoe/gofakeit v3.18.0+incompatible h1:wDOmHc9DLG4nRjUVVaxA+CEglKOW72Y5+4WNxUIkjM8=
|
github.com/brianvoe/gofakeit/v5 v5.2.0 h1:De9X+2PQum9U2zCaIDxLV7wx0YBL6c7RN2sFBImzHGI=
|
||||||
github.com/brianvoe/gofakeit v3.18.0+incompatible/go.mod h1:kfwdRA90vvNhPutZWfH7WPaDzUjz+CZFqG+rPkOjGOc=
|
github.com/brianvoe/gofakeit/v5 v5.2.0/go.mod h1:/ZENnKqX+XrN8SORLe/fu5lZDIo1tuPncWuRD+eyhSI=
|
||||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
|
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||||
github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
github.com/daaku/go.zipexe v1.0.0 h1:VSOgZtH418pH9L16hC/JrgSNJbbAL26pj7lmD1+CGdY=
|
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/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
|
||||||
@ -50,21 +49,23 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
|
|||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
||||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/dop251/goja v0.0.0-20190912223329-aa89e6a4c733 h1:cyNc40Dx5YNEO94idePU8rhVd3dn+sd04Arh0kDBAaw=
|
github.com/dop251/goja v0.0.0-20200414142002-77e84ffb8c65 h1:Nud597JuGCF/MScrb6NNVDRgmuk8X7w3pFc5GvSsm5E=
|
||||||
github.com/dop251/goja v0.0.0-20190912223329-aa89e6a4c733/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA=
|
github.com/dop251/goja v0.0.0-20200414142002-77e84ffb8c65/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA=
|
||||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
|
github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
|
||||||
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-sourcemap/sourcemap v2.1.2+incompatible h1:0b/xya7BKGhXuqFESKM4oIiRo9WOt2ebz7KxfreD6ug=
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
|
||||||
github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/gobuffalo/flect v0.1.6 h1:D7KWNRFiCknJKA495/e1BO7oxqf8tbieaLv/ehoZ/+g=
|
github.com/gobuffalo/flect v0.2.1 h1:GPoRjEN0QObosV4XwuoWvSd5uSiL0N3e91/xqyY4crQ=
|
||||||
github.com/gobuffalo/flect v0.1.6/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
|
github.com/gobuffalo/flect v0.2.1/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc=
|
||||||
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
@ -76,7 +77,10 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
|||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
@ -90,11 +94,13 @@ github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZb
|
|||||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||||
github.com/jackc/chunkreader/v2 v2.0.0 h1:DUwgMQuuPnS0rhMXenUtZpqZqrR/30NWY+qQvTpSvEs=
|
github.com/jackc/chunkreader/v2 v2.0.0 h1:DUwgMQuuPnS0rhMXenUtZpqZqrR/30NWY+qQvTpSvEs=
|
||||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||||
|
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
||||||
|
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||||
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
|
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
|
||||||
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
|
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
|
||||||
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
|
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
|
||||||
github.com/jackc/pgconn v1.0.1 h1:ZANo4pIkeHKIVD1cQMcxu8fwrwIICLblzi9HCjooZeQ=
|
github.com/jackc/pgconn v1.5.0 h1:oFSOilzIZkyg787M1fEmyMfOUUvwj0daqYMfaWwNL4o=
|
||||||
github.com/jackc/pgconn v1.0.1/go.mod h1:GgY/Lbj1VonNaVdNUHs9AwWom3yP2eymFQ1C8z9r/Lk=
|
github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
|
||||||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
||||||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
||||||
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2 h1:JVX6jT/XfzNqIjye4717ITLaNwV9mWbJx0dLCpcRzdA=
|
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2 h1:JVX6jT/XfzNqIjye4717ITLaNwV9mWbJx0dLCpcRzdA=
|
||||||
@ -107,25 +113,31 @@ github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod
|
|||||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
|
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
|
||||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||||
github.com/jackc/pgproto3/v2 v2.0.0 h1:FApgMJ/GtaXfI0s8Lvd0kaLaRwMOhs4VH92pwkwQQvU=
|
github.com/jackc/pgproto3/v2 v2.0.1 h1:Rdjp4NFjwHnEslx2b66FfCI2S0LhO4itac3hXz6WX9M=
|
||||||
github.com/jackc/pgproto3/v2 v2.0.0/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8 h1:Q3tB+ExeflWUW7AFcAhXqk40s9mnNYLk1nOkKNZ5GnU=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||||
github.com/jackc/pgtype v1.0.1 h1:7GWB9n3DdnO3TIbj59wMAE9QcHPL4cy/Bbtk5P1Noow=
|
github.com/jackc/pgtype v1.3.0 h1:l8JvKrby3RI7Kg3bYEeU9TA4vqC38QDpFCfcrC7KuN0=
|
||||||
github.com/jackc/pgtype v1.0.1/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
|
github.com/jackc/pgtype v1.3.0/go.mod h1:b0JqxHvPmljG+HQ5IsvQ0yqeSi4nGcDTVjFoiLDb0Ik=
|
||||||
|
github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o=
|
||||||
|
github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
|
||||||
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
||||||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||||
github.com/jackc/pgx/v4 v4.0.1 h1:NNrG0MX2AVEJw1NNDYg+ixSXycCfWWKeqMuQHQkAngc=
|
github.com/jackc/pgx/v4 v4.6.0 h1:Fh0O9GdlG4gYpjpwOqjdEodJUQM9jzN3Hdv7PN0xmm0=
|
||||||
github.com/jackc/pgx/v4 v4.0.1/go.mod h1:NeQ64VJooukJGFLX2r01sJL/gRbKlpvsO2giBvjfgrY=
|
github.com/jackc/pgx/v4 v4.6.0/go.mod h1:vPh43ZzxijXUVJ+t/EmXBtFmbFVO72cuneCT9oAlxAg=
|
||||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v1.0.0 h1:rbjAshlgKscNa7j0jAM0uNQflis5o2XUogPMVAwtcsM=
|
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v1.0.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
|
||||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
@ -158,17 +170,24 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
|
|||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4=
|
||||||
|
github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229 h1:E2B8qYyeSgv5MXpmzZXRNp8IAQ4vjxIjhpAf5hv/tAg=
|
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229 h1:E2B8qYyeSgv5MXpmzZXRNp8IAQ4vjxIjhpAf5hv/tAg=
|
||||||
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
|
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
|
||||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg=
|
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
|
||||||
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
|
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
@ -188,14 +207,18 @@ github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
|||||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||||
github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY=
|
github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY=
|
||||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
|
||||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE=
|
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE=
|
||||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||||
@ -204,18 +227,22 @@ github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
|||||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
|
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
|
||||||
|
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
|
|
||||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
|
||||||
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
|
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
|
||||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||||
|
github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs=
|
||||||
|
github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
@ -224,13 +251,18 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
|
|||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||||
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
|
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
|
||||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
|
github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4=
|
||||||
|
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
@ -249,17 +281,14 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
|||||||
go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo=
|
go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo=
|
||||||
go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
|
go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
|
|
||||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU=
|
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8=
|
||||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad h1:5E5raQxcv+6CZ11RrBYQe5WRbUIWpScjh0kvHZkZIrQ=
|
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||||
@ -285,8 +314,6 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A=
|
|
||||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@ -296,8 +323,9 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg=
|
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg=
|
||||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
|
||||||
|
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
@ -307,6 +335,7 @@ golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGm
|
|||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@ -317,6 +346,8 @@ golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
@ -328,13 +359,18 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33
|
|||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||||
|
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
||||||
|
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ=
|
||||||
|
gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099 h1:XJP7lxbSxWLOMNdBE4B/STaqVy6L73o0knwj2vIlxnw=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099 h1:XJP7lxbSxWLOMNdBE4B/STaqVy6L73o0knwj2vIlxnw=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||||
|
@ -3,13 +3,11 @@ package serv
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/dosco/super-graph/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type actionFn func(w http.ResponseWriter, r *http.Request) error
|
type actionFn func(w http.ResponseWriter, r *http.Request) error
|
||||||
|
|
||||||
func newAction(a *config.Action) (http.Handler, error) {
|
func newAction(a *Action) (http.Handler, error) {
|
||||||
var fn actionFn
|
var fn actionFn
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@ -32,7 +30,7 @@ func newAction(a *config.Action) (http.Handler, error) {
|
|||||||
return http.HandlerFunc(httpFn), nil
|
return http.HandlerFunc(httpFn), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSQLAction(a *config.Action) (actionFn, error) {
|
func newSQLAction(a *Action) (actionFn, error) {
|
||||||
fn := func(w http.ResponseWriter, r *http.Request) error {
|
fn := func(w http.ResponseWriter, r *http.Request) error {
|
||||||
_, err := db.ExecContext(r.Context(), a.SQL)
|
_, err := db.ExecContext(r.Context(), a.SQL)
|
||||||
return err
|
return err
|
111
internal/serv/api.go
Normal file
111
internal/serv/api.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package serv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/dosco/super-graph/core"
|
||||||
|
"github.com/dosco/super-graph/internal/serv/internal/auth"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LogLevelNone int = iota
|
||||||
|
LogLevelInfo
|
||||||
|
LogLevelWarn
|
||||||
|
LogLevelError
|
||||||
|
LogLevelDebug
|
||||||
|
)
|
||||||
|
|
||||||
|
type Core = core.Config
|
||||||
|
|
||||||
|
// Config struct holds the Super Graph config values
|
||||||
|
type Config struct {
|
||||||
|
Core `mapstructure:",squash"`
|
||||||
|
Serv `mapstructure:",squash"`
|
||||||
|
|
||||||
|
cpath string
|
||||||
|
vi *viper.Viper
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serv struct contains config values used by the Super Graph service
|
||||||
|
type Serv struct {
|
||||||
|
AppName string `mapstructure:"app_name"`
|
||||||
|
Production bool
|
||||||
|
LogLevel string `mapstructure:"log_level"`
|
||||||
|
HostPort string `mapstructure:"host_port"`
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
|
HTTPGZip bool `mapstructure:"http_compress"`
|
||||||
|
WebUI bool `mapstructure:"web_ui"`
|
||||||
|
EnableTracing bool `mapstructure:"enable_tracing"`
|
||||||
|
WatchAndReload bool `mapstructure:"reload_on_config_change"`
|
||||||
|
AuthFailBlock bool `mapstructure:"auth_fail_block"`
|
||||||
|
SeedFile string `mapstructure:"seed_file"`
|
||||||
|
MigrationsPath string `mapstructure:"migrations_path"`
|
||||||
|
AllowedOrigins []string `mapstructure:"cors_allowed_origins"`
|
||||||
|
DebugCORS bool `mapstructure:"cors_debug"`
|
||||||
|
|
||||||
|
Auth auth.Auth
|
||||||
|
Auths []auth.Auth
|
||||||
|
|
||||||
|
DB struct {
|
||||||
|
Type string
|
||||||
|
Host string
|
||||||
|
Port uint16
|
||||||
|
DBName string
|
||||||
|
User string
|
||||||
|
Password string
|
||||||
|
Schema string
|
||||||
|
PoolSize int32 `mapstructure:"pool_size"`
|
||||||
|
MaxRetries int `mapstructure:"max_retries"`
|
||||||
|
PingTimeout time.Duration `mapstructure:"ping_timeout"`
|
||||||
|
EnableTLS bool `mapstructure:"enable_tls"`
|
||||||
|
ServerName string `mapstructure:"server_name"`
|
||||||
|
ServerCert string `mapstructure:"server_cert"`
|
||||||
|
ClientCert string `mapstructure:"client_cert"`
|
||||||
|
ClientKey string `mapstructure:"client_key"`
|
||||||
|
} `mapstructure:"database"`
|
||||||
|
|
||||||
|
Actions []Action
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth struct contains authentication related config values used by the Super Graph service
|
||||||
|
type Auth struct {
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
Cookie string
|
||||||
|
CredsInHeader bool `mapstructure:"creds_in_header"`
|
||||||
|
|
||||||
|
Rails struct {
|
||||||
|
Version string
|
||||||
|
SecretKeyBase string `mapstructure:"secret_key_base"`
|
||||||
|
URL string
|
||||||
|
Password string
|
||||||
|
MaxIdle int `mapstructure:"max_idle"`
|
||||||
|
MaxActive int `mapstructure:"max_active"`
|
||||||
|
Salt string
|
||||||
|
SignSalt string `mapstructure:"sign_salt"`
|
||||||
|
AuthSalt string `mapstructure:"auth_salt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
JWT struct {
|
||||||
|
Provider string
|
||||||
|
Secret string
|
||||||
|
PubKeyFile string `mapstructure:"public_key_file"`
|
||||||
|
PubKeyType string `mapstructure:"public_key_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
Header struct {
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
Exists bool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action struct contains config values for a Super Graph service action
|
||||||
|
type Action struct {
|
||||||
|
Name string
|
||||||
|
SQL string
|
||||||
|
AuthName string `mapstructure:"auth_name"`
|
||||||
|
}
|
@ -6,11 +6,8 @@ import (
|
|||||||
_log "log"
|
_log "log"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/dosco/super-graph/config"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,12 +26,13 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
log *_log.Logger // logger
|
log *_log.Logger // logger
|
||||||
zlog *zap.Logger // fast logger
|
zlog *zap.Logger // fast logger
|
||||||
conf *config.Config // parsed config
|
logLevel int // log level
|
||||||
confPath string // path to the config file
|
conf *Config // parsed config
|
||||||
db *sql.DB // database connection pool
|
confPath string // path to the config file
|
||||||
secretKey [32]byte // encryption key
|
db *sql.DB // database connection pool
|
||||||
|
secretKey [32]byte // encryption key
|
||||||
)
|
)
|
||||||
|
|
||||||
func Cmd() {
|
func Cmd() {
|
||||||
@ -132,12 +130,12 @@ e.g. db:migrate -+1
|
|||||||
Run: cmdNew,
|
Run: cmdNew,
|
||||||
})
|
})
|
||||||
|
|
||||||
rootCmd.AddCommand(&cobra.Command{
|
// rootCmd.AddCommand(&cobra.Command{
|
||||||
Use: fmt.Sprintf("conf:dump [%s]", strings.Join(viper.SupportedExts, "|")),
|
// Use: fmt.Sprintf("conf:dump [%s]", strings.Join(viper.SupportedExts, "|")),
|
||||||
Short: "Dump config to file",
|
// Short: "Dump config to file",
|
||||||
Long: "Dump current config to a file in the selected format",
|
// Long: "Dump current config to a file in the selected format",
|
||||||
Run: cmdConfDump,
|
// Run: cmdConfDump,
|
||||||
})
|
// })
|
||||||
|
|
||||||
rootCmd.AddCommand(&cobra.Command{
|
rootCmd.AddCommand(&cobra.Command{
|
||||||
Use: "version",
|
Use: "version",
|
||||||
@ -158,6 +156,20 @@ func cmdVersion(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BuildDetails() string {
|
func BuildDetails() string {
|
||||||
|
if len(version) == 0 {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
Super Graph (unknown version)
|
||||||
|
For documentation, visit https://supergraph.dev
|
||||||
|
|
||||||
|
To build with version information please use the Makefile
|
||||||
|
> git clone https://github.com/dosco/super-graph
|
||||||
|
> cd super-graph && make install
|
||||||
|
|
||||||
|
Licensed under the Apache Public License 2.0
|
||||||
|
Copyright 2020, Vikram Rangnekar
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
return fmt.Sprintf(`
|
return fmt.Sprintf(`
|
||||||
Super Graph %v
|
Super Graph %v
|
||||||
For documentation, visit https://supergraph.dev
|
For documentation, visit https://supergraph.dev
|
||||||
@ -168,7 +180,7 @@ Branch : %v
|
|||||||
Go version : %v
|
Go version : %v
|
||||||
|
|
||||||
Licensed under the Apache Public License 2.0
|
Licensed under the Apache Public License 2.0
|
||||||
Copyright 2020, Vikram Rangnekar.
|
Copyright 2020, Vikram Rangnekar
|
||||||
`,
|
`,
|
||||||
version,
|
version,
|
||||||
lastCommitSHA,
|
lastCommitSHA,
|
21
internal/serv/cmd_conf.go
Normal file
21
internal/serv/cmd_conf.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package serv
|
||||||
|
|
||||||
|
// func cmdConfDump(cmd *cobra.Command, args []string) {
|
||||||
|
// if len(args) != 1 {
|
||||||
|
// cmd.Help() //nolint: errcheck
|
||||||
|
// os.Exit(1)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fname := fmt.Sprintf("%s.%s", config.GetConfigName(), args[0])
|
||||||
|
|
||||||
|
// conf, err := initConf()
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatalf("ERR failed to read config: %s", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if err := conf.WriteConfigAs(fname); err != nil {
|
||||||
|
// log.Fatalf("ERR failed to write config: %s", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// log.Printf("INF config dumped to ./%s", fname)
|
||||||
|
// }
|
@ -9,7 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dosco/super-graph/cmd/internal/serv/internal/migrate"
|
"github.com/dosco/super-graph/internal/serv/internal/migrate"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ func cmdDBSetup(cmd *cobra.Command, args []string) {
|
|||||||
cmdDBCreate(cmd, []string{})
|
cmdDBCreate(cmd, []string{})
|
||||||
cmdDBMigrate(cmd, []string{"up"})
|
cmdDBMigrate(cmd, []string{"up"})
|
||||||
|
|
||||||
sfile := path.Join(conf.ConfigPathUsed(), conf.SeedFile)
|
sfile := path.Join(conf.cpath, conf.SeedFile)
|
||||||
_, err := os.Stat(sfile)
|
_, err := os.Stat(sfile)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -55,7 +55,7 @@ func cmdDBReset(cmd *cobra.Command, args []string) {
|
|||||||
func cmdDBCreate(cmd *cobra.Command, args []string) {
|
func cmdDBCreate(cmd *cobra.Command, args []string) {
|
||||||
initConfOnce()
|
initConfOnce()
|
||||||
|
|
||||||
db, err := initDB(conf)
|
db, err := initDB(conf, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("ERR failed to connect to database: %s", err)
|
log.Fatalf("ERR failed to connect to database: %s", err)
|
||||||
}
|
}
|
||||||
@ -74,7 +74,7 @@ func cmdDBCreate(cmd *cobra.Command, args []string) {
|
|||||||
func cmdDBDrop(cmd *cobra.Command, args []string) {
|
func cmdDBDrop(cmd *cobra.Command, args []string) {
|
||||||
initConfOnce()
|
initConfOnce()
|
||||||
|
|
||||||
db, err := initDB(conf)
|
db, err := initDB(conf, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("ERR failed to connect to database: %s", err)
|
log.Fatalf("ERR failed to connect to database: %s", err)
|
||||||
}
|
}
|
||||||
@ -131,7 +131,7 @@ func cmdDBMigrate(cmd *cobra.Command, args []string) {
|
|||||||
initConfOnce()
|
initConfOnce()
|
||||||
dest := args[0]
|
dest := args[0]
|
||||||
|
|
||||||
conn, err := initDB(conf)
|
conn, err := initDB(conf, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("ERR failed to connect to database: %s", err)
|
log.Fatalf("ERR failed to connect to database: %s", err)
|
||||||
}
|
}
|
||||||
@ -144,7 +144,7 @@ func cmdDBMigrate(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
m.Data = getMigrationVars()
|
m.Data = getMigrationVars()
|
||||||
|
|
||||||
err = m.LoadMigrations(path.Join(conf.ConfigPathUsed(), conf.MigrationsPath))
|
err = m.LoadMigrations(path.Join(conf.cpath, conf.MigrationsPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("ERR failed to load migrations: %s", err)
|
log.Fatalf("ERR failed to load migrations: %s", err)
|
||||||
}
|
}
|
||||||
@ -223,7 +223,7 @@ func cmdDBMigrate(cmd *cobra.Command, args []string) {
|
|||||||
func cmdDBStatus(cmd *cobra.Command, args []string) {
|
func cmdDBStatus(cmd *cobra.Command, args []string) {
|
||||||
initConfOnce()
|
initConfOnce()
|
||||||
|
|
||||||
db, err := initDB(conf)
|
db, err := initDB(conf, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("ERR failed to connect to database: %s", err)
|
log.Fatalf("ERR failed to connect to database: %s", err)
|
||||||
}
|
}
|
@ -13,7 +13,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/brianvoe/gofakeit"
|
"github.com/brianvoe/gofakeit/v5"
|
||||||
"github.com/dop251/goja"
|
"github.com/dop251/goja"
|
||||||
"github.com/dosco/super-graph/core"
|
"github.com/dosco/super-graph/core"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -28,19 +28,19 @@ func cmdDBSeed(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
conf.Production = false
|
conf.Production = false
|
||||||
|
|
||||||
db, err = initDB(conf)
|
db, err = initDB(conf, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("ERR failed to connect to database: %s", err)
|
log.Fatalf("ERR failed to connect to database: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sfile := path.Join(conf.ConfigPathUsed(), conf.SeedFile)
|
sfile := path.Join(conf.cpath, conf.SeedFile)
|
||||||
|
|
||||||
b, err := ioutil.ReadFile(sfile)
|
b, err := ioutil.ReadFile(sfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("ERR failed to read seed file %s: %s", sfile, err)
|
log.Fatalf("ERR failed to read seed file %s: %s", sfile, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sg, err = core.NewSuperGraph(conf, db)
|
sg, err = core.NewSuperGraph(&conf.Core, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("ERR failed to initialize Super Graph: %s", err)
|
log.Fatalf("ERR failed to initialize Super Graph: %s", err)
|
||||||
}
|
}
|
||||||
@ -232,6 +232,10 @@ func imageURL(width int, height int) string {
|
|||||||
return fmt.Sprintf("https://picsum.photos/%d/%d?%d", width, height, rand.Intn(5000))
|
return fmt.Sprintf("https://picsum.photos/%d/%d?%d", width, height, rand.Intn(5000))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getRandValue(values []string) string {
|
||||||
|
return values[rand.Intn(len(values))]
|
||||||
|
}
|
||||||
|
|
||||||
//nolint: errcheck
|
//nolint: errcheck
|
||||||
func setFakeFuncs(f *goja.Object) {
|
func setFakeFuncs(f *goja.Object) {
|
||||||
gofakeit.Seed(0)
|
gofakeit.Seed(0)
|
||||||
@ -259,7 +263,6 @@ func setFakeFuncs(f *goja.Object) {
|
|||||||
f.Set("country_abr", gofakeit.CountryAbr)
|
f.Set("country_abr", gofakeit.CountryAbr)
|
||||||
f.Set("state", gofakeit.State)
|
f.Set("state", gofakeit.State)
|
||||||
f.Set("state_abr", gofakeit.StateAbr)
|
f.Set("state_abr", gofakeit.StateAbr)
|
||||||
f.Set("status_code", gofakeit.StatusCode)
|
|
||||||
f.Set("street", gofakeit.Street)
|
f.Set("street", gofakeit.Street)
|
||||||
f.Set("street_name", gofakeit.StreetName)
|
f.Set("street_name", gofakeit.StreetName)
|
||||||
f.Set("street_number", gofakeit.StreetNumber)
|
f.Set("street_number", gofakeit.StreetNumber)
|
||||||
@ -282,12 +285,10 @@ func setFakeFuncs(f *goja.Object) {
|
|||||||
f.Set("beer_yeast", gofakeit.BeerYeast)
|
f.Set("beer_yeast", gofakeit.BeerYeast)
|
||||||
|
|
||||||
// Cars
|
// Cars
|
||||||
f.Set("vehicle", gofakeit.Vehicle)
|
f.Set("car", gofakeit.Car)
|
||||||
f.Set("vehicle_type", gofakeit.VehicleType)
|
f.Set("car_type", gofakeit.CarType)
|
||||||
f.Set("car_maker", gofakeit.CarMaker)
|
f.Set("car_maker", gofakeit.CarMaker)
|
||||||
f.Set("car_model", gofakeit.CarModel)
|
f.Set("car_model", gofakeit.CarModel)
|
||||||
f.Set("fuel_type", gofakeit.FuelType)
|
|
||||||
f.Set("transmission_gear_type", gofakeit.TransmissionGearType)
|
|
||||||
|
|
||||||
// Text
|
// Text
|
||||||
f.Set("word", gofakeit.Word)
|
f.Set("word", gofakeit.Word)
|
||||||
@ -315,7 +316,6 @@ func setFakeFuncs(f *goja.Object) {
|
|||||||
f.Set("domain_suffix", gofakeit.DomainSuffix)
|
f.Set("domain_suffix", gofakeit.DomainSuffix)
|
||||||
f.Set("ipv4_address", gofakeit.IPv4Address)
|
f.Set("ipv4_address", gofakeit.IPv4Address)
|
||||||
f.Set("ipv6_address", gofakeit.IPv6Address)
|
f.Set("ipv6_address", gofakeit.IPv6Address)
|
||||||
f.Set("simple_status_code", gofakeit.SimpleStatusCode)
|
|
||||||
f.Set("http_method", gofakeit.HTTPMethod)
|
f.Set("http_method", gofakeit.HTTPMethod)
|
||||||
f.Set("user_agent", gofakeit.UserAgent)
|
f.Set("user_agent", gofakeit.UserAgent)
|
||||||
f.Set("user_agent_firefox", gofakeit.FirefoxUserAgent)
|
f.Set("user_agent_firefox", gofakeit.FirefoxUserAgent)
|
||||||
@ -379,8 +379,8 @@ func setFakeFuncs(f *goja.Object) {
|
|||||||
//f.Set("language_abbreviation", gofakeit.LanguageAbbreviation)
|
//f.Set("language_abbreviation", gofakeit.LanguageAbbreviation)
|
||||||
|
|
||||||
// File
|
// File
|
||||||
f.Set("extension", gofakeit.Extension)
|
f.Set("file_extension", gofakeit.FileExtension)
|
||||||
f.Set("mine_type", gofakeit.MimeType)
|
f.Set("file_mine_type", gofakeit.FileMimeType)
|
||||||
|
|
||||||
// Numbers
|
// Numbers
|
||||||
f.Set("number", gofakeit.Number)
|
f.Set("number", gofakeit.Number)
|
||||||
@ -404,7 +404,7 @@ func setFakeFuncs(f *goja.Object) {
|
|||||||
f.Set("digit", gofakeit.Digit)
|
f.Set("digit", gofakeit.Digit)
|
||||||
f.Set("letter", gofakeit.Letter)
|
f.Set("letter", gofakeit.Letter)
|
||||||
f.Set("lexify", gofakeit.Lexify)
|
f.Set("lexify", gofakeit.Lexify)
|
||||||
f.Set("rand_string", gofakeit.RandString)
|
f.Set("rand_string", getRandValue)
|
||||||
f.Set("shuffle_strings", gofakeit.ShuffleStrings)
|
f.Set("shuffle_strings", gofakeit.ShuffleStrings)
|
||||||
f.Set("numerify", gofakeit.Numerify)
|
f.Set("numerify", gofakeit.Numerify)
|
||||||
|
|
@ -19,7 +19,7 @@ func cmdServ(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
initWatcher()
|
initWatcher()
|
||||||
|
|
||||||
db, err = initDB(conf)
|
db, err = initDB(conf, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalInProd(err, "failed to connect to database")
|
fatalInProd(err, "failed to connect to database")
|
||||||
}
|
}
|
||||||
@ -28,7 +28,7 @@ func cmdServ(cmd *cobra.Command, args []string) {
|
|||||||
// initResolvers()
|
// initResolvers()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
sg, err = core.NewSuperGraph(conf, db)
|
sg, err = core.NewSuperGraph(&conf.Core, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalInProd(err, "failed to initialize Super Graph")
|
fatalInProd(err, "failed to initialize Super Graph")
|
||||||
}
|
}
|
115
internal/serv/config.go
Normal file
115
internal/serv/config.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package serv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
if err := vi.ReadInConfig(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
inherits := vi.GetString("inherits")
|
||||||
|
|
||||||
|
if len(inherits) != 0 {
|
||||||
|
vi = newViper(cpath, inherits)
|
||||||
|
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
|
||||||
|
vi.SetConfigName(cfile)
|
||||||
|
|
||||||
|
if err := vi.MergeInConfig(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &Config{cpath: cpath, vi: vi}
|
||||||
|
|
||||||
|
if err := vi.Unmarshal(&c); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode config, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.Core.AllowListFile) == 0 {
|
||||||
|
c.Core.AllowListFile = path.Join(cpath, "allow.list")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newViper(configPath, configFile string) *viper.Viper {
|
||||||
|
vi := viper.New()
|
||||||
|
|
||||||
|
vi.SetEnvPrefix("SG")
|
||||||
|
vi.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
|
vi.AutomaticEnv()
|
||||||
|
|
||||||
|
vi.AddConfigPath(configPath)
|
||||||
|
vi.SetConfigName(configFile)
|
||||||
|
vi.AddConfigPath("./config")
|
||||||
|
|
||||||
|
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("seed_file", "seed.js")
|
||||||
|
|
||||||
|
vi.SetDefault("database.type", "postgres")
|
||||||
|
vi.SetDefault("database.host", "localhost")
|
||||||
|
vi.SetDefault("database.port", 5432)
|
||||||
|
vi.SetDefault("database.user", "postgres")
|
||||||
|
vi.SetDefault("database.schema", "public")
|
||||||
|
|
||||||
|
vi.SetDefault("env", "development")
|
||||||
|
|
||||||
|
vi.BindEnv("env", "GO_ENV") //nolint: errcheck
|
||||||
|
vi.BindEnv("host", "HOST") //nolint: errcheck
|
||||||
|
vi.BindEnv("port", "PORT") //nolint: errcheck
|
||||||
|
|
||||||
|
vi.SetDefault("auth.rails.max_idle", 80)
|
||||||
|
vi.SetDefault("auth.rails.max_active", 12000)
|
||||||
|
|
||||||
|
return vi
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetConfigName() string {
|
||||||
|
if len(os.Getenv("GO_ENV")) == 0 {
|
||||||
|
return "dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
ge := strings.ToLower(os.Getenv("GO_ENV"))
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(ge, "pro"):
|
||||||
|
return "prod"
|
||||||
|
|
||||||
|
case strings.HasPrefix(ge, "sta"):
|
||||||
|
return "stage"
|
||||||
|
|
||||||
|
case strings.HasPrefix(ge, "tes"):
|
||||||
|
return "test"
|
||||||
|
|
||||||
|
case strings.HasPrefix(ge, "dev"):
|
||||||
|
return "dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
return ge
|
||||||
|
}
|
@ -8,9 +8,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/dosco/super-graph/cmd/internal/serv/internal/auth"
|
|
||||||
"github.com/dosco/super-graph/config"
|
|
||||||
"github.com/dosco/super-graph/core"
|
"github.com/dosco/super-graph/core"
|
||||||
|
"github.com/dosco/super-graph/internal/serv/internal/auth"
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@ -83,7 +82,7 @@ func apiV1(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
res, err := sg.GraphQL(ct, req.Query, req.Vars)
|
res, err := sg.GraphQL(ct, req.Query, req.Vars)
|
||||||
|
|
||||||
if conf.LogLevel() >= config.LogLevelDebug {
|
if logLevel >= LogLevelDebug {
|
||||||
log.Printf("DBG query:\n%s\nsql:\n%s", req.Query, res.SQL())
|
log.Printf("DBG query:\n%s\nsql:\n%s", req.Query, res.SQL())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +93,7 @@ func apiV1(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
json.NewEncoder(w).Encode(res)
|
json.NewEncoder(w).Encode(res)
|
||||||
|
|
||||||
if conf.LogLevel() >= config.LogLevelInfo {
|
if logLevel >= LogLevelInfo {
|
||||||
zlog.Info("success",
|
zlog.Info("success",
|
||||||
zap.String("op", res.Operation()),
|
zap.String("op", res.Operation()),
|
||||||
zap.String("name", res.QueryName()),
|
zap.String("name", res.QueryName()),
|
||||||
@ -111,7 +110,7 @@ func renderErr(w http.ResponseWriter, err error, res *core.Result) {
|
|||||||
|
|
||||||
json.NewEncoder(w).Encode(&errorResp{err})
|
json.NewEncoder(w).Encode(&errorResp{err})
|
||||||
|
|
||||||
if conf.LogLevel() >= config.LogLevelError {
|
if logLevel >= LogLevelError {
|
||||||
if res != nil {
|
if res != nil {
|
||||||
zlog.Error(err.Error(),
|
zlog.Error(err.Error(),
|
||||||
zap.String("op", res.Operation()),
|
zap.String("op", res.Operation()),
|
199
internal/serv/init.go
Normal file
199
internal/serv/init.go
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
package serv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v4"
|
||||||
|
"github.com/jackc/pgx/v4/stdlib"
|
||||||
|
//_ "github.com/jackc/pgx/v4/stdlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PEM_SIG = "--BEGIN "
|
||||||
|
)
|
||||||
|
|
||||||
|
func initConf() (*Config, error) {
|
||||||
|
c, err := ReadInConfig(path.Join(confPath, GetConfigName()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch c.LogLevel {
|
||||||
|
case "debug":
|
||||||
|
logLevel = LogLevelDebug
|
||||||
|
case "error":
|
||||||
|
logLevel = LogLevelError
|
||||||
|
case "warn":
|
||||||
|
logLevel = LogLevelWarn
|
||||||
|
case "info":
|
||||||
|
logLevel = LogLevelInfo
|
||||||
|
default:
|
||||||
|
logLevel = LogLevelNone
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auths: validate and sanitize
|
||||||
|
am := make(map[string]struct{})
|
||||||
|
|
||||||
|
for i := 0; i < len(c.Auths); i++ {
|
||||||
|
a := &c.Auths[i]
|
||||||
|
a.Name = sanitize(a.Name)
|
||||||
|
|
||||||
|
if _, ok := am[a.Name]; ok {
|
||||||
|
c.Auths = append(c.Auths[:i], c.Auths[i+1:]...)
|
||||||
|
log.Printf("WRN duplicate auth found: %s", a.Name)
|
||||||
|
}
|
||||||
|
am[a.Name] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actions: validate and sanitize
|
||||||
|
axm := make(map[string]struct{})
|
||||||
|
|
||||||
|
for i := 0; i < len(c.Actions); i++ {
|
||||||
|
a := &c.Actions[i]
|
||||||
|
a.Name = sanitize(a.Name)
|
||||||
|
a.AuthName = sanitize(a.AuthName)
|
||||||
|
|
||||||
|
if _, ok := axm[a.Name]; ok {
|
||||||
|
c.Actions = append(c.Actions[:i], c.Actions[i+1:]...)
|
||||||
|
log.Printf("WRN duplicate action found: %s", a.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := am[a.AuthName]; !ok {
|
||||||
|
c.Actions = append(c.Actions[:i], c.Actions[i+1:]...)
|
||||||
|
log.Printf("WRN invalid auth_name '%s' for auth: %s", a.AuthName, a.Name)
|
||||||
|
}
|
||||||
|
axm[a.Name] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var anonFound bool
|
||||||
|
|
||||||
|
for _, r := range c.Roles {
|
||||||
|
if sanitize(r.Name) == "anon" {
|
||||||
|
anonFound = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !anonFound {
|
||||||
|
log.Printf("WRN unauthenticated requests will be blocked. no role 'anon' defined")
|
||||||
|
c.AuthFailBlock = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDB(c *Config, useDB bool) (*sql.DB, error) {
|
||||||
|
var db *sql.DB
|
||||||
|
var err error
|
||||||
|
|
||||||
|
config, _ := pgx.ParseConfig("")
|
||||||
|
config.Host = c.DB.Host
|
||||||
|
config.Port = c.DB.Port
|
||||||
|
config.User = c.DB.User
|
||||||
|
config.Password = c.DB.Password
|
||||||
|
config.RuntimeParams = map[string]string{
|
||||||
|
"application_name": c.AppName,
|
||||||
|
"search_path": c.DB.Schema,
|
||||||
|
}
|
||||||
|
|
||||||
|
if useDB {
|
||||||
|
config.Database = c.DB.DBName
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.DB.EnableTLS {
|
||||||
|
if len(c.DB.ServerName) == 0 {
|
||||||
|
return nil, errors.New("server_name is required")
|
||||||
|
}
|
||||||
|
if len(c.DB.ServerCert) == 0 {
|
||||||
|
return nil, errors.New("server_cert is required")
|
||||||
|
}
|
||||||
|
if len(c.DB.ClientCert) == 0 {
|
||||||
|
return nil, errors.New("client_cert is required")
|
||||||
|
}
|
||||||
|
if len(c.DB.ClientKey) == 0 {
|
||||||
|
return nil, errors.New("client_key is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCertPool := x509.NewCertPool()
|
||||||
|
var pem []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if strings.Contains(c.DB.ServerCert, PEM_SIG) {
|
||||||
|
pem = []byte(c.DB.ServerCert)
|
||||||
|
} else {
|
||||||
|
pem, err = ioutil.ReadFile(c.DB.ServerCert)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("db tls: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
|
||||||
|
return nil, errors.New("db tls: failed to append pem")
|
||||||
|
}
|
||||||
|
|
||||||
|
clientCert := make([]tls.Certificate, 0, 1)
|
||||||
|
var certs tls.Certificate
|
||||||
|
|
||||||
|
if strings.Contains(c.DB.ClientCert, PEM_SIG) {
|
||||||
|
certs, err = tls.X509KeyPair([]byte(c.DB.ClientCert), []byte(c.DB.ClientKey))
|
||||||
|
} else {
|
||||||
|
certs, err = tls.LoadX509KeyPair(c.DB.ClientCert, c.DB.ClientKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("db tls: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientCert = append(clientCert, certs)
|
||||||
|
config.TLSConfig = &tls.Config{
|
||||||
|
RootCAs: rootCertPool,
|
||||||
|
Certificates: clientCert,
|
||||||
|
ServerName: c.DB.ServerName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// switch c.LogLevel {
|
||||||
|
// case "debug":
|
||||||
|
// config.LogLevel = pgx.LogLevelDebug
|
||||||
|
// case "info":
|
||||||
|
// config.LogLevel = pgx.LogLevelInfo
|
||||||
|
// case "warn":
|
||||||
|
// config.LogLevel = pgx.LogLevelWarn
|
||||||
|
// case "error":
|
||||||
|
// config.LogLevel = pgx.LogLevelError
|
||||||
|
// default:
|
||||||
|
// config.LogLevel = pgx.LogLevelNone
|
||||||
|
// }
|
||||||
|
|
||||||
|
//config.Logger = NewSQLLogger(logger)
|
||||||
|
|
||||||
|
// if c.DB.MaxRetries != 0 {
|
||||||
|
// opt.MaxRetries = c.DB.MaxRetries
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if c.DB.PoolSize != 0 {
|
||||||
|
// config.MaxConns = conf.DB.PoolSize
|
||||||
|
// }
|
||||||
|
|
||||||
|
for i := 1; i < 10; i++ {
|
||||||
|
db = stdlib.OpenDB(*config)
|
||||||
|
if db == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(time.Duration(i*100) * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
@ -5,11 +5,43 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/dosco/super-graph/config"
|
|
||||||
"github.com/dosco/super-graph/core"
|
"github.com/dosco/super-graph/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SimpleHandler(ac *config.Auth, next http.Handler) (http.HandlerFunc, error) {
|
// Auth struct contains authentication related config values used by the Super Graph service
|
||||||
|
type Auth struct {
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
Cookie string
|
||||||
|
CredsInHeader bool `mapstructure:"creds_in_header"`
|
||||||
|
|
||||||
|
Rails struct {
|
||||||
|
Version string
|
||||||
|
SecretKeyBase string `mapstructure:"secret_key_base"`
|
||||||
|
URL string
|
||||||
|
Password string
|
||||||
|
MaxIdle int `mapstructure:"max_idle"`
|
||||||
|
MaxActive int `mapstructure:"max_active"`
|
||||||
|
Salt string
|
||||||
|
SignSalt string `mapstructure:"sign_salt"`
|
||||||
|
AuthSalt string `mapstructure:"auth_salt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
JWT struct {
|
||||||
|
Provider string
|
||||||
|
Secret string
|
||||||
|
PubKeyFile string `mapstructure:"public_key_file"`
|
||||||
|
PubKeyType string `mapstructure:"public_key_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
Header struct {
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
Exists bool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SimpleHandler(ac *Auth, next http.Handler) (http.HandlerFunc, error) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
@ -32,7 +64,7 @@ func SimpleHandler(ac *config.Auth, next http.Handler) (http.HandlerFunc, error)
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func HeaderHandler(ac *config.Auth, next http.Handler) (http.HandlerFunc, error) {
|
func HeaderHandler(ac *Auth, next http.Handler) (http.HandlerFunc, error) {
|
||||||
hdr := ac.Header
|
hdr := ac.Header
|
||||||
|
|
||||||
if len(hdr.Name) == 0 {
|
if len(hdr.Name) == 0 {
|
||||||
@ -64,7 +96,7 @@ func HeaderHandler(ac *config.Auth, next http.Handler) (http.HandlerFunc, error)
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithAuth(next http.Handler, ac *config.Auth) (http.Handler, error) {
|
func WithAuth(next http.Handler, ac *Auth) (http.Handler, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if ac.CredsInHeader {
|
if ac.CredsInHeader {
|
@ -7,7 +7,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
jwt "github.com/dgrijalva/jwt-go"
|
jwt "github.com/dgrijalva/jwt-go"
|
||||||
"github.com/dosco/super-graph/config"
|
|
||||||
"github.com/dosco/super-graph/core"
|
"github.com/dosco/super-graph/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,7 +15,7 @@ const (
|
|||||||
jwtAuth0 int = iota + 1
|
jwtAuth0 int = iota + 1
|
||||||
)
|
)
|
||||||
|
|
||||||
func JwtHandler(ac *config.Auth, next http.Handler) (http.HandlerFunc, error) {
|
func JwtHandler(ac *Auth, next http.Handler) (http.HandlerFunc, error) {
|
||||||
var key interface{}
|
var key interface{}
|
||||||
var jwtProvider int
|
var jwtProvider int
|
||||||
|
|
@ -9,13 +9,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bradfitz/gomemcache/memcache"
|
"github.com/bradfitz/gomemcache/memcache"
|
||||||
"github.com/dosco/super-graph/config"
|
|
||||||
"github.com/dosco/super-graph/core"
|
"github.com/dosco/super-graph/core"
|
||||||
"github.com/dosco/super-graph/cmd/internal/serv/internal/rails"
|
"github.com/dosco/super-graph/internal/serv/internal/rails"
|
||||||
"github.com/garyburd/redigo/redis"
|
"github.com/garyburd/redigo/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RailsHandler(ac *config.Auth, next http.Handler) (http.HandlerFunc, error) {
|
func RailsHandler(ac *Auth, next http.Handler) (http.HandlerFunc, error) {
|
||||||
ru := ac.Rails.URL
|
ru := ac.Rails.URL
|
||||||
|
|
||||||
if strings.HasPrefix(ru, "memcache:") {
|
if strings.HasPrefix(ru, "memcache:") {
|
||||||
@ -29,7 +28,7 @@ func RailsHandler(ac *config.Auth, next http.Handler) (http.HandlerFunc, error)
|
|||||||
return RailsCookieHandler(ac, next)
|
return RailsCookieHandler(ac, next)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RailsRedisHandler(ac *config.Auth, next http.Handler) (http.HandlerFunc, error) {
|
func RailsRedisHandler(ac *Auth, next http.Handler) (http.HandlerFunc, error) {
|
||||||
cookie := ac.Cookie
|
cookie := ac.Cookie
|
||||||
|
|
||||||
if len(cookie) == 0 {
|
if len(cookie) == 0 {
|
||||||
@ -85,7 +84,7 @@ func RailsRedisHandler(ac *config.Auth, next http.Handler) (http.HandlerFunc, er
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func RailsMemcacheHandler(ac *config.Auth, next http.Handler) (http.HandlerFunc, error) {
|
func RailsMemcacheHandler(ac *Auth, next http.Handler) (http.HandlerFunc, error) {
|
||||||
cookie := ac.Cookie
|
cookie := ac.Cookie
|
||||||
|
|
||||||
if len(cookie) == 0 {
|
if len(cookie) == 0 {
|
||||||
@ -128,7 +127,7 @@ func RailsMemcacheHandler(ac *config.Auth, next http.Handler) (http.HandlerFunc,
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func RailsCookieHandler(ac *config.Auth, next http.Handler) (http.HandlerFunc, error) {
|
func RailsCookieHandler(ac *Auth, next http.Handler) (http.HandlerFunc, error) {
|
||||||
cookie := ac.Cookie
|
cookie := ac.Cookie
|
||||||
if len(cookie) == 0 {
|
if len(cookie) == 0 {
|
||||||
return nil, fmt.Errorf("no auth.cookie defined")
|
return nil, fmt.Errorf("no auth.cookie defined")
|
||||||
@ -159,7 +158,7 @@ func RailsCookieHandler(ac *config.Auth, next http.Handler) (http.HandlerFunc, e
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func railsAuth(ac *config.Auth) (*rails.Auth, error) {
|
func railsAuth(ac *Auth) (*rails.Auth, error) {
|
||||||
secret := ac.Rails.SecretKeyBase
|
secret := ac.Rails.SecretKeyBase
|
||||||
if len(secret) == 0 {
|
if len(secret) == 0 {
|
||||||
return nil, errors.New("no auth.rails.secret_key_base defined")
|
return nil, errors.New("no auth.rails.secret_key_base defined")
|
@ -11,12 +11,11 @@ import (
|
|||||||
|
|
||||||
rice "github.com/GeertJohan/go.rice"
|
rice "github.com/GeertJohan/go.rice"
|
||||||
"github.com/NYTimes/gziphandler"
|
"github.com/NYTimes/gziphandler"
|
||||||
"github.com/dosco/super-graph/cmd/internal/serv/internal/auth"
|
"github.com/dosco/super-graph/internal/serv/internal/auth"
|
||||||
"github.com/dosco/super-graph/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func initWatcher() {
|
func initWatcher() {
|
||||||
cpath := conf.ConfigPathUsed()
|
cpath := conf.cpath
|
||||||
if conf != nil && !conf.WatchAndReload {
|
if conf != nil && !conf.WatchAndReload {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -170,7 +169,7 @@ func setActionRoutes(routes map[string]http.Handler) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func findAuth(name string) *config.Auth {
|
func findAuth(name string) *auth.Auth {
|
||||||
for _, a := range conf.Auths {
|
for _, a := range conf.Auths {
|
||||||
if strings.EqualFold(a.Name, name) {
|
if strings.EqualFold(a.Name, name) {
|
||||||
return &a
|
return &a
|
@ -133,20 +133,36 @@ database:
|
|||||||
# database ping timeout is used for db health checking
|
# database ping timeout is used for db health checking
|
||||||
ping_timeout: 1m
|
ping_timeout: 1m
|
||||||
|
|
||||||
# Define additional variables here to be used with filters
|
# Set up an secure tls encrypted db connection
|
||||||
variables:
|
enable_tls: false
|
||||||
#admin_account_id: "5"
|
|
||||||
admin_account_id: "sql:select id from users where admin = true limit 1"
|
# Required for tls. For example with Google Cloud SQL it's
|
||||||
|
# <gcp-project-id>:<cloud-sql-instance>"
|
||||||
|
# server_name: blah
|
||||||
|
|
||||||
|
# Required for tls. Can be a file path or the contents of the pem file
|
||||||
|
# server_cert: ./server-ca.pem
|
||||||
|
|
||||||
|
# Required for tls. Can be a file path or the contents of the pem file
|
||||||
|
# client_cert: ./client-cert.pem
|
||||||
|
|
||||||
|
# Required for tls. Can be a file path or the contents of the pem file
|
||||||
|
# client_key: ./client-key.pem
|
||||||
|
|
||||||
|
# Define additional variables here to be used with filters
|
||||||
|
variables:
|
||||||
|
#admin_account_id: "5"
|
||||||
|
admin_account_id: "sql:select id from users where admin = true limit 1"
|
||||||
|
|
||||||
|
|
||||||
# Field and table names that you wish to block
|
# Field and table names that you wish to block
|
||||||
blocklist:
|
blocklist:
|
||||||
- ar_internal_metadata
|
- ar_internal_metadata
|
||||||
- schema_migrations
|
- schema_migrations
|
||||||
- secret
|
- secret
|
||||||
- password
|
- password
|
||||||
- encrypted
|
- encrypted
|
||||||
- token
|
- token
|
||||||
|
|
||||||
# Create custom actions with their own api endpoints
|
# Create custom actions with their own api endpoints
|
||||||
# For example the below action will be available at /api/v1/actions/refresh_leaderboard_users
|
# For example the below action will be available at /api/v1/actions/refresh_leaderboard_users
|
@ -77,4 +77,20 @@ database:
|
|||||||
set_user_id: false
|
set_user_id: false
|
||||||
|
|
||||||
# database ping timeout is used for db health checking
|
# database ping timeout is used for db health checking
|
||||||
ping_timeout: 5m
|
ping_timeout: 5m
|
||||||
|
|
||||||
|
# Set up an secure tls encrypted db connection
|
||||||
|
enable_tls: false
|
||||||
|
|
||||||
|
# Required for tls. For example with Google Cloud SQL it's
|
||||||
|
# <gcp-project-id>:<cloud-sql-instance>"
|
||||||
|
# server_name: blah
|
||||||
|
|
||||||
|
# Required for tls. Can be a file path or the contents of the pem file
|
||||||
|
# server_cert: ./server-ca.pem
|
||||||
|
|
||||||
|
# Required for tls. Can be a file path or the contents of the pem file
|
||||||
|
# client_cert: ./client-cert.pem
|
||||||
|
|
||||||
|
# Required for tls. Can be a file path or the contents of the pem file
|
||||||
|
# client_key: ./client-key.pem
|
@ -10,20 +10,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/cespare/xxhash/v2"
|
|
||||||
"github.com/dosco/super-graph/jsn"
|
"github.com/dosco/super-graph/jsn"
|
||||||
)
|
)
|
||||||
|
|
||||||
// nolint: errcheck
|
|
||||||
func mkkey(h *xxhash.Digest, k1 string, k2 string) uint64 {
|
|
||||||
h.WriteString(k1)
|
|
||||||
h.WriteString(k2)
|
|
||||||
v := h.Sum64()
|
|
||||||
h.Reset()
|
|
||||||
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// nolint: errcheck
|
// nolint: errcheck
|
||||||
func gqlHash(b string, vars []byte, role string) string {
|
func gqlHash(b string, vars []byte, role string) string {
|
||||||
b = strings.TrimSpace(b)
|
b = strings.TrimSpace(b)
|
||||||
@ -113,12 +102,12 @@ func al(b byte) bool {
|
|||||||
func fatalInProd(err error, msg string) {
|
func fatalInProd(err error, msg string) {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
if !isDev() {
|
if isDev() {
|
||||||
|
log.Printf("ERR %s: %s", msg, err)
|
||||||
|
} else {
|
||||||
log.Fatalf("ERR %s: %s", msg, err)
|
log.Fatalf("ERR %s: %s", msg, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("ERR %s: %s", msg, err)
|
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
@ -126,3 +115,7 @@ func fatalInProd(err error, msg string) {
|
|||||||
func isDev() bool {
|
func isDev() bool {
|
||||||
return strings.HasPrefix(os.Getenv("GO_ENV"), "dev")
|
return strings.HasPrefix(os.Getenv("GO_ENV"), "dev")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sanitize(value string) string {
|
||||||
|
return strings.ToLower(strings.TrimSpace(value))
|
||||||
|
}
|
@ -7,7 +7,7 @@
|
|||||||
/coverage
|
/coverage
|
||||||
|
|
||||||
# production
|
# production
|
||||||
/build
|
# /build
|
||||||
|
|
||||||
# development
|
# development
|
||||||
/src/components/dataviz/core/*.js.map
|
/src/components/dataviz/core/*.js.map
|
30
internal/serv/web/build/asset-manifest.json
Normal file
30
internal/serv/web/build/asset-manifest.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"files": {
|
||||||
|
"main.css": "/static/css/main.c6b5c55c.chunk.css",
|
||||||
|
"main.js": "/static/js/main.04d74040.chunk.js",
|
||||||
|
"main.js.map": "/static/js/main.04d74040.chunk.js.map",
|
||||||
|
"runtime-main.js": "/static/js/runtime-main.4aea9da3.js",
|
||||||
|
"runtime-main.js.map": "/static/js/runtime-main.4aea9da3.js.map",
|
||||||
|
"static/js/2.03370bd3.chunk.js": "/static/js/2.03370bd3.chunk.js",
|
||||||
|
"static/js/2.03370bd3.chunk.js.map": "/static/js/2.03370bd3.chunk.js.map",
|
||||||
|
"index.html": "/index.html",
|
||||||
|
"precache-manifest.e33bc3c7c6774d7032c490820c96901d.js": "/precache-manifest.e33bc3c7c6774d7032c490820c96901d.js",
|
||||||
|
"service-worker.js": "/service-worker.js",
|
||||||
|
"static/css/main.c6b5c55c.chunk.css.map": "/static/css/main.c6b5c55c.chunk.css.map",
|
||||||
|
"static/media/GraphQLLanguageService.js.flow": "/static/media/GraphQLLanguageService.js.5ab204b9.flow",
|
||||||
|
"static/media/autocompleteUtils.js.flow": "/static/media/autocompleteUtils.js.4ce7ba19.flow",
|
||||||
|
"static/media/getAutocompleteSuggestions.js.flow": "/static/media/getAutocompleteSuggestions.js.7f98f032.flow",
|
||||||
|
"static/media/getDefinition.js.flow": "/static/media/getDefinition.js.4dbec62f.flow",
|
||||||
|
"static/media/getDiagnostics.js.flow": "/static/media/getDiagnostics.js.65b0979a.flow",
|
||||||
|
"static/media/getHoverInformation.js.flow": "/static/media/getHoverInformation.js.d9411837.flow",
|
||||||
|
"static/media/getOutline.js.flow": "/static/media/getOutline.js.c04e3998.flow",
|
||||||
|
"static/media/index.js.flow": "/static/media/index.js.02c24280.flow",
|
||||||
|
"static/media/logo.png": "/static/media/logo.57ee3b60.png"
|
||||||
|
},
|
||||||
|
"entrypoints": [
|
||||||
|
"static/js/runtime-main.4aea9da3.js",
|
||||||
|
"static/js/2.03370bd3.chunk.js",
|
||||||
|
"static/css/main.c6b5c55c.chunk.css",
|
||||||
|
"static/js/main.04d74040.chunk.js"
|
||||||
|
]
|
||||||
|
}
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
1
internal/serv/web/build/index.html
Normal file
1
internal/serv/web/build/index.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700|Source+Code+Pro:400,700" rel="stylesheet"><title>Super Graph - GraphQL API for Rails</title><link href="/static/css/main.c6b5c55c.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(i){function e(e){for(var r,t,n=e[0],o=e[1],u=e[2],l=0,f=[];l<n.length;l++)t=n[l],Object.prototype.hasOwnProperty.call(p,t)&&p[t]&&f.push(p[t][0]),p[t]=0;for(r in o)Object.prototype.hasOwnProperty.call(o,r)&&(i[r]=o[r]);for(s&&s(e);f.length;)f.shift()();return c.push.apply(c,u||[]),a()}function a(){for(var e,r=0;r<c.length;r++){for(var t=c[r],n=!0,o=1;o<t.length;o++){var u=t[o];0!==p[u]&&(n=!1)}n&&(c.splice(r--,1),e=l(l.s=t[0]))}return e}var t={},p={1:0},c=[];function l(e){if(t[e])return t[e].exports;var r=t[e]={i:e,l:!1,exports:{}};return i[e].call(r.exports,r,r.exports,l),r.l=!0,r.exports}l.m=i,l.c=t,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(r,e){if(1&e&&(r=l(r)),8&e)return r;if(4&e&&"object"==typeof r&&r&&r.__esModule)return r;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:r}),2&e&&"string"!=typeof r)for(var n in r)l.d(t,n,function(e){return r[e]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var r=this.webpackJsonpweb=this.webpackJsonpweb||[],n=r.push.bind(r);r.push=e,r=r.slice();for(var o=0;o<r.length;o++)e(r[o]);var s=n;a()}([])</script><script src="/static/js/2.03370bd3.chunk.js"></script><script src="/static/js/main.04d74040.chunk.js"></script></body></html>
|
@ -0,0 +1,58 @@
|
|||||||
|
self.__precacheManifest = (self.__precacheManifest || []).concat([
|
||||||
|
{
|
||||||
|
"revision": "ecdae64182d05c64e7f7f200ed03a4ed",
|
||||||
|
"url": "/index.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"revision": "6e9467dc213a3e2b84ea",
|
||||||
|
"url": "/static/css/main.c6b5c55c.chunk.css"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"revision": "c156a125990ddf5dcc51",
|
||||||
|
"url": "/static/js/2.03370bd3.chunk.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"revision": "6e9467dc213a3e2b84ea",
|
||||||
|
"url": "/static/js/main.04d74040.chunk.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"revision": "427262b6771d3f49a7c5",
|
||||||
|
"url": "/static/js/runtime-main.4aea9da3.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"revision": "5ab204b9b95c06640dbefae9a65b1db2",
|
||||||
|
"url": "/static/media/GraphQLLanguageService.js.5ab204b9.flow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"revision": "4ce7ba191f7ebee4426768f246b2f0e0",
|
||||||
|
"url": "/static/media/autocompleteUtils.js.4ce7ba19.flow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"revision": "7f98f032085704c8943ec2d1925c7c84",
|
||||||
|
"url": "/static/media/getAutocompleteSuggestions.js.7f98f032.flow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"revision": "4dbec62f1d8e8417afb9cbd19f1268c3",
|
||||||
|
"url": "/static/media/getDefinition.js.4dbec62f.flow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"revision": "65b0979ac23feca49e4411883fd8eaab",
|
||||||
|
"url": "/static/media/getDiagnostics.js.65b0979a.flow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"revision": "d94118379d362fc161aa1246bcc14d43",
|
||||||
|
"url": "/static/media/getHoverInformation.js.d9411837.flow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"revision": "c04e3998712b37a96f0bfd283fa06b52",
|
||||||
|
"url": "/static/media/getOutline.js.c04e3998.flow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"revision": "02c24280c5e4a7eb3c6cfcb079a8f1e3",
|
||||||
|
"url": "/static/media/index.js.02c24280.flow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"revision": "57ee3b6084cb9d3c754cc12d25a98035",
|
||||||
|
"url": "/static/media/logo.57ee3b60.png"
|
||||||
|
}
|
||||||
|
]);
|
39
internal/serv/web/build/service-worker.js
Normal file
39
internal/serv/web/build/service-worker.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Welcome to your Workbox-powered service worker!
|
||||||
|
*
|
||||||
|
* You'll need to register this file in your web app and you should
|
||||||
|
* disable HTTP caching for this file too.
|
||||||
|
* See https://goo.gl/nhQhGp
|
||||||
|
*
|
||||||
|
* The rest of the code is auto-generated. Please don't update this file
|
||||||
|
* directly; instead, make changes to your Workbox build configuration
|
||||||
|
* and re-run your build process.
|
||||||
|
* See https://goo.gl/2aRDsh
|
||||||
|
*/
|
||||||
|
|
||||||
|
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
|
||||||
|
|
||||||
|
importScripts(
|
||||||
|
"/precache-manifest.e33bc3c7c6774d7032c490820c96901d.js"
|
||||||
|
);
|
||||||
|
|
||||||
|
self.addEventListener('message', (event) => {
|
||||||
|
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||||
|
self.skipWaiting();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
workbox.core.clientsClaim();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The workboxSW.precacheAndRoute() method efficiently caches and responds to
|
||||||
|
* requests for URLs in the manifest.
|
||||||
|
* See https://goo.gl/S9QRab
|
||||||
|
*/
|
||||||
|
self.__precacheManifest = [].concat(self.__precacheManifest || []);
|
||||||
|
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
|
||||||
|
|
||||||
|
workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/index.html"), {
|
||||||
|
|
||||||
|
blacklist: [/^\/_/,/\/[^/?]+\.[^/]+$/],
|
||||||
|
});
|
@ -0,0 +1,2 @@
|
|||||||
|
body{margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:#0f202d}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}.playground>div:nth-child(2){height:calc(100vh - 131px)}
|
||||||
|
/*# sourceMappingURL=main.c6b5c55c.chunk.css.map */
|
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["index.css"],"names":[],"mappings":"AAAA,KACE,QAAS,CACT,SAAU,CACV,mIAEY,CACZ,kCAAmC,CACnC,iCAAkC,CAClC,wBACF,CAEA,KACE,uEAEF,CAEA,6BACE,0BACF","file":"main.c6b5c55c.chunk.css","sourcesContent":["body {\n margin: 0;\n padding: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\",\n \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\",\n sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n background-color: #0f202d;\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, \"Courier New\",\n monospace;\n}\n\n.playground > div:nth-child(2) {\n height: calc(100vh - 131px);\n}\n"]}
|
2
internal/serv/web/build/static/js/2.03370bd3.chunk.js
Normal file
2
internal/serv/web/build/static/js/2.03370bd3.chunk.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
internal/serv/web/build/static/js/main.04d74040.chunk.js
Normal file
2
internal/serv/web/build/static/js/main.04d74040.chunk.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
(this.webpackJsonpweb=this.webpackJsonpweb||[]).push([[0],{163:function(e,t,n){var r={".":61,"./":61,"./GraphQLLanguageService":117,"./GraphQLLanguageService.js":117,"./GraphQLLanguageService.js.flow":315,"./autocompleteUtils":91,"./autocompleteUtils.js":91,"./autocompleteUtils.js.flow":316,"./getAutocompleteSuggestions":77,"./getAutocompleteSuggestions.js":77,"./getAutocompleteSuggestions.js.flow":317,"./getDefinition":92,"./getDefinition.js":92,"./getDefinition.js.flow":318,"./getDiagnostics":94,"./getDiagnostics.js":94,"./getDiagnostics.js.flow":319,"./getHoverInformation":95,"./getHoverInformation.js":95,"./getHoverInformation.js.flow":320,"./getOutline":116,"./getOutline.js":116,"./getOutline.js.flow":321,"./index":61,"./index.js":61,"./index.js.flow":322};function o(e){var t=a(e);return n(t)}function a(e){if(!n.o(r,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r[e]}o.keys=function(){return Object.keys(r)},o.resolve=a,e.exports=o,o.id=163},190:function(e,t,n){"use strict";(function(e){var r=n(100),o=n(101),a=n(201),i=n(191),s=n(202),l=n(5),c=n.n(l),u=n(20),g=n(130),f=(n(441),window.fetch);window.fetch=function(){return arguments[1].credentials="include",Promise.resolve(f.apply(e,arguments))};var p=function(e){function t(){return Object(r.a)(this,t),Object(a.a)(this,Object(i.a)(t).apply(this,arguments))}return Object(s.a)(t,e),Object(o.a)(t,[{key:"render",value:function(){return c.a.createElement("div",null,c.a.createElement("header",{style:{background:"#09141b",color:"#03a9f4",letterSpacing:"0.15rem",height:"65px",display:"flex",alignItems:"center"}},c.a.createElement("h3",{style:{textDecoration:"none",margin:"0px",fontSize:"18px"}},c.a.createElement("span",{style:{textTransform:"uppercase",marginLeft:"20px",paddingRight:"10px",borderRight:"1px solid #fff"}},"Super Graph"),c.a.createElement("span",{style:{fontSize:"16px",marginLeft:"10px",color:"#fff"}},"Instant GraphQL"))),c.a.createElement(u.Provider,{store:g.store},c.a.createElement(g.Playground,{endpoint:"/api/v1/graphql",settings:"{ 'schema.polling.enable': false, 'request.credentials': 'include', 'general.betaUpdates': true, 'editor.reuseHeaders': true, 'editor.theme': 'dark' }"})))}}]),t}(l.Component);t.a=p}).call(this,n(32))},205:function(e,t,n){e.exports=n(206)},206:function(e,t,n){"use strict";n.r(t);var r=n(5),o=n.n(r),a=n(52),i=n.n(a),s=n(190);i.a.render(o.a.createElement(s.a,null),document.getElementById("root"))},441:function(e,t,n){}},[[205,1,2]]]);
|
||||||
|
//# sourceMappingURL=main.04d74040.chunk.js.map
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user