gqlgen_sqlboiler

package module
v2.0.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 9, 2020 License: MIT Imports: 25 Imported by: 1

README

gqlgen-sqlboiler

We want developers to be able to build software faster using modern tools like GraphQL, Golang, React Native without depending on commercial providers like Firebase or AWS Amplify.

This program generates code like this between your generated gqlgen and sqlboiler with support for Relay.dev (unique id's etc). We can automatically generate the implementation of queries and mutations like create, update, delete working based on your graphql scheme and your sqlboiler models.

To make this program a success tight coupling (same naming) between your database and graphql scheme is needed at the moment. The advantage of this program is the most when you have a database already designed. However everything is created with support for change so you could write some extra GrapQL resolvers if you'd like without a problem.

Usage

  1. Generate database structs with: https://github.com/volatiletech/sqlboiler (--no-back-referencing is IMPORTANT!) e.g. sqlboiler mysql --no-back-referencing
  2. (optional, but recommended) Generate GrapQL scheme from sqlboiler structs: https://github.com/web-ridge/sqlboiler-graphql-schema
    e.g. go run github.com/web-ridge/sqlboiler-graphql-schema --output=schema.graphql --skip-input-fields=userId --directives=isAuthenticated --pagination=no
  3. Install: https://github.com/99designs/gqlgen
  4. Generate gqlgen structs + converts between gqlgen and sqlboiler with this program
    e.g. go run convert_plugin.go for file contents of that program see bottom of this readme

Done

  • Generate converts between sqlboiler structs and graphql (with relations included)
  • Generate converts between input models and sqlboiler
  • Fetch sqlboiler preloads from graphql context
  • Support for foreign keys named differently than their corresponding model
  • New plugin which generates CRUD resolvers based on mutations in graphql scheme.
  • Support one-to-one relationships inside input types.
  • Generate code which implements the generated where and search filters
  • Batch update/delete generation in resolvers (Not tested yet).
  • Enum support.
  • public errors in resolvers + optional logging via zerolog. (feel free for PR for configurable logging!)

Roadmap

  • Batch create generation in resolvers (have working version here for.PostgreSQL https://github.com/web-ridge/contact-tracing, need maybe different implementation for different ORM's?).
  • Support gqlgen multiple .graphql files
  • Edges/connections
  • Generate tests
  • Run automatic tests in Github CI/CD in https://github.com/web-ridge/gqlgen-sqlboiler-examples
  • Crud of adding/removing relationships from many-to-many on edges.
  • Support more relationships inside input types
  • Do a three-way-diff merge for changes and let user choose parts of code which should not take over generated code.

Requirements

  • Use unsigned ints for foreign keys + ids. Otherwise converts will give compile errors. Unsigned ints for id's is allso recommended since it gives you twice as big id's and id's should not be negative anyway ;)

Examples

https://github.com/web-ridge/gqlgen-sqlboiler-examples

More examples are welcome. Send a PR ;-)

Example result of this plugin

https://github.com/web-ridge/gqlgen-sqlboiler-examples/tree/master/social-network/helpers

Code snippet

func PostToGraphQL(m *models.Post) *graphql_models.Post {
	if m == nil {
		return nil
	}

	r := &graphql_models.Post{
		ID:      PostIDToGraphQL(m.ID),
		Content: m.Content,
	}

	if boilergql.UintIsFilled(m.UserID) {
		if m.R != nil && m.R.User != nil {
			r.User = UserToGraphQL(m.R.User)
		} else {
			r.User = UserWithUintID(m.UserID)
		}
	}
	if m.R != nil && m.R.Comments != nil {
		r.Comments = CommentsToGraphQL(m.R.Comments)
	}
	if m.R != nil && m.R.Images != nil {
		r.Images = ImagesToGraphQL(m.R.Images)
	}
	if m.R != nil && m.R.Likes != nil {
		r.Likes = LikesToGraphQL(m.R.Likes)
	}

	return r
}

sqlboiler.yml

mysql:
  dbname: dbname
  host: localhost
  port: 8889
  user: root
  pass: root
  sslmode: "false"
  blacklist:
    - notifications
    - jobs
    - password_resets
    - migrations
mysqldump:
  column-statistics: 0

gqlgen.yml

schema:
  - schema.graphql
exec:
  filename: graphql_models/generated.go
  package: graphql_models
model:
  filename: graphql_models/genereated_models.go
  package: graphql_models
resolver:
  filename: resolver.go
  type: Resolver

Run normal generator go run github.com/99designs/gqlgen -v

Put this in your program convert_plugin.go e.g.

// +build ignore

package main

import (
	"fmt"
	"os"

	"github.com/99designs/gqlgen/api"
	"github.com/99designs/gqlgen/codegen/config"
	gbgen "github.com/web-ridge/gqlgen-sqlboiler/v2"
)

func main() {
	cfg, err := config.LoadConfigFromDefaultLocations()
	if err != nil {
		fmt.Fprintln(os.Stderr, "failed to load config", err.Error())
		os.Exit(2)
	}

	convertHelpersDir := "helpers"
	sqlboilerDir := "models"
	gqlgenModelDir := "graphql_models"
	err = api.Generate(cfg,
		api.AddPlugin(gbgen.NewConvertPlugin(
			convertHelpersDir, // directory where convert.go, convert_input.go and preload.go should live
			sqlboilerDir,      // directory where sqlboiler files are put
			gqlgenModelDir,    // directory where gqlgen models live
		)),
		api.AddPlugin(gbgen.NewResolverPlugin(
			convertHelpersDir,
			sqlboilerDir,
			gqlgenModelDir,
			"github.com/yourauth/implementation" // leave empty if you don't have auth
		)),
	)
	if err != nil {
		fmt.Println("error!!")
		fmt.Fprintln(os.Stderr, err.Error())
		os.Exit(3)
	}
}

go run convert_plugin.go

Donate

Did we save you a lot of time? Please consider a donation so we can invest more time in this library: paypal

Documentation

Index

Constants

This section is empty.

Variables

View Source
var InputTypes = []string{"Create", "Update", "Delete"}

Functions

func HasStringPrimaryIDsInModels added in v2.0.7

func HasStringPrimaryIDsInModels(models []*Model) bool

func NameForDir

func NameForDir(dir string) string

NameForDir manually looks for package stanzas in files located in the given directory. This can be much faster than having to consult go list, because we already know exactly where to look.

func NewConvertPlugin

func NewConvertPlugin(directory, backendModelsPath, frontendModelsPath string) plugin.Plugin

func NewResolverPlugin

func NewResolverPlugin(convertHelpersDir, backendModelsPath, frontendModelsPath string, authImport string) plugin.Plugin

func PkgAndType

func PkgAndType(name string) (string, string)

take a string in the form github.com/package/blah.Type and split it into package and type

func SanitizePackageName

func SanitizePackageName(pkg string) string

Types

type BoilerField

type BoilerField struct {
	Name             string
	PluralName       string
	Type             string
	IsForeignKey     bool
	IsRequired       bool
	IsArray          bool
	IsRelation       bool
	RelationshipName string
	Relationship     BoilerModel
}

type BoilerModel

type BoilerModel struct {
	Name                  string
	PluralName            string
	Fields                []*BoilerField
	HasOrganizationID     bool
	HasUserOrganizationID bool
	HasUserID             bool
}

func FindBoilerModel

func FindBoilerModel(models []*BoilerModel, modelName string) BoilerModel

func GetBoilerModels

func GetBoilerModels(dir string) []*BoilerModel

parseModelsAndFieldsFromBoiler since these are like User.ID, User.Organization and we want them grouped by modelName and their belonging fields.

type BoilerType

type BoilerType struct {
	Name string
	Type string
}

type ColumnSetting

type ColumnSetting struct {
	Name                  string
	RelationshipModelName string
	IDAvailable           bool
}

type ConvertConfig

type ConvertConfig struct {
	IsCustom         bool
	ToBoiler         string
	ToGraphQL        string
	GraphTypeAsText  string
	BoilerTypeAsText string
}

type ConvertPlugin

type ConvertPlugin struct {
	// contains filtered or unexported fields
}

func (*ConvertPlugin) MutateConfig

func (m *ConvertPlugin) MutateConfig(originalCfg *config.Config) error

func (*ConvertPlugin) Name

func (m *ConvertPlugin) Name() string

type Enum

type Enum struct {
	Description string
	Name        string

	Values []*EnumValue
}

type EnumValue

type EnumValue struct {
	Description string
	Name        string
	NameLower   string
}

type Field

type Field struct {
	Name              string
	PluralName        string
	Type              string
	IsNumberID        bool
	IsPrimaryNumberID bool
	IsRequired        bool
	IsPlural          bool
	ConvertConfig     ConvertConfig
	// relation stuff
	IsRelation bool
	// boiler relation stuff is inside this field
	BoilerField BoilerField
	// graphql relation ship can be found here
	Relationship *Model
	IsOr         bool
	IsAnd        bool

	// Some stuff
	Description  string
	OriginalType types.Type
	Tag          string
}

type File

type File struct {
	// These are separated because the type definition of the resolver object may live in a different file from the
	//resolver method implementations, for example when extending a type in a different graphql schema file
	Objects   []*codegen.Object
	Resolvers []*Resolver

	RemainingSource string
	// contains filtered or unexported fields
}

func (*File) Imports

func (f *File) Imports() string

type Import

type Import struct {
	Alias      string
	ImportPath string
}

type Interface

type Interface struct {
	Description string
	Name        string
}

type Model

type Model struct {
	Name                  string
	PluralName            string
	BoilerModel           BoilerModel
	PrimaryKeyType        string
	Fields                []*Field
	IsNormal              bool
	IsInput               bool
	IsCreateInput         bool
	IsUpdateInput         bool
	IsNormalInput         bool
	IsPayload             bool
	IsWhere               bool
	IsFilter              bool
	PreloadMap            map[string]ColumnSetting
	HasOrganizationID     bool
	HasUserOrganizationID bool
	HasUserID             bool
	HasStringPrimaryID    bool
	// other stuff
	Description string
	PureFields  []*ast.FieldDefinition
	Implements  []string
}

func GetModelsWithInformation

func GetModelsWithInformation(enums []*Enum, cfg *config.Config, boilerModels []*BoilerModel) []*Model

type ModelBuild

type ModelBuild struct {
	BackendModelsPath   string
	FrontendModelsPath  string
	HasStringPrimaryIDs bool
	PackageName         string
	Interfaces          []*Interface
	Models              []*Model
	Enums               []*Enum
	Scalars             []string
}

type Resolver

type Resolver struct {
	Object                    *codegen.Object
	Field                     *codegen.Field
	Implementation            string
	IsSingle                  bool
	IsList                    bool
	IsCreate                  bool
	IsUpdate                  bool
	IsDelete                  bool
	IsBatchCreate             bool
	IsBatchUpdate             bool
	IsBatchDelete             bool
	BoilerWhiteList           string
	ResolveOrganizationID     bool
	ResolveUserOrganizationID bool
	ResolveUserID             bool
	Model                     Model
	InputModel                Model

	PublicErrorKey     string
	PublicErrorMessage string
}

type ResolverBuild

type ResolverBuild struct {
	*File
	HasRoot      bool
	PackageName  string
	ResolverType string
}

type ResolverPlugin

type ResolverPlugin struct {
	// contains filtered or unexported fields
}

func (*ResolverPlugin) GenerateCode

func (m *ResolverPlugin) GenerateCode(data *codegen.Data) error

func (*ResolverPlugin) Name

func (m *ResolverPlugin) Name() string

type Rewriter

type Rewriter struct {
	// contains filtered or unexported fields
}

func NewRewriter

func NewRewriter(importPath string) (*Rewriter, error)

func (*Rewriter) ExistingImports

func (r *Rewriter) ExistingImports(filename string) []Import

func (*Rewriter) GetMethodBody

func (r *Rewriter) GetMethodBody(structname string, methodname string) string

func (*Rewriter) MarkStructCopied

func (r *Rewriter) MarkStructCopied(name string)

func (*Rewriter) RemainingSource

func (r *Rewriter) RemainingSource(filename string) string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL