You've already forked golang-saas-starter-kit
mirror of
https://github.com/raseels-repos/golang-saas-starter-kit.git
synced 2025-08-08 22:36:41 +02:00
checkpoint
This commit is contained in:
@@ -17,7 +17,7 @@ import (
|
||||
)
|
||||
|
||||
// Run in the main entry point for the dbtable2crud cmd.
|
||||
func Run(db *sqlx.DB, log *log.Logger, dbName, dbTable, modelFile, modelName, templateDir, goSrcPath string) error {
|
||||
func Run(db *sqlx.DB, log *log.Logger, dbName, dbTable, modelFile, modelName, templateDir, goSrcPath string, saveChanges bool) error {
|
||||
log.SetPrefix(log.Prefix() + " : dbtable2crud")
|
||||
|
||||
// Ensure the schema is up to date
|
||||
@@ -50,13 +50,13 @@ func Run(db *sqlx.DB, log *log.Logger, dbName, dbTable, modelFile, modelName, te
|
||||
}
|
||||
|
||||
// Update the model file with new or updated code.
|
||||
err = updateModel(log, model, templateDir, tmplData)
|
||||
err = updateModel(log, model, modelFile, templateDir, tmplData, saveChanges)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the model crud file with new or updated code.
|
||||
err = updateModelCrud(db, log, dbName, dbTable, modelFile, modelName, templateDir, model, tmplData)
|
||||
err = updateModelCrud(db, log, dbName, dbTable, modelFile, modelName, templateDir, model, tmplData, saveChanges)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -93,7 +93,7 @@ func validateModel(log *log.Logger, model *modelDef) error {
|
||||
}
|
||||
|
||||
// updateModel updated the parsed code file with the new code.
|
||||
func updateModel(log *log.Logger, model *modelDef, templateDir string, tmplData map[string]interface{}) error {
|
||||
func updateModel(log *log.Logger, model *modelDef, modelFile, templateDir string, tmplData map[string]interface{}, saveChanges bool) error {
|
||||
|
||||
// Execute template and parse code to be used to compare against modelFile.
|
||||
tmplObjs, err := loadTemplateObjects(log, model, templateDir, "models.tmpl", tmplData)
|
||||
@@ -204,34 +204,24 @@ func updateModel(log *log.Logger, model *modelDef, templateDir string, tmplData
|
||||
}
|
||||
}
|
||||
|
||||
// Produce a diff after the updates have been applied.
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(curCode, model.String(), true)
|
||||
|
||||
fmt.Println(dmp.DiffPrettyText(diffs))
|
||||
if saveChanges {
|
||||
err = model.Save(modelFile)
|
||||
if err != nil {
|
||||
err = errors.WithMessagef(err, "Failed to save changes for %s to %s", model.Name, modelFile)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Produce a diff after the updates have been applied.
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(curCode, model.String(), true)
|
||||
fmt.Println(dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateModelCrud updated the parsed code file with the new code.
|
||||
func updateModelCrud(db *sqlx.DB, log *log.Logger, dbName, dbTable, modelFile, modelName, templateDir string, baseModel *modelDef, tmplData map[string]interface{}) error {
|
||||
|
||||
modelDir := filepath.Dir(modelFile)
|
||||
crudFile := filepath.Join(modelDir, FormatCamelLowerUnderscore(baseModel.Name)+".go")
|
||||
|
||||
var crudDoc *goparse.GoDocument
|
||||
if _, err := os.Stat(crudFile); os.IsNotExist(err) {
|
||||
crudDoc, err = goparse.NewGoDocument(baseModel.Package)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Parse the supplied model file.
|
||||
crudDoc, err = goparse.ParseFile(log, modelFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
func updateModelCrud(db *sqlx.DB, log *log.Logger, dbName, dbTable, modelFile, modelName, templateDir string, baseModel *modelDef, tmplData map[string]interface{}, saveChanges bool) error {
|
||||
|
||||
// Load all the updated struct fields from the base model file.
|
||||
structFields := make(map[string]map[string]modelField)
|
||||
@@ -257,12 +247,51 @@ func updateModelCrud(db *sqlx.DB, log *log.Logger, dbName, dbTable, modelFile, m
|
||||
}
|
||||
tmplData["StructFields"] = structFields
|
||||
|
||||
// Execute template and parse code to be used to compare against modelFile.
|
||||
tmplObjs, err := loadTemplateObjects(log, baseModel, templateDir, "model_crud.tmpl", tmplData)
|
||||
// Get the dir to store crud methods and test files.
|
||||
modelDir := filepath.Dir(modelFile)
|
||||
|
||||
// Process the CRUD hanlders template and write to file.
|
||||
crudFilePath := filepath.Join(modelDir, FormatCamelLowerUnderscore(baseModel.Name)+".go")
|
||||
crudTmplFile := "model_crud.tmpl"
|
||||
err := updateModelCrudFile(db, log, dbName, dbTable, templateDir, crudFilePath, crudTmplFile, baseModel, tmplData, saveChanges)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Process the CRUD test template and write to file.
|
||||
testFilePath := filepath.Join(modelDir, FormatCamelLowerUnderscore(baseModel.Name)+"_test.go")
|
||||
testTmplFile := "model_crud_test.tmpl"
|
||||
err = updateModelCrudFile(db, log, dbName, dbTable, templateDir, testFilePath, testTmplFile, baseModel, tmplData, saveChanges)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateModelCrudFile processes the input file.
|
||||
func updateModelCrudFile(db *sqlx.DB, log *log.Logger, dbName, dbTable, templateDir, crudFilePath, tmplFile string, baseModel *modelDef, tmplData map[string]interface{}, saveChanges bool) error {
|
||||
|
||||
// Execute template and parse code to be used to compare against modelFile.
|
||||
tmplObjs, err := loadTemplateObjects(log, baseModel, templateDir, tmplFile, tmplData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var crudDoc *goparse.GoDocument
|
||||
if _, err := os.Stat(crudFilePath); os.IsNotExist(err) {
|
||||
crudDoc, err = goparse.NewGoDocument(baseModel.Package)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Parse the supplied model file.
|
||||
crudDoc, err = goparse.ParseFile(log, crudFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Store the current code as a string to produce a diff.
|
||||
curCode := crudDoc.String()
|
||||
|
||||
@@ -335,44 +364,18 @@ func updateModelCrud(db *sqlx.DB, log *log.Logger, dbName, dbTable, modelFile, m
|
||||
objHeaders = []*goparse.GoObject{}
|
||||
}
|
||||
|
||||
/*
|
||||
// Set some flags to determine additional imports and need to be added.
|
||||
var hasEnum bool
|
||||
var hasPq bool
|
||||
for _, f := range crudModel.Fields {
|
||||
if f.DbColumn != nil && f.DbColumn.IsEnum {
|
||||
hasEnum = true
|
||||
}
|
||||
if strings.HasPrefix(strings.Trim(f.FieldType, "*"), "pq.") {
|
||||
hasPq = true
|
||||
}
|
||||
if saveChanges {
|
||||
err = crudDoc.Save(crudFilePath)
|
||||
if err != nil {
|
||||
err = errors.WithMessagef(err, "Failed to save changes for %s to %s", baseModel.Name, crudFilePath)
|
||||
return err
|
||||
}
|
||||
|
||||
reqImports := []string{}
|
||||
if hasEnum {
|
||||
reqImports = append(reqImports, "database/sql/driver")
|
||||
reqImports = append(reqImports, "gopkg.in/go-playground/validator.v9")
|
||||
reqImports = append(reqImports, "github.com/pkg/errors")
|
||||
}
|
||||
|
||||
if hasPq {
|
||||
reqImports = append(reqImports, "github.com/lib/pq")
|
||||
}
|
||||
|
||||
for _, in := range reqImports {
|
||||
err := model.AddImport(goparse.GoImport{Name: in})
|
||||
if err != nil {
|
||||
err = errors.WithMessagef(err, "Failed to add import %s for %s", in, crudModel.Name)
|
||||
return err
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Produce a diff after the updates have been applied.
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(curCode, crudDoc.String(), true)
|
||||
|
||||
fmt.Println(dmp.DiffPrettyText(diffs))
|
||||
} else {
|
||||
// Produce a diff after the updates have been applied.
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(curCode, crudDoc.String(), true)
|
||||
fmt.Println(dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@@ -203,7 +203,7 @@ func ParseGoObject(lines []string, depth int) (obj *GoObject, err error) {
|
||||
obj.Type = GoObjectType_Func
|
||||
|
||||
if strings.HasPrefix(firstStrip, "func (") {
|
||||
funcLine := strings.TrimLeft(strings.TrimSpace(strings.TrimLeft(firstStrip, "func ")), "(")
|
||||
funcLine := strings.TrimLeft(strings.TrimSpace(strings.Replace(firstStrip, "func ", "", 1)), "(")
|
||||
|
||||
var structName string
|
||||
pts := strings.Split(strings.Split(funcLine, ")")[0], " ")
|
||||
@@ -227,7 +227,7 @@ func ParseGoObject(lines []string, depth int) (obj *GoObject, err error) {
|
||||
|
||||
obj.Name = fmt.Sprintf("%s.%s", structName, funcName)
|
||||
} else {
|
||||
obj.Name = strings.TrimLeft(firstStrip, "func ")
|
||||
obj.Name = strings.Replace(firstStrip, "func ", "", 1)
|
||||
obj.Name = strings.Split(obj.Name, "(")[0]
|
||||
}
|
||||
} else if strings.HasSuffix(firstStrip, "struct {") || strings.HasSuffix(firstStrip, "struct{") {
|
||||
@@ -242,7 +242,7 @@ func ParseGoObject(lines []string, depth int) (obj *GoObject, err error) {
|
||||
firstStrip = strings.TrimSpace(strings.Replace(firstStrip, "type ", "", 1))
|
||||
obj.Name = strings.Split(firstStrip, " ")[0]
|
||||
} else if strings.HasPrefix(firstStrip, "package") {
|
||||
obj.Name = strings.TrimSpace(strings.TrimLeft(firstStrip, "package "))
|
||||
obj.Name = strings.TrimSpace(strings.Replace(firstStrip, "package ", "", 1))
|
||||
|
||||
obj.Type = GoObjectType_Package
|
||||
} else if strings.HasPrefix(firstStrip, "import") {
|
||||
|
@@ -15,15 +15,6 @@ func init() {
|
||||
logger = log.New(os.Stdout, "", log.LstdFlags|log.Lmicroseconds|log.Lshortfile)
|
||||
}
|
||||
|
||||
func TestParseFileModel1(t *testing.T) {
|
||||
|
||||
_, err := ParseFile(logger, "test_gofile_model1.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("got error %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMultilineVar(t *testing.T) {
|
||||
g := gomega.NewGomegaWithT(t)
|
||||
|
||||
|
@@ -1,126 +0,0 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"time"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/go-playground/validator.v9"
|
||||
)
|
||||
|
||||
// Account represents someone with access to our system.
|
||||
type Account struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Address1 string `json:"address1"`
|
||||
Address2 string `json:"address2"`
|
||||
City string `json:"city"`
|
||||
Region string `json:"region"`
|
||||
Country string `json:"country"`
|
||||
Zipcode string `json:"zipcode"`
|
||||
Status AccountStatus `json:"status"`
|
||||
Timezone string `json:"timezone"`
|
||||
SignupUserID sql.NullString `json:"signup_user_id"`
|
||||
BillingUserID sql.NullString `json:"billing_user_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ArchivedAt pq.NullTime `json:"archived_at"`
|
||||
}
|
||||
|
||||
// CreateAccountRequest contains information needed to create a new Account.
|
||||
type CreateAccountRequest struct {
|
||||
Name string `json:"name" validate:"required,unique"`
|
||||
Address1 string `json:"address1" validate:"required"`
|
||||
Address2 string `json:"address2" validate:"omitempty"`
|
||||
City string `json:"city" validate:"required"`
|
||||
Region string `json:"region" validate:"required"`
|
||||
Country string `json:"country" validate:"required"`
|
||||
Zipcode string `json:"zipcode" validate:"required"`
|
||||
Status *AccountStatus `json:"status" validate:"omitempty,oneof=active pending disabled"`
|
||||
Timezone *string `json:"timezone" validate:"omitempty"`
|
||||
SignupUserID *string `json:"signup_user_id" validate:"omitempty,uuid"`
|
||||
BillingUserID *string `json:"billing_user_id" validate:"omitempty,uuid"`
|
||||
}
|
||||
|
||||
// UpdateAccountRequest defines what information may be provided to modify an existing
|
||||
// Account. All fields are optional so clients can send just the fields they want
|
||||
// changed. It uses pointer fields so we can differentiate between a field that
|
||||
// was not provided and a field that was provided as explicitly blank. Normally
|
||||
// we do not want to use pointers to basic types but we make exceptions around
|
||||
// marshalling/unmarshalling.
|
||||
type UpdateAccountRequest struct {
|
||||
ID string `validate:"required,uuid"`
|
||||
Name *string `json:"name" validate:"omitempty,unique"`
|
||||
Address1 *string `json:"address1" validate:"omitempty"`
|
||||
Address2 *string `json:"address2" validate:"omitempty"`
|
||||
City *string `json:"city" validate:"omitempty"`
|
||||
Region *string `json:"region" validate:"omitempty"`
|
||||
Country *string `json:"country" validate:"omitempty"`
|
||||
Zipcode *string `json:"zipcode" validate:"omitempty"`
|
||||
Status *AccountStatus `json:"status" validate:"omitempty,oneof=active pending disabled"`
|
||||
Timezone *string `json:"timezone" validate:"omitempty"`
|
||||
SignupUserID *string `json:"signup_user_id" validate:"omitempty,uuid"`
|
||||
BillingUserID *string `json:"billing_user_id" validate:"omitempty,uuid"`
|
||||
}
|
||||
|
||||
// AccountFindRequest defines the possible options to search for accounts. By default
|
||||
// archived accounts will be excluded from response.
|
||||
type AccountFindRequest struct {
|
||||
Where *string
|
||||
Args []interface{}
|
||||
Order []string
|
||||
Limit *uint
|
||||
Offset *uint
|
||||
IncludedArchived bool
|
||||
}
|
||||
|
||||
// AccountStatus represents the status of an account.
|
||||
type AccountStatus string
|
||||
|
||||
// AccountStatus values define the status field of a user account.
|
||||
const (
|
||||
// AccountStatus_Active defines the state when a user can access an account.
|
||||
AccountStatus_Active AccountStatus = "active"
|
||||
// AccountStatus_Pending defined the state when an account was created but
|
||||
// not activated.
|
||||
AccountStatus_Pending AccountStatus = "pending"
|
||||
// AccountStatus_Disabled defines the state when a user has been disabled from
|
||||
// accessing an account.
|
||||
AccountStatus_Disabled AccountStatus = "disabled"
|
||||
)
|
||||
|
||||
// AccountStatus_Values provides list of valid AccountStatus values.
|
||||
var AccountStatus_Values = []AccountStatus{
|
||||
AccountStatus_Active,
|
||||
AccountStatus_Pending,
|
||||
AccountStatus_Disabled,
|
||||
}
|
||||
|
||||
// Scan supports reading the AccountStatus value from the database.
|
||||
func (s *AccountStatus) Scan(value interface{}) error {
|
||||
asBytes, ok := value.([]byte)
|
||||
if !ok {
|
||||
return errors.New("Scan source is not []byte")
|
||||
}
|
||||
*s = AccountStatus(string(asBytes))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value converts the AccountStatus value to be stored in the database.
|
||||
func (s AccountStatus) Value() (driver.Value, error) {
|
||||
v := validator.New()
|
||||
|
||||
errs := v.Var(s, "required,oneof=active invited disabled")
|
||||
if errs != nil {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
return string(s), nil
|
||||
}
|
||||
|
||||
// String converts the AccountStatus value to a string.
|
||||
func (s AccountStatus) String() string {
|
||||
return string(s)
|
||||
}
|
@@ -125,13 +125,15 @@ func main() {
|
||||
{
|
||||
Name: "dbtable2crud",
|
||||
Aliases: []string{"dbtable2crud"},
|
||||
Usage: "dbtable2crud -table=projects -file=../../internal/project/models.go -model=Project",
|
||||
Usage: "dbtable2crud -table=projects -file=../../internal/project/models.go -model=Project -saveChanges=false",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "dbtable, table"},
|
||||
cli.StringFlag{Name: "modelFile, modelfile, file"},
|
||||
cli.StringFlag{Name: "modelName, modelname, model"},
|
||||
cli.StringFlag{Name: "templateDir, templates", Value: "./templates/dbtable2crud"},
|
||||
cli.StringFlag{Name: "projectPath", Value: ""},
|
||||
cli.StringFlag{Name: "projectPath"},
|
||||
cli.BoolFlag{Name: "saveChanges, save"},
|
||||
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
dbTable := strings.TrimSpace(c.String("dbtable"))
|
||||
@@ -201,7 +203,7 @@ func main() {
|
||||
modelName = strings.Replace(modelName, " ", "", -1)
|
||||
}
|
||||
|
||||
return dbtable2crud.Run(masterDb, log, cfg.DB.Database, dbTable, modelFile, modelName, templateDir, projectPath)
|
||||
return dbtable2crud.Run(masterDb, log, cfg.DB.Database, dbTable, modelFile, modelName, templateDir, projectPath, c.Bool("saveChanges"))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@@ -31,7 +31,6 @@ var (
|
||||
)
|
||||
{{ end }}
|
||||
{{ define "Helpers"}}
|
||||
|
||||
// {{ FormatCamelLower $.Model.Name }}MapColumns is the list of columns needed for mapRowsTo{{ $.Model.Name }}
|
||||
var {{ FormatCamelLower $.Model.Name }}MapColumns = "{{ JoinStrings $.Model.ColumnNames "," }}"
|
||||
|
||||
|
@@ -0,0 +1,66 @@
|
||||
{{ define "imports"}}
|
||||
import (
|
||||
"github.com/lib/pq"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"{{ $.GoSrcPath }}/internal/platform/auth"
|
||||
"{{ $.GoSrcPath }}/internal/platform/tests"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
"github.com/pborman/uuid"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
{{ end }}
|
||||
{{ define "Globals"}}
|
||||
var test *tests.Test
|
||||
|
||||
// TestMain is the entry point for testing.
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(testMain(m))
|
||||
}
|
||||
|
||||
func testMain(m *testing.M) int {
|
||||
test = tests.New()
|
||||
defer test.TearDown()
|
||||
return m.Run()
|
||||
}
|
||||
{{ end }}
|
||||
{{ define "TestFindRequestQuery"}}
|
||||
// TestFindRequestQuery validates findRequestQuery
|
||||
func TestFindRequestQuery(t *testing.T) {
|
||||
where := "name = ? or address1 = ?"
|
||||
var (
|
||||
limit uint = 12
|
||||
offset uint = 34
|
||||
)
|
||||
|
||||
req := {{ $.Model.Name }}FindRequest{
|
||||
Where: &where,
|
||||
Args: []interface{}{
|
||||
"lee brown",
|
||||
"103 East Main St.",
|
||||
},
|
||||
Order: []string{
|
||||
"id asc",
|
||||
"created_at desc",
|
||||
},
|
||||
Limit: &limit,
|
||||
Offset: &offset,
|
||||
}
|
||||
expected := "SELECT " + accountMapColumns + " FROM " + accountTableName + " WHERE (name = ? or address1 = ?) ORDER BY id asc, created_at desc LIMIT 12 OFFSET 34"
|
||||
|
||||
res, args := findRequestQuery(req)
|
||||
|
||||
if diff := cmp.Diff(res.String(), expected); diff != "" {
|
||||
t.Fatalf("\t%s\tExpected result query to match. Diff:\n%s", tests.Failed, diff)
|
||||
}
|
||||
if diff := cmp.Diff(args, req.Args); diff != "" {
|
||||
t.Fatalf("\t%s\tExpected result query to match. Diff:\n%s", tests.Failed, diff)
|
||||
}
|
||||
}
|
||||
{{ end }}
|
Reference in New Issue
Block a user