1
0
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:
Lee Brown
2019-06-24 02:07:12 -08:00
parent bdbe3c587a
commit 7b5c2a5807
7 changed files with 143 additions and 208 deletions

View File

@@ -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
}

View File

@@ -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") {

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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"))
},
},
}

View File

@@ -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 "," }}"

View File

@@ -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 }}