Files
server/app/services/code_generator_service.go
T
2026-01-16 15:49:34 +08:00

1116 lines
30 KiB
Go

package services
import (
"embed"
"fmt"
"go/format"
"os"
"path/filepath"
"strings"
"text/template"
"time"
)
type FieldConfig struct {
Name string `json:"name"`
Label string `json:"label"`
GoType string `json:"go_type"`
DBType string `json:"db_type"`
Validators []string `json:"validators"`
Required bool `json:"required"`
Searchable bool `json:"searchable"`
Sortable bool `json:"sortable"`
SearchType string `json:"search_type"`
SearchUIType string `json:"search_ui_type"`
Dictionary string `json:"dictionary"`
ApiUrl string `json:"api_url"`
Relation *RelationConfig `json:"relation"`
}
type RelationConfig struct {
Table string `json:"table"` // 关联表名
ForeignKey string `json:"foreign_key"` // 外键字段
DisplayField string `json:"display_field"` // 显示字段
RelationType string `json:"relation_type"` // 关联类型:hasOne, belongsTo, hasMany
}
type FieldType struct {
Label string `json:"label"`
Value string `json:"value"`
GoType string `json:"go_type"`
DBType string `json:"db_type"`
Validators []string `json:"validators"`
}
type GeneratedFile struct {
Path string `json:"path"`
Content string `json:"content"`
}
type FilesExistError struct {
Files []string
}
func (e *FilesExistError) Error() string {
return fmt.Sprintf("files already exist: %v", e.Files)
}
type CodeGeneratorService interface {
Preview(moduleName, tableName string, fields []FieldConfig, fileType string, options map[string]bool) (string, error)
Save(moduleName, tableName string, fields []FieldConfig, selectedFiles []string, options map[string]bool) ([]string, error)
ForceSave(moduleName, tableName string, fields []FieldConfig, selectedFiles []string, options map[string]bool) ([]string, error)
GetFieldTypes() []FieldType
Generate(moduleName, tableName string, fields []FieldConfig, selectedFiles []string, options map[string]bool) ([]GeneratedFile, error)
}
type CodeGeneratorServiceImpl struct{}
//go:embed templates/*
var templates embed.FS
func NewCodeGeneratorService() CodeGeneratorService {
return &CodeGeneratorServiceImpl{}
}
func (s *CodeGeneratorServiceImpl) Generate(moduleName, tableName string, fields []FieldConfig, selectedFiles []string, options map[string]bool) ([]GeneratedFile, error) {
var files []GeneratedFile
generators := []struct {
fileType string
generate func(string, string, []FieldConfig, map[string]bool) (GeneratedFile, error)
}{
{"model", s.generateModel},
{"controller", s.generateController},
{"service", s.generateService},
{"request_create", s.generateRequestCreate},
{"request_update", s.generateRequestUpdate},
{"migration", s.generateMigration},
{"api", s.generateFrontendAPI},
{"list_page", s.generateFrontendListPage},
{"form_page", s.generateFrontendFormPage},
}
// Create a map for faster lookup of selected files
selectedMap := make(map[string]bool)
if len(selectedFiles) > 0 {
for _, f := range selectedFiles {
selectedMap[f] = true
}
}
for _, gen := range generators {
// If selectedFiles is provided, only generate selected files
if len(selectedFiles) > 0 && !selectedMap[gen.fileType] {
continue
}
file, err := gen.generate(moduleName, tableName, fields, options)
if err != nil {
return nil, fmt.Errorf("failed to generate %s: %w", gen.fileType, err)
}
if strings.HasSuffix(file.Path, ".go") {
formatted, err := format.Source([]byte(file.Content))
if err == nil {
file.Content = string(formatted)
}
}
files = append(files, file)
}
return files, nil
}
func (s *CodeGeneratorServiceImpl) Preview(moduleName, tableName string, fields []FieldConfig, fileType string, options map[string]bool) (string, error) {
templateName, err := s.getTemplateName(fileType)
if err != nil {
return "", err
}
templateContent, err := templates.ReadFile(templateName)
if err != nil {
return "", fmt.Errorf("failed to read template: %w", err)
}
tmpl, err := template.New("preview").Delims("<<", ">>").Parse(string(templateContent))
if err != nil {
return "", fmt.Errorf("failed to parse template: %w", err)
}
data := s.buildTemplateData(moduleName, tableName, fields, fileType, options)
var builder strings.Builder
if err := tmpl.Execute(&builder, data); err != nil {
return "", fmt.Errorf("failed to execute template: %w", err)
}
content := builder.String()
if strings.Contains(templateName, ".go") || fileType == "model" || fileType == "controller" || fileType == "service" || fileType == "request_create" || fileType == "request_update" || fileType == "migration" {
formatted, err := format.Source([]byte(content))
if err == nil {
return string(formatted), nil
}
}
return content, nil
}
func (s *CodeGeneratorServiceImpl) Save(moduleName, tableName string, fields []FieldConfig, selectedFiles []string, options map[string]bool) ([]string, error) {
files, err := s.Generate(moduleName, tableName, fields, selectedFiles, options)
if err != nil {
return nil, err
}
var existingFiles []string
for _, file := range files {
if _, err := os.Stat(file.Path); err == nil {
existingFiles = append(existingFiles, file.Path)
}
}
if len(existingFiles) > 0 {
return nil, &FilesExistError{
Files: existingFiles,
}
}
var savedFiles []string
for _, file := range files {
dir := filepath.Dir(file.Path)
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, fmt.Errorf("failed to create directory %s: %w", dir, err)
}
if err := os.WriteFile(file.Path, []byte(file.Content), 0644); err != nil {
return nil, fmt.Errorf("failed to write file %s: %w", file.Path, err)
}
savedFiles = append(savedFiles, file.Path)
}
return savedFiles, nil
}
func (s *CodeGeneratorServiceImpl) ForceSave(moduleName, tableName string, fields []FieldConfig, selectedFiles []string, options map[string]bool) ([]string, error) {
files, err := s.Generate(moduleName, tableName, fields, selectedFiles, options)
if err != nil {
return nil, err
}
var savedFiles []string
for _, file := range files {
dir := filepath.Dir(file.Path)
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, fmt.Errorf("failed to create directory %s: %w", dir, err)
}
if err := os.WriteFile(file.Path, []byte(file.Content), 0644); err != nil {
return nil, fmt.Errorf("failed to write file %s: %w", file.Path, err)
}
savedFiles = append(savedFiles, file.Path)
}
return savedFiles, nil
}
func (s *CodeGeneratorServiceImpl) GetFieldTypes() []FieldType {
return []FieldType{
{
Label: "字符串",
Value: "string",
GoType: "string",
DBType: "string",
Validators: []string{"string", "max:255"},
},
{
Label: "文本",
Value: "text",
GoType: "string",
DBType: "text",
Validators: []string{"string"},
},
{
Label: "整数",
Value: "integer",
GoType: "int",
DBType: "integer",
Validators: []string{"integer"},
},
{
Label: "小数",
Value: "decimal",
GoType: "float64",
DBType: "decimal",
Validators: []string{"numeric"},
},
{
Label: "布尔值",
Value: "boolean",
GoType: "bool",
DBType: "boolean",
Validators: []string{"boolean"},
},
{
Label: "日期",
Value: "date",
GoType: "time.Time",
DBType: "date",
Validators: []string{"date"},
},
{
Label: "日期时间",
Value: "datetime",
GoType: "time.Time",
DBType: "datetime",
Validators: []string{"date"},
},
{
Label: "时间戳",
Value: "timestamp",
GoType: "int64",
DBType: "timestamp",
Validators: []string{"integer"},
},
{
Label: "JSON",
Value: "json",
GoType: "string",
DBType: "json",
Validators: []string{"json"},
},
}
}
func (s *CodeGeneratorServiceImpl) getTemplateName(fileType string) (string, error) {
switch fileType {
case "model":
return "templates/model.tpl", nil
case "controller":
return "templates/controller.tpl", nil
case "service":
return "templates/service.tpl", nil
case "request_create":
return "templates/request_create.tpl", nil
case "request_update":
return "templates/request_update.tpl", nil
case "migration":
return "templates/migration.tpl", nil
case "api":
return "templates/api.js.tpl", nil
case "list_page":
return "templates/list_page.vue.tpl", nil
case "form_page":
return "templates/form_page.vue.tpl", nil
default:
return "", fmt.Errorf("unknown file type: %s", fileType)
}
}
func (s *CodeGeneratorServiceImpl) buildTemplateData(moduleName, tableName string, fields []FieldConfig, fileType string, options map[string]bool) interface{} {
templateFields := s.convertFieldsToTemplateFields(fields)
// Default options
hasCreate := true
hasEdit := true
hasDelete := true
if options != nil {
if val, ok := options["has_create"]; ok {
hasCreate = val
}
if val, ok := options["has_edit"]; ok {
hasEdit = val
}
if val, ok := options["has_delete"]; ok {
hasDelete = val
}
}
switch fileType {
case "model":
return struct {
ModelName string
TableName string
Fields []TemplateFieldConfig
}{
ModelName: toPascalCase(moduleName),
TableName: tableName,
Fields: templateFields,
}
case "controller":
var searchableFields []TemplateFieldConfig
var listFields []TemplateFieldConfig
for _, field := range templateFields {
if field.Searchable {
searchableFields = append(searchableFields, field)
}
if field.Relation == nil {
listFields = append(listFields, field)
}
}
return struct {
ControllerName string
ServiceName string
ModelName string
ModuleName string
SearchableFields []TemplateFieldConfig
RequestCreateName string
RequestUpdateName string
HasCreate bool
HasEdit bool
HasDelete bool
}{
ControllerName: toPascalCase(moduleName) + "Controller",
ServiceName: toPascalCase(moduleName) + "Service",
ModelName: toPascalCase(moduleName),
ModuleName: moduleName,
SearchableFields: searchableFields,
RequestCreateName: toPascalCase(moduleName) + "Create",
RequestUpdateName: toPascalCase(moduleName) + "Update",
HasCreate: hasCreate,
HasEdit: hasEdit,
HasDelete: hasDelete,
}
case "service":
var searchableFields []TemplateFieldConfig
for _, field := range templateFields {
if field.Searchable {
searchableFields = append(searchableFields, field)
}
}
return struct {
ServiceName string
ModelName string
ModuleName string
SearchableFields []TemplateFieldConfig
RequestCreateName string
RequestUpdateName string
FormFields []TemplateFieldConfig
HasCreate bool
HasEdit bool
HasDelete bool
}{
ServiceName: toPascalCase(moduleName) + "Service",
ModelName: toPascalCase(moduleName),
ModuleName: moduleName,
SearchableFields: searchableFields,
RequestCreateName: toPascalCase(moduleName) + "Create",
RequestUpdateName: toPascalCase(moduleName) + "Update",
FormFields: templateFields,
HasCreate: hasCreate,
HasEdit: hasEdit,
HasDelete: hasDelete,
}
case "request_create":
return struct {
ModuleName string
TableName string
FormFields []TemplateFieldConfig
RequestCreateName string
}{
ModuleName: moduleName,
TableName: tableName,
FormFields: templateFields,
RequestCreateName: toPascalCase(moduleName) + "Create",
}
case "request_update":
return struct {
ModuleName string
TableName string
FormFields []TemplateFieldConfig
RequestUpdateName string
}{
ModuleName: moduleName,
TableName: tableName,
FormFields: templateFields,
RequestUpdateName: toPascalCase(moduleName) + "Update",
}
case "migration":
for i := range templateFields {
templateFields[i].MigrationMethod = getMigrationMethod(templateFields[i].DBType)
}
return struct {
ModelName string
TableName string
Fields []TemplateFieldConfig
}{
ModelName: toPascalCase(moduleName),
TableName: tableName,
Fields: templateFields,
}
case "api":
return struct {
ModelName string
ModuleName string
ModuleNameK string
FormFields []TemplateFieldConfig
HasCreate bool
HasEdit bool
HasDelete bool
}{
ModelName: toPascalCase(moduleName),
ModuleName: moduleName,
ModuleNameK: toKebabCase(moduleName),
FormFields: templateFields,
HasCreate: hasCreate,
HasEdit: hasEdit,
HasDelete: hasDelete,
}
case "list_page":
var searchableFields []TemplateFieldConfig
var listFields []TemplateFieldConfig
for _, field := range templateFields {
if field.Searchable {
searchableFields = append(searchableFields, field)
}
if field.Relation == nil {
listFields = append(listFields, field)
}
}
return struct {
ModelName string
ModuleName string
ModuleNameK string
SearchableFields []TemplateFieldConfig
ListFields []TemplateFieldConfig
FormFields []TemplateFieldConfig
HasCreate bool
HasEdit bool
HasDelete bool
}{
ModelName: toPascalCase(moduleName),
ModuleName: moduleName,
ModuleNameK: toKebabCase(moduleName),
SearchableFields: searchableFields,
ListFields: listFields,
FormFields: templateFields,
HasCreate: hasCreate,
HasEdit: hasEdit,
HasDelete: hasDelete,
}
case "form_page":
return struct {
ModelName string
ModuleName string
ModuleNameK string
FormFields []TemplateFieldConfig
HasCreate bool
HasEdit bool
}{
ModelName: toPascalCase(moduleName),
ModuleName: moduleName,
ModuleNameK: toKebabCase(moduleName),
FormFields: templateFields,
HasCreate: hasCreate,
HasEdit: hasEdit,
}
default:
return struct {
ModuleName string
TableName string
FormFields []TemplateFieldConfig
RequestCreateName string
RequestUpdateName string
}{
ModuleName: moduleName,
TableName: tableName,
FormFields: templateFields,
RequestCreateName: toPascalCase(moduleName) + "Create",
RequestUpdateName: toPascalCase(moduleName) + "Update",
}
}
}
type TemplateFieldConfig struct {
Name string
Label string
FieldName string
JsonName string
GoType string
DBType string
Validators []string
Required bool
MigrationMethod string
PascalName string
Comment string
FormType string
SearchType string
SearchUIType string
Dictionary string
ApiUrl string
Sortable bool
Searchable bool
Relation *RelationConfig
}
func (s *CodeGeneratorServiceImpl) convertFieldsToTemplateFields(fields []FieldConfig) []TemplateFieldConfig {
fieldTypes := s.GetFieldTypes()
fieldTypeMap := make(map[string]FieldType)
for _, ft := range fieldTypes {
fieldTypeMap[ft.Value] = ft
}
templateFields := make([]TemplateFieldConfig, len(fields))
for i, field := range fields {
fieldType, exists := fieldTypeMap[field.DBType]
if !exists {
fieldType = fieldTypes[0]
}
templateFields[i] = TemplateFieldConfig{
Name: field.Name,
Label: field.Label,
FieldName: toPascalCase(field.Name),
JsonName: field.Name,
GoType: fieldType.GoType,
DBType: field.DBType,
Validators: field.Validators,
Required: field.Required,
PascalName: toPascalCase(field.Name),
Comment: field.Label,
FormType: getFormType(field.DBType),
SearchType: getSearchType(field.SearchType),
SearchUIType: getSearchUIType(field.SearchUIType, field.DBType),
Dictionary: field.Dictionary,
ApiUrl: getApiUrl(field.Dictionary, field.ApiUrl),
Sortable: getSortable(field.DBType),
Searchable: field.Searchable,
Relation: field.Relation,
}
}
return templateFields
}
func (s *CodeGeneratorServiceImpl) generateModel(moduleName, tableName string, fields []FieldConfig, options map[string]bool) (GeneratedFile, error) {
templateContent, err := templates.ReadFile("templates/model.tpl")
if err != nil {
return GeneratedFile{}, fmt.Errorf("failed to read model template: %w", err)
}
templateFields := s.convertFieldsToTemplateFields(fields)
data := struct {
ModelName string
TableName string
Fields []TemplateFieldConfig
}{
ModelName: toPascalCase(moduleName),
TableName: tableName,
Fields: templateFields,
}
content, err := s.executeTemplate(string(templateContent), data)
if err != nil {
return GeneratedFile{}, err
}
return GeneratedFile{
Path: fmt.Sprintf("app/models/%s.go", toSnakeCase(moduleName)),
Content: content,
}, nil
}
func (s *CodeGeneratorServiceImpl) generateController(moduleName, tableName string, fields []FieldConfig, options map[string]bool) (GeneratedFile, error) {
templateContent, err := templates.ReadFile("templates/controller.tpl")
if err != nil {
return GeneratedFile{}, fmt.Errorf("failed to read controller template: %w", err)
}
// Default options
hasCreate := true
hasEdit := true
hasDelete := true
if options != nil {
if val, ok := options["has_create"]; ok {
hasCreate = val
}
if val, ok := options["has_edit"]; ok {
hasEdit = val
}
if val, ok := options["has_delete"]; ok {
hasDelete = val
}
}
templateFields := s.convertFieldsToTemplateFields(fields)
data := struct {
ControllerName string
ServiceName string
ModelName string
ModuleName string
SearchableFields []TemplateFieldConfig
RequestCreateName string
RequestUpdateName string
HasCreate bool
HasEdit bool
HasDelete bool
}{
ControllerName: toPascalCase(moduleName) + "Controller",
ServiceName: toPascalCase(moduleName) + "Service",
ModelName: toPascalCase(moduleName),
ModuleName: moduleName,
SearchableFields: templateFields,
RequestCreateName: toPascalCase(moduleName) + "Create",
RequestUpdateName: toPascalCase(moduleName) + "Update",
HasCreate: hasCreate,
HasEdit: hasEdit,
HasDelete: hasDelete,
}
content, err := s.executeTemplate(string(templateContent), data)
if err != nil {
return GeneratedFile{}, err
}
return GeneratedFile{
Path: fmt.Sprintf("app/http/controllers/admin/%s_controller.go", toSnakeCase(moduleName)),
Content: content,
}, nil
}
func (s *CodeGeneratorServiceImpl) generateService(moduleName, tableName string, fields []FieldConfig, options map[string]bool) (GeneratedFile, error) {
templateContent, err := templates.ReadFile("templates/service.tpl")
if err != nil {
return GeneratedFile{}, fmt.Errorf("failed to read service template: %w", err)
}
// Default options
hasCreate := true
hasEdit := true
hasDelete := true
if options != nil {
if val, ok := options["has_create"]; ok {
hasCreate = val
}
if val, ok := options["has_edit"]; ok {
hasEdit = val
}
if val, ok := options["has_delete"]; ok {
hasDelete = val
}
}
templateFields := s.convertFieldsToTemplateFields(fields)
data := struct {
ServiceName string
ModelName string
ModuleName string
SearchableFields []TemplateFieldConfig
RequestCreateName string
RequestUpdateName string
FormFields []TemplateFieldConfig
HasCreate bool
HasEdit bool
HasDelete bool
}{
ServiceName: toPascalCase(moduleName) + "Service",
ModelName: toPascalCase(moduleName),
ModuleName: moduleName,
SearchableFields: templateFields,
RequestCreateName: toPascalCase(moduleName) + "Create",
RequestUpdateName: toPascalCase(moduleName) + "Update",
FormFields: templateFields,
HasCreate: hasCreate,
HasEdit: hasEdit,
HasDelete: hasDelete,
}
content, err := s.executeTemplate(string(templateContent), data)
if err != nil {
return GeneratedFile{}, err
}
return GeneratedFile{
Path: fmt.Sprintf("app/services/%s_service.go", toSnakeCase(moduleName)),
Content: content,
}, nil
}
func (s *CodeGeneratorServiceImpl) generateRequestCreate(moduleName, tableName string, fields []FieldConfig, options map[string]bool) (GeneratedFile, error) {
templateContent, err := templates.ReadFile("templates/request_create.tpl")
if err != nil {
return GeneratedFile{}, fmt.Errorf("failed to read request_create template: %w", err)
}
templateFields := s.convertFieldsToTemplateFields(fields)
data := struct {
ModuleName string
TableName string
FormFields []TemplateFieldConfig
RequestCreateName string
}{
ModuleName: moduleName,
TableName: tableName,
FormFields: templateFields,
RequestCreateName: toPascalCase(moduleName) + "Create",
}
content, err := s.executeTemplate(string(templateContent), data)
if err != nil {
return GeneratedFile{}, err
}
return GeneratedFile{
Path: fmt.Sprintf("app/http/requests/admin/%s_create.go", toSnakeCase(moduleName)),
Content: content,
}, nil
}
func (s *CodeGeneratorServiceImpl) generateRequestUpdate(moduleName, tableName string, fields []FieldConfig, options map[string]bool) (GeneratedFile, error) {
templateContent, err := templates.ReadFile("templates/request_update.tpl")
if err != nil {
return GeneratedFile{}, fmt.Errorf("failed to read request_update template: %w", err)
}
templateFields := s.convertFieldsToTemplateFields(fields)
data := struct {
ModuleName string
TableName string
FormFields []TemplateFieldConfig
RequestUpdateName string
}{
ModuleName: moduleName,
TableName: tableName,
FormFields: templateFields,
RequestUpdateName: toPascalCase(moduleName) + "Update",
}
content, err := s.executeTemplate(string(templateContent), data)
if err != nil {
return GeneratedFile{}, err
}
return GeneratedFile{
Path: fmt.Sprintf("app/http/requests/admin/%s_update.go", toSnakeCase(moduleName)),
Content: content,
}, nil
}
func (s *CodeGeneratorServiceImpl) generateMigration(moduleName, tableName string, fields []FieldConfig, options map[string]bool) (GeneratedFile, error) {
templateContent, err := templates.ReadFile("templates/migration.tpl")
if err != nil {
return GeneratedFile{}, fmt.Errorf("failed to read migration template: %w", err)
}
templateFields := s.convertFieldsToTemplateFields(fields)
for i := range templateFields {
templateFields[i].MigrationMethod = getMigrationMethod(templateFields[i].DBType)
}
timestamp := time.Now().Format("20060102150405")
data := struct {
ModelName string
TableName string
Fields []TemplateFieldConfig
Timestamp string
}{
ModelName: toPascalCase(moduleName),
TableName: tableName,
Fields: templateFields,
Timestamp: timestamp,
}
content, err := s.executeTemplate(string(templateContent), data)
if err != nil {
return GeneratedFile{}, err
}
return GeneratedFile{
Path: fmt.Sprintf("database/migrations/%s_create_%s_table.go", timestamp, toSnakeCase(tableName)),
Content: content,
}, nil
}
func (s *CodeGeneratorServiceImpl) generateFrontendAPI(moduleName, tableName string, fields []FieldConfig, options map[string]bool) (GeneratedFile, error) {
templateContent, err := templates.ReadFile("templates/api.js.tpl")
if err != nil {
return GeneratedFile{}, fmt.Errorf("failed to read api template: %w", err)
}
// Default options
hasCreate := true
hasEdit := true
hasDelete := true
if options != nil {
if val, ok := options["has_create"]; ok {
hasCreate = val
}
if val, ok := options["has_edit"]; ok {
hasEdit = val
}
if val, ok := options["has_delete"]; ok {
hasDelete = val
}
}
templateFields := s.convertFieldsToTemplateFields(fields)
data := struct {
ModelName string
ModuleName string
ModuleNameK string
FormFields []TemplateFieldConfig
HasCreate bool
HasEdit bool
HasDelete bool
}{
ModelName: toPascalCase(moduleName),
ModuleName: moduleName,
ModuleNameK: toKebabCase(moduleName),
FormFields: templateFields,
HasCreate: hasCreate,
HasEdit: hasEdit,
HasDelete: hasDelete,
}
content, err := s.executeTemplate(string(templateContent), data)
if err != nil {
return GeneratedFile{}, err
}
return GeneratedFile{
Path: fmt.Sprintf("html/src/api/%s.js", toKebabCase(moduleName)),
Content: content,
}, nil
}
func (s *CodeGeneratorServiceImpl) generateFrontendListPage(moduleName, tableName string, fields []FieldConfig, options map[string]bool) (GeneratedFile, error) {
templateContent, err := templates.ReadFile("templates/list_page.vue.tpl")
if err != nil {
return GeneratedFile{}, fmt.Errorf("failed to read list_page template: %w", err)
}
// Default options
hasCreate := true
hasEdit := true
hasDelete := true
if options != nil {
if val, ok := options["has_create"]; ok {
hasCreate = val
}
if val, ok := options["has_edit"]; ok {
hasEdit = val
}
if val, ok := options["has_delete"]; ok {
hasDelete = val
}
}
templateFields := s.convertFieldsToTemplateFields(fields)
data := struct {
ModelName string
ModuleName string
ModuleNameK string
SearchableFields []TemplateFieldConfig
ListFields []TemplateFieldConfig
FormFields []TemplateFieldConfig
HasCreate bool
HasEdit bool
HasDelete bool
}{
ModelName: toPascalCase(moduleName),
ModuleName: moduleName,
ModuleNameK: toKebabCase(moduleName),
SearchableFields: templateFields,
ListFields: templateFields,
FormFields: templateFields,
HasCreate: hasCreate,
HasEdit: hasEdit,
HasDelete: hasDelete,
}
content, err := s.executeTemplate(string(templateContent), data)
if err != nil {
return GeneratedFile{}, err
}
return GeneratedFile{
Path: fmt.Sprintf("html/src/views/%s/%sList.vue", toKebabCase(moduleName), toPascalCase(moduleName)),
Content: content,
}, nil
}
func (s *CodeGeneratorServiceImpl) generateFrontendFormPage(moduleName, tableName string, fields []FieldConfig, options map[string]bool) (GeneratedFile, error) {
templateContent, err := templates.ReadFile("templates/form_page.vue.tpl")
if err != nil {
return GeneratedFile{}, fmt.Errorf("failed to read form_page template: %w", err)
}
// Default options
hasCreate := true
hasEdit := true
if options != nil {
if val, ok := options["has_create"]; ok {
hasCreate = val
}
if val, ok := options["has_edit"]; ok {
hasEdit = val
}
}
templateFields := s.convertFieldsToTemplateFields(fields)
data := struct {
ModelName string
ModuleName string
ModuleNameK string
FormFields []TemplateFieldConfig
HasCreate bool
HasEdit bool
}{
ModelName: toPascalCase(moduleName),
ModuleName: moduleName,
ModuleNameK: toKebabCase(moduleName),
FormFields: templateFields,
HasCreate: hasCreate,
HasEdit: hasEdit,
}
content, err := s.executeTemplate(string(templateContent), data)
if err != nil {
return GeneratedFile{}, err
}
return GeneratedFile{
Path: fmt.Sprintf("html/src/views/%s/%sForm.vue", toKebabCase(moduleName), toPascalCase(moduleName)),
Content: content,
}, nil
}
func (s *CodeGeneratorServiceImpl) executeTemplate(templateContent string, data interface{}) (string, error) {
tmpl, err := template.New("code").Delims("<<", ">>").Parse(templateContent)
if err != nil {
return "", fmt.Errorf("failed to parse template: %w", err)
}
var builder strings.Builder
if err := tmpl.Execute(&builder, data); err != nil {
return "", fmt.Errorf("failed to execute template: %w", err)
}
return builder.String(), nil
}
func toSnakeCase(s string) string {
result := ""
for i, r := range s {
if i > 0 && r >= 'A' && r <= 'Z' {
result += "_"
}
result += string(r)
}
return strings.ToLower(result)
}
func toPascalCase(s string) string {
words := strings.Split(s, "_")
for i := range words {
if len(words[i]) > 0 {
words[i] = strings.ToUpper(words[i][:1]) + strings.ToLower(words[i][1:])
}
}
return strings.Join(words, "")
}
func toKebabCase(s string) string {
return strings.ReplaceAll(toSnakeCase(s), "_", "-")
}
func getMigrationMethod(dbType string) string {
switch dbType {
case "string":
return "String"
case "text":
return "Text"
case "integer":
return "Integer"
case "decimal":
return "Decimal"
case "boolean":
return "Boolean"
case "date":
return "Date"
case "datetime":
return "DateTime"
case "timestamp":
return "Timestamp"
case "json":
return "Json"
default:
return "String"
}
}
func getFormType(dbType string) string {
switch dbType {
case "string":
return "input"
case "text":
return "textarea"
case "integer":
return "input"
case "decimal":
return "input"
case "boolean":
return "switch"
case "date":
return "date-picker"
case "datetime":
return "datetime-picker"
case "timestamp":
return "datetime-picker"
case "json":
return "textarea"
default:
return "input"
}
}
func getSearchType(searchType string) string {
if searchType == "" {
return "like"
}
return searchType
}
func getSearchUIType(searchUIType string, dbType string) string {
if searchUIType != "" {
return searchUIType
}
switch dbType {
case "date":
return "date"
case "datetime", "timestamp":
return "datetime"
case "boolean":
return "select"
default:
return "input"
}
}
func getApiUrl(dictionary string, apiUrl string) string {
if apiUrl != "" {
return apiUrl
}
if dictionary != "" {
return "/options?type=" + dictionary
}
return ""
}
func getSortable(dbType string) bool {
switch dbType {
case "string", "integer", "decimal", "date", "datetime", "timestamp":
return true
case "text", "boolean", "json":
return false
default:
return true
}
}