mirror of
https://github.com/raseels-repos/golang-saas-starter-kit.git
synced 2025-06-08 23:56:37 +02:00
230 lines
6.0 KiB
Go
230 lines
6.0 KiB
Go
|
package dbtable2crud
|
||
|
|
||
|
import (
|
||
|
"log"
|
||
|
"strings"
|
||
|
|
||
|
"geeks-accelerator/oss/saas-starter-kit/example-project/tools/truss/internal/goparse"
|
||
|
"github.com/fatih/structtag"
|
||
|
"github.com/jmoiron/sqlx"
|
||
|
"github.com/pkg/errors"
|
||
|
)
|
||
|
|
||
|
// modelDef defines info about the struct and associated db table.
|
||
|
type modelDef struct {
|
||
|
*goparse.GoDocument
|
||
|
Name string
|
||
|
TableName string
|
||
|
PrimaryField string
|
||
|
PrimaryColumn string
|
||
|
PrimaryType string
|
||
|
Fields []modelField
|
||
|
FieldNames []string
|
||
|
ColumnNames []string
|
||
|
}
|
||
|
|
||
|
// modelField defines a struct field and associated db column.
|
||
|
type modelField struct {
|
||
|
ColumnName string
|
||
|
DbColumn *psqlColumn
|
||
|
FieldName string
|
||
|
FieldType string
|
||
|
FieldIsPtr bool
|
||
|
Tags *structtag.Tags
|
||
|
ApiHide bool
|
||
|
ApiRead bool
|
||
|
ApiCreate bool
|
||
|
ApiUpdate bool
|
||
|
DefaultValue string
|
||
|
}
|
||
|
|
||
|
// parseModelFile parses the entire model file and then loads the specified model struct.
|
||
|
func parseModelFile(db *sqlx.DB, log *log.Logger, dbName, dbTable, modelFile, modelName string) (*modelDef, error) {
|
||
|
|
||
|
// Parse the supplied model file.
|
||
|
doc, err := goparse.ParseFile(log, modelFile)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Init new modelDef.
|
||
|
model := &modelDef{
|
||
|
GoDocument: doc,
|
||
|
Name: modelName,
|
||
|
TableName: dbTable,
|
||
|
}
|
||
|
|
||
|
// Append the field the the model def.
|
||
|
model.Fields, err = parseModelFields(doc, modelName, nil)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
for _, sf := range model.Fields {
|
||
|
model.FieldNames = append(model.FieldNames, sf.FieldName)
|
||
|
model.ColumnNames = append(model.ColumnNames, sf.ColumnName)
|
||
|
}
|
||
|
|
||
|
// Query the database for a table definition.
|
||
|
dbCols, err := descTable(db, dbName, dbTable)
|
||
|
if err != nil {
|
||
|
return model, err
|
||
|
}
|
||
|
|
||
|
// Loop over all the database table columns and append to the associated
|
||
|
// struct field. Don't force all database table columns to be defined in the
|
||
|
// in the struct.
|
||
|
for _, dbCol := range dbCols {
|
||
|
for idx, sf := range model.Fields {
|
||
|
if sf.ColumnName != dbCol.Column {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if dbCol.IsPrimaryKey {
|
||
|
model.PrimaryColumn = sf.ColumnName
|
||
|
model.PrimaryField = sf.FieldName
|
||
|
model.PrimaryType = sf.FieldType
|
||
|
}
|
||
|
|
||
|
if dbCol.DefaultValue != nil {
|
||
|
sf.DefaultValue = *dbCol.DefaultValue
|
||
|
|
||
|
if dbCol.IsEnum {
|
||
|
sf.DefaultValue = strings.Trim(sf.DefaultValue, "'")
|
||
|
sf.DefaultValue = sf.FieldType + "_" + FormatCamel(sf.DefaultValue)
|
||
|
} else if strings.HasPrefix(sf.DefaultValue, "'") {
|
||
|
sf.DefaultValue = strings.Trim(sf.DefaultValue, "'")
|
||
|
sf.DefaultValue = "\"" + sf.DefaultValue + "\""
|
||
|
}
|
||
|
}
|
||
|
|
||
|
c := dbCol
|
||
|
sf.DbColumn = &c
|
||
|
model.Fields[idx] = sf
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Print out the model for debugging.
|
||
|
//modelJSON, err := json.MarshalIndent(model, "", " ")
|
||
|
//if err != nil {
|
||
|
// return model, errors.WithStack(err )
|
||
|
//}
|
||
|
//log.Printf(string(modelJSON))
|
||
|
|
||
|
return model, nil
|
||
|
}
|
||
|
|
||
|
// parseModelFields parses the fields from a struct.
|
||
|
func parseModelFields(doc *goparse.GoDocument, modelName string, baseModel *modelDef) ([]modelField, error) {
|
||
|
|
||
|
// Ensure the model file has a struct with the model name supplied.
|
||
|
if !doc.HasType(modelName, goparse.GoObjectType_Struct) {
|
||
|
err := errors.Errorf("Struct with the name %s does not exist", modelName)
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Load the struct from parsed go file.
|
||
|
docModel := doc.Get(modelName, goparse.GoObjectType_Struct)
|
||
|
|
||
|
// Loop over all the objects contained between the struct definition start and end.
|
||
|
// This should be a list of variables defined for model.
|
||
|
resp := []modelField{}
|
||
|
for _, l := range docModel.Objects().List() {
|
||
|
|
||
|
// Skip all lines that are not a var.
|
||
|
if l.Type != goparse.GoObjectType_Line {
|
||
|
log.Printf("parseModelFile : Model %s has line that is %s, not type line, skipping - %s\n", modelName, l.Type, l.String())
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Extract the var name, type and defined tags from the line.
|
||
|
sv, err := goparse.ParseStructProp(l)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Init new modelField for the struct var.
|
||
|
sf := modelField{
|
||
|
FieldName: sv.Name,
|
||
|
FieldType: sv.Type,
|
||
|
FieldIsPtr: strings.HasPrefix(sv.Type, "*"),
|
||
|
Tags: sv.Tags,
|
||
|
}
|
||
|
|
||
|
// Extract the column name from the var tags.
|
||
|
if sf.Tags != nil {
|
||
|
// First try to get the column name from the db tag.
|
||
|
dbt, err := sf.Tags.Get("db")
|
||
|
if err != nil && !strings.Contains(err.Error(), "not exist") {
|
||
|
err = errors.WithStack(err)
|
||
|
return nil, err
|
||
|
} else if dbt != nil {
|
||
|
sf.ColumnName = dbt.Name
|
||
|
}
|
||
|
|
||
|
// Second try to get the column name from the json tag.
|
||
|
if sf.ColumnName == "" {
|
||
|
jt, err := sf.Tags.Get("json")
|
||
|
if err != nil && !strings.Contains(err.Error(), "not exist") {
|
||
|
err = errors.WithStack(err)
|
||
|
return nil, err
|
||
|
} else if jt != nil && jt.Name != "-" {
|
||
|
sf.ColumnName = jt.Name
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var apiActionsSet bool
|
||
|
tt, err := sf.Tags.Get("truss")
|
||
|
if err != nil && !strings.Contains(err.Error(), "not exist") {
|
||
|
err = errors.WithStack(err)
|
||
|
return nil, err
|
||
|
} else if tt != nil {
|
||
|
if tt.Name == "api-create" || tt.HasOption("api-create") {
|
||
|
sf.ApiCreate = true
|
||
|
apiActionsSet = true
|
||
|
}
|
||
|
if tt.Name == "api-read" || tt.HasOption("api-read") {
|
||
|
sf.ApiRead = true
|
||
|
apiActionsSet = true
|
||
|
}
|
||
|
if tt.Name == "api-update" || tt.HasOption("api-update") {
|
||
|
sf.ApiUpdate = true
|
||
|
apiActionsSet = true
|
||
|
}
|
||
|
if tt.Name == "api-hide" || tt.HasOption("api-hide") {
|
||
|
sf.ApiHide = true
|
||
|
apiActionsSet = true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !apiActionsSet {
|
||
|
sf.ApiCreate = true
|
||
|
sf.ApiRead = true
|
||
|
sf.ApiUpdate = true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Set the column name to the field name if empty and does not equal '-'.
|
||
|
if sf.ColumnName == "" {
|
||
|
sf.ColumnName = sf.FieldName
|
||
|
}
|
||
|
|
||
|
// If a base model as already been parsed with the db columns,
|
||
|
// append to the current field.
|
||
|
if baseModel != nil {
|
||
|
for _, baseSf := range baseModel.Fields {
|
||
|
if baseSf.ColumnName == sf.ColumnName {
|
||
|
sf.DefaultValue = baseSf.DefaultValue
|
||
|
sf.DbColumn = baseSf.DbColumn
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Append the field the the model def.
|
||
|
resp = append(resp, sf)
|
||
|
}
|
||
|
|
||
|
return resp, nil
|
||
|
}
|