1
0
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
Raw Normal View History

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
}