diff --git a/cli/generator.go b/cli/generator.go new file mode 100644 index 00000000..dd1afab1 --- /dev/null +++ b/cli/generator.go @@ -0,0 +1,205 @@ +package cli + +import ( + "fmt" + "os" + "strings" + "time" +) + +type FieldInfo struct { + Name string + Type string +} + +// GenerateModelEntity membuat GORM-ready model, entity, dan file migrasi +func GenerateModelEntity(modelName string, fields []FieldInfo, baseFolder string) error { + if modelName == "" || len(fields) == 0 { + return fmt.Errorf("modelName and fields must be provided") + } + + modelsFolder := fmt.Sprintf("%s/internal/models", baseFolder) + entityFolder := fmt.Sprintf("%s/internal/entity", baseFolder) + migrationsFolder := fmt.Sprintf("%s/internal/migrations", baseFolder) + + if err := os.MkdirAll(modelsFolder, os.ModePerm); err != nil { + return err + } + if err := os.MkdirAll(entityFolder, os.ModePerm); err != nil { + return err + } + if err := os.MkdirAll(migrationsFolder, os.ModePerm); err != nil { + return err + } + + // Tulis model + modelFile := fmt.Sprintf("%s/%s.go", modelsFolder, strings.ToLower(modelName)) + if err := writeFile(modelFile, modelName, fields, true); err != nil { + return err + } + + // Tulis entity + entityFile := fmt.Sprintf("%s/%s.go", entityFolder, strings.ToLower(modelName)) + if err := writeFile(entityFile, modelName, fields, false); err != nil { + return err + } + + moduleName, err := getModuleName(baseFolder) + if err != nil { + return err + } + if err := createMigrationFile(modelName, migrationsFolder, moduleName); err != nil { + return err + } + + if err:= updateMasterMigration(migrationsFolder, modelName);err !=nil{ + return err + } + + return nil +} + +func writeFile(filename, modelName string, fields []FieldInfo, isModel bool) error { + f, err := os.Create(filename) + if err != nil { + return err + } + defer f.Close() + + var lines []string + for _, field := range fields { + lines = append(lines, fmt.Sprintf("\t%s %s `gorm:\"column:%s\"`", capitalize(field.Name), mapType(field.Type), field.Name)) + } + + var content string + if isModel { + content = fmt.Sprintf(`package models + +import ( + "time" + "gorm.io/gorm" +) + +type %s struct { + ID uint `+"`gorm:\"primaryKey\"`"+` + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `+"`gorm:\"index\"`"+` +%s +} +`, modelName, joinLines(lines)) + } else { + content = fmt.Sprintf(`package entity + +type %s struct { + ID uint +%s +} +`, modelName, joinLines(lines)) + } + + _, err = f.WriteString(content) + if err != nil { + return err + } + + fmt.Println("Create file:", filename) + return nil +} + +func createMigrationFile(modelName, migrationsFolder, moduleName string) error { + timestamp := time.Now().Format("20060102_150405") + filename := fmt.Sprintf("%s/%s_create_%s.go", migrationsFolder, timestamp, strings.ToLower(modelName)) + + content := fmt.Sprintf(`package migrations + +import ( + "%s/configs" + "%s/internal/models" +) + +// Up migrates table %s +func Up%s() { + configs.DB.AutoMigrate(&models.%s{}) +} + +// Down rolls back table %s +func Down%s() { + configs.DB.Migrator().DropTable(&models.%s{}) +} +`, moduleName, moduleName, modelName, modelName, modelName, modelName, modelName, modelName) + + return os.WriteFile(filename, []byte(content), 0644) +} + +func updateMasterMigration(migrationsFolder, modelName string) error { + masterFile := fmt.Sprintf("%s/migrate.go", migrationsFolder) + + // Jika belum ada, buat file baru + if _, err := os.Stat(masterFile); os.IsNotExist(err) { + content := `package migrations + +import "fmt" + +func MigrateAll() { + fmt.Println("Running migrations...") + Up` + modelName + `() + // Add other migrations here + fmt.Println("Migrations completed!") +} + +func RollbackAll() { + fmt.Println("Rolling back migrations...") + Down` + modelName + `() + // Add other rollbacks here + fmt.Println("Rollback completed!") +} +` + return os.WriteFile(masterFile, []byte(content), 0644) + } + + // Jika sudah ada, append import Up/Down baru jika belum ada + data, err := os.ReadFile(masterFile) + if err != nil { + return err + } + + text := string(data) + if !strings.Contains(text, "Up"+modelName+"()") { + text = strings.Replace(text, "// Add other migrations here", "Up"+modelName+"()\n\t// Add other migrations here", 1) + } + if !strings.Contains(text, "Down"+modelName+"()") { + text = strings.Replace(text, "// Add other rollbacks here", "Down"+modelName+"()\n\t// Add other rollbacks here", 1) + } + + return os.WriteFile(masterFile, []byte(text), 0644) +} + +func mapType(t string) string { + switch t { + case "string": + return "string" + case "int": + return "int" + case "float": + return "float64" + case "bool": + return "bool" + default: + return "string" + } +} + +func capitalize(s string) string { + if len(s) == 0 { + return "" + } + return strings.ToUpper(s[:1]) + s[1:] +} + +func joinLines(lines []string) string { + if len(lines) == 0 { + return "" + } + return "\n" + strings.Join(lines, "\n") +} diff --git a/cli/generatorDbConfig.go b/cli/generatorDbConfig.go new file mode 100644 index 00000000..fa37f173 --- /dev/null +++ b/cli/generatorDbConfig.go @@ -0,0 +1,60 @@ +package cli + +import ( + "fmt" + "os" + "strings" +) + +// GenerateDBConfig membuat file configs/db.go untuk berbagai DB +func GenerateDBConfig(baseFolder, dbType string) error { + configsFolder := fmt.Sprintf("%s/configs", baseFolder) + if err := os.MkdirAll(configsFolder, os.ModePerm); err != nil { + return err + } + + dbFile := fmt.Sprintf("%s/db.go", configsFolder) + + dbType = strings.ToLower(dbType) + var importLine, openLine string + + switch dbType { + case "postgres": + importLine = `"gorm.io/driver/postgres"` + openLine = `gorm.Open(postgres.Open(dsn), &gorm.Config{})` + case "mysql": + importLine = `"gorm.io/driver/mysql"` + openLine = `gorm.Open(mysql.Open(dsn), &gorm.Config{})` + case "sqlite": + importLine = `"gorm.io/driver/sqlite"` + openLine = `gorm.Open(sqlite.Open(dsn), &gorm.Config{})` + case "sqlserver": + importLine = `"gorm.io/driver/sqlserver"` + openLine = `gorm.Open(sqlserver.Open(dsn), &gorm.Config{})` + default: + return fmt.Errorf("Unsupported DB type: %s", dbType) + } + + content := fmt.Sprintf(`package configs + +import ( + "fmt" + "log" + "gorm.io/gorm" + %s +) + +var DB *gorm.DB + +func InitDB(dsn string) { + var err error + DB, err = %s + if err != nil { + log.Fatalf("Failed to connect to database: %%v", err) + } + fmt.Println("Database connected via GORM") +} +`, importLine, openLine) + + return os.WriteFile(dbFile, []byte(content), 0644) +} diff --git a/cli/getmodule.go b/cli/getmodule.go new file mode 100644 index 00000000..f2da633d --- /dev/null +++ b/cli/getmodule.go @@ -0,0 +1,31 @@ +package cli + +import ( + "bufio" + "fmt" + "os" + "strings" +) + + +func getModuleName(baseFolder string) (string, error) { + file, err := os.Open(baseFolder + "/go.mod") + if err != nil { + return "", fmt.Errorf("cannot open go.mod: %v", err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if strings.HasPrefix(line, "module ") { + return strings.TrimSpace(strings.TrimPrefix(line, "module ")), nil + } + } + + if err := scanner.Err(); err != nil { + return "", fmt.Errorf("error reading go.mod: %v", err) + } + + return "", fmt.Errorf("module name not found in go.mod") +} \ No newline at end of file diff --git a/cli/relation.go b/cli/relation.go new file mode 100644 index 00000000..44bb76fd --- /dev/null +++ b/cli/relation.go @@ -0,0 +1,47 @@ +package cli + +import ( + "fmt" + "os" + "strings" +) + +type RelationType string + +const ( + One2Many RelationType = "one2many" + Many2Many RelationType = "many2many" +) + +type RelationInfo struct { + FieldName string + Target string + Type RelationType +} + +// AddRelation menambahkan relasi ke file model +func AddRelation(modelFile string, relations []RelationInfo, modelName string) error { + content, err := os.ReadFile(modelFile) + if err != nil { + return err + } + + lines := strings.Split(string(content), "\n") + for i, line := range lines { + if strings.TrimSpace(line) == "}" { + for _, r := range relations { + var relLine string + if r.Type == One2Many { + relLine = fmt.Sprintf("\t%s []%s `gorm:\"foreignKey:%sID\"`", r.FieldName, r.Target, modelName) + } else if r.Type == Many2Many { + relLine = fmt.Sprintf("\t%s []%s `gorm:\"many2many:%s_%s\"`", r.FieldName, r.Target, strings.ToLower(modelName), strings.ToLower(r.Target)) + } + lines = append(lines[:i], append([]string{relLine}, lines[i:]...)...) + i++ + } + break + } + } + + return os.WriteFile(modelFile, []byte(strings.Join(lines, "\n")), 0644) +} diff --git a/cmd/cli.go b/cmd/cli.go new file mode 100644 index 00000000..982ec5a9 --- /dev/null +++ b/cmd/cli.go @@ -0,0 +1,77 @@ +package main + +import ( + "flag" + "fmt" + "log" + "strings" + + "gorm.io/gorm/cli" +) + +func main() { + // --- Flags --- + modelName := flag.String("name", "", "Model name, e.g.: User") + attributes := flag.String("attributes", "", "Model attributes, e.g.: name:string,email:string") + baseFolder := flag.String("folder", ".", "Base folder of the project") + relations := flag.String("relations", "", "Relations, e.g.: Products:Product:one2many,Tags:Tag:many2many") + initDB := flag.Bool("init", false, "Generate configs/db.go for supported databases") + dbType := flag.String("db", "postgres", "Database type: postgres, mysql, sqlite, sqlserver") + + flag.Parse() + + if *initDB { + if err := cli.GenerateDBConfig(*baseFolder, *dbType); err != nil { + log.Fatal("Failed to create db.go:", err) + } + fmt.Println("configs/db.go created successfully for", *dbType) + return + } + + if *modelName == "" || *attributes == "" { + fmt.Println("Use : go run main.go --name User --attributes name:string,email:string") + return + } + + fields := parseFields(*attributes) + + if err := cli.GenerateModelEntity(*modelName, fields, *baseFolder); err != nil { + log.Fatal(err) + } + + if *relations != "" { + rels := parseRelations(*relations) + modelFile := fmt.Sprintf("%s/internal/models/%s.go", *baseFolder, strings.ToLower(*modelName)) + if err := cli.AddRelation(modelFile, rels, *modelName); err != nil { + log.Fatal(err) + } + } + + fmt.Println("Done!") +} + +// --- Helpers --- +func parseFields(attr string) []cli.FieldInfo { + var fields []cli.FieldInfo + for _, a := range strings.Split(attr, ",") { + parts := strings.Split(a, ":") + if len(parts) != 2 { + log.Fatalf("Attribute format is invalid: %s", a) + } + fields = append(fields, cli.FieldInfo{Name: parts[0], Type: parts[1]}) + } + return fields +} + +func parseRelations(rel string) []cli.RelationInfo { + var rels []cli.RelationInfo + for _, r := range strings.Split(rel, ",") { + parts := strings.Split(r, ":") + if len(parts) != 3 { + log.Fatalf("Relation format is invalid: %s", r) + } + rt := cli.RelationType(parts[2]) + rels = append(rels, cli.RelationInfo{FieldName: parts[0], Target: parts[1], Type: rt}) + } + return rels +} diff --git a/internal/entity/user.go b/internal/entity/user.go new file mode 100644 index 00000000..d1e09e5c --- /dev/null +++ b/internal/entity/user.go @@ -0,0 +1,8 @@ +package entity + +type User struct { + ID uint + + Name string `gorm:"column:name"` + Email string `gorm:"column:email"` +} diff --git a/internal/migrations/20250821_051926_create_user.go b/internal/migrations/20250821_051926_create_user.go new file mode 100644 index 00000000..7fe32483 --- /dev/null +++ b/internal/migrations/20250821_051926_create_user.go @@ -0,0 +1,16 @@ +package migrations + +import ( + "gorm.io/gorm/configs" + "gorm.io/gorm/internal/models" +) + +// Up migrates table User +func UpUser() { + configs.DB.AutoMigrate(&models.User{}) +} + +// Down rolls back table User +func DownUser() { + configs.DB.Migrator().DropTable(&models.User{}) +} diff --git a/internal/migrations/migrate.go b/internal/migrations/migrate.go new file mode 100644 index 00000000..d85f0a3f --- /dev/null +++ b/internal/migrations/migrate.go @@ -0,0 +1,17 @@ +package migrations + +import "fmt" + +func MigrateAll() { + fmt.Println("Running migrations...") + UpUser() + // Add other migrations here + fmt.Println("Migrations completed!") +} + +func RollbackAll() { + fmt.Println("Rolling back migrations...") + DownUser() + // Add other rollbacks here + fmt.Println("Rollback completed!") +} diff --git a/internal/models/user.go b/internal/models/user.go new file mode 100644 index 00000000..efaffc15 --- /dev/null +++ b/internal/models/user.go @@ -0,0 +1,16 @@ +package models + +import ( + "time" + "gorm.io/gorm" +) + +type User struct { + ID uint `gorm:"primaryKey"` + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `gorm:"index"` + + Name string `gorm:"column:name"` + Email string `gorm:"column:email"` +}