Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type Hasura struct {
Secret string `yaml:"admin_secret"`
RowsLimit uint64 `yaml:"select_limit"`
EnableAggregations bool `yaml:"allow_aggregation"`
Rest *bool `yaml:"rest"`
}

// Validate -
Expand Down
23 changes: 21 additions & 2 deletions hasura/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func (api *API) Health() error {
}

// ExportMetadata -
func (api *API) ExportMetadata(data interface{}) (ExportMetadataResponse, error) {
func (api *API) ExportMetadata(data *Metadata) (ExportMetadataResponse, error) {
req := request{
Type: "export_metadata",
Args: data,
Expand All @@ -123,7 +123,7 @@ func (api *API) ExportMetadata(data interface{}) (ExportMetadataResponse, error)
}

// ReplaceMetadata -
func (api *API) ReplaceMetadata(data interface{}) error {
func (api *API) ReplaceMetadata(data *Metadata) error {
req := request{
Type: "replace_metadata",
Args: data,
Expand Down Expand Up @@ -174,3 +174,22 @@ func (api *API) DropSelectPermissions(table, role string) error {
}
return api.post("/v1/query", nil, req, nil)
}

// CreateRestEndpoint -
func (api *API) CreateRestEndpoint(name, url, queryName, collectionName string) error {
req := request{
Type: "create_rest_endpoint",
Args: map[string]interface{}{
"name": name,
"url": url,
"methods": []string{"GET"},
"definition": map[string]interface{}{
"query": map[string]interface{}{
"query_name": queryName,
"collection_name": collectionName,
},
},
},
}
return api.post("/v1/query", nil, req, nil)
}
174 changes: 111 additions & 63 deletions hasura/hasura.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package hasura

import (
"fmt"
"io/ioutil"
"os"
"reflect"
"strings"
"time"
Expand All @@ -12,6 +15,10 @@ import (
log "github.com/sirupsen/logrus"
)

const (
allowedQueries = "allowed-queries"
)

// Create - creates hasura models
func Create(hasura config.Hasura, cfg config.Database, views []string, models ...interface{}) error {
api := New(hasura.URL, hasura.Secret)
Expand All @@ -34,38 +41,34 @@ func Create(hasura config.Hasura, cfg config.Database, views []string, models ..

log.Info("Merging metadata...")
tables := make(map[string]struct{})
dataTables := metadata["tables"].([]interface{})
for i := range dataTables {
dataTable, ok := dataTables[i].(map[string]interface{})
if !ok {
continue
}
table, ok := dataTable["table"].(map[string]interface{})
if !ok {
continue
}
name := table["name"].(string)
tables[name] = struct{}{}
for i := range metadata.Tables {
tables[metadata.Tables[i].Schema.Name] = struct{}{}
}

for _, table := range export.Tables {
tableData, ok := table["table"].(map[string]interface{})
if !ok {
continue
}
name := tableData["name"]
if _, ok := tables[name.(string)]; !ok {
dataTables = append(dataTables, table)
if _, ok := tables[table.Schema.Name]; !ok {
metadata.Tables = append(metadata.Tables, table)
}
}

metadata["tables"] = dataTables
if err := createQueryCollections(api, hasura, metadata); err != nil {
return err
}

log.Info("Replacing metadata...")
if err := api.ReplaceMetadata(metadata); err != nil {
return err
}

if hasura.Rest == nil || *hasura.Rest {
log.Info("Creating REST endpoints...")
for _, query := range metadata.QueryCollections[0].Definition.Queries {
if err := api.CreateRestEndpoint(query.Name, query.Name, query.Name, allowedQueries); err != nil {
return err
}
}
}

log.Info("Tracking views...")
for i := range views {
if err := api.TrackTable("public", views[i]); err != nil {
Expand All @@ -79,7 +82,7 @@ func Create(hasura config.Hasura, cfg config.Database, views []string, models ..
if err := api.CreateSelectPermissions(views[i], "user", Permission{
Limit: hasura.RowsLimit,
AllowAggs: hasura.EnableAggregations,
Columns: "*",
Columns: Columns{"*"},
Filter: map[string]interface{}{},
}); err != nil {
return err
Expand All @@ -90,8 +93,8 @@ func Create(hasura config.Hasura, cfg config.Database, views []string, models ..
}

// Generate - creates hasura table structure in JSON from `models`. `models` should be pointer to your table models. `cfg` is DB config from YAML.
func Generate(hasura config.Hasura, cfg config.Database, models ...interface{}) (map[string]interface{}, error) {
tables := make([]interface{}, 0)
func Generate(hasura config.Hasura, cfg config.Database, models ...interface{}) (*Metadata, error) {
tables := make([]Table, 0)
schema := getSchema(cfg)
for _, model := range models {
table, err := generateOne(hasura, schema, model)
Expand All @@ -101,14 +104,14 @@ func Generate(hasura config.Hasura, cfg config.Database, models ...interface{})
tables = append(tables, table.HasuraSchema)
}

return formatMetadata(tables), nil
return newMetadata(2, tables), nil
}

type table struct {
Name string
Schema string
Columns []string
HasuraSchema map[string]interface{}
HasuraSchema Table
}

func newTable(schema, name string) table {
Expand All @@ -118,6 +121,7 @@ func newTable(schema, name string) table {
Name: name,
}
}

func generateOne(hasura config.Hasura, schema string, model interface{}) (table, error) {
value := reflect.ValueOf(model)
if value.Kind() != reflect.Ptr {
Expand All @@ -129,53 +133,26 @@ func generateOne(hasura config.Hasura, schema string, model interface{}) (table,
}

t := newTable(schema, getTableName(value, typ))
t.HasuraSchema = formatTable(t.Name, t.Schema)
t.HasuraSchema = newMetadataTable(t.Name, t.Schema)
t.Columns = getColumns(typ)

if p, ok := t.HasuraSchema["select_permissions"]; ok {
t.HasuraSchema["select_permissions"] = append(p.([]interface{}), formatSelectPermissions(hasura.RowsLimit, hasura.EnableAggregations, t.Columns...))
} else {
t.HasuraSchema["select_permissions"] = []interface{}{
formatSelectPermissions(hasura.RowsLimit, hasura.EnableAggregations, t.Columns...),
}
}
t.HasuraSchema["object_relationships"] = []interface{}{}
t.HasuraSchema["array_relationships"] = []interface{}{}
t.HasuraSchema.SelectPermissions = append(t.HasuraSchema.SelectPermissions, formatSelectPermissions(hasura.RowsLimit, hasura.EnableAggregations, t.Columns...))

return t, nil
}

func formatSelectPermissions(limit uint64, allowAggs bool, columns ...string) map[string]interface{} {
func formatSelectPermissions(limit uint64, allowAggs bool, columns ...string) SelectPermission {
if limit == 0 {
limit = 10
}
return map[string]interface{}{
"role": "user",
"permission": map[string]interface{}{
"columns": columns,
"filter": map[string]interface{}{},
"allow_aggregations": allowAggs,
"limit": limit,
},
}
}

func formatTable(name, schema string) map[string]interface{} {
return map[string]interface{}{
"table": map[string]interface{}{
"schema": schema,
"name": name,
return SelectPermission{
Role: "user",
Permission: Permission{
Columns: columns,
Filter: map[string]interface{}{},
AllowAggs: allowAggs,
Limit: limit,
},
"object_relationships": []interface{}{},
"array_relationships": []interface{}{},
"select_permissions": []interface{}{},
}
}

func formatMetadata(tables []interface{}) map[string]interface{} {
return map[string]interface{}{
"version": 2,
"tables": tables,
}
}

Expand All @@ -193,7 +170,6 @@ func getTableName(value reflect.Value, typ reflect.Type) string {
return res[0].String()
}

// TODO: parsing schema from connection string
func getSchema(cfg config.Database) string {
return "public"
}
Expand All @@ -214,3 +190,75 @@ func getColumns(typ reflect.Type) []string {
}
return columns
}

func createQueryCollections(api *API, cfg config.Hasura, metadata *Metadata) error {
if metadata == nil {
return nil
}

files, err := ioutil.ReadDir("graphql")
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}

queries := make([]Query, 0)
for i := range files {
name := files[i].Name()
if !strings.HasSuffix(name, ".graphql") {
continue
}

queryName := strings.TrimSuffix(name, ".graphql")

f, err := os.Open(fmt.Sprintf("graphql/%s", name))
if err != nil {
return err
}
defer f.Close()

data, err := ioutil.ReadAll(f)
if err != nil {
return err
}

queries = append(queries, Query{
Name: queryName,
Query: string(data),
})
}

if len(metadata.QueryCollections) > 0 && len(metadata.QueryCollections[0].Definition.Queries) > 0 {
metadata.QueryCollections[0].Definition.Queries = mergeQueries(queries, metadata.QueryCollections[0].Definition.Queries)
} else {
metadata.QueryCollections = []QueryCollection{
{
Name: allowedQueries,
Definition: Definition{
Queries: queries,
},
},
}
}

return nil
}

func mergeQueries(a []Query, b []Query) []Query {
for i := range a {
var found bool
for j := range b {
if b[j].Name == a[i].Name {
found = true
break
}
}

if !found {
b = append(b, a[i])
}
}
return b
}
Loading