diff --git a/.vscode/launch.json b/.vscode/launch.json index e542bfc3..7098c42c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,12 +7,12 @@ "mode": "auto", "program": "${workspaceFolder}/cmd/main-api" }, - // { - // "name": "Launch Package excel migrator", - // "type": "go", - // "request": "launch", - // "mode": "auto", - // "program": "${workspaceFolder}/cmd/excelmigrator" - // } + { + "name": "Launch Package migratioon", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/cmd/migration" + } ] } \ No newline at end of file diff --git a/atlas.hcl b/atlas.hcl new file mode 100644 index 00000000..d00e8155 --- /dev/null +++ b/atlas.hcl @@ -0,0 +1,7 @@ +env "dev" { + url = "postgres://moko:password@localhost:5432/simrs_vx1?sslmode=disable" + dev = "docker://postgres/17/dev?search_path=public" + migration { + dir = "file://migrations" + } +} \ No newline at end of file diff --git a/cmd/migration/migrations/20250814082958_auto_migration.sql b/cmd/migration/migrations/20250814082958_auto_migration.sql new file mode 100644 index 00000000..e69f151c --- /dev/null +++ b/cmd/migration/migrations/20250814082958_auto_migration.sql @@ -0,0 +1,12 @@ +-- Create "User" table +CREATE TABLE "User" ( + "Id" bigserial NOT NULL, + "CreatedAt" text NULL, + "UpdatedAt" text NULL, + "DeteledAt" timestamptz NULL, + "Name" character varying(25) NOT NULL, + "Password" character varying(255) NOT NULL, + "Status_Code" character varying(10) NOT NULL, + "FailedLoginCount" smallint NULL, + PRIMARY KEY ("Id") +); diff --git a/cmd/migration/migrations/20250814083106_auto_migration.sql b/cmd/migration/migrations/20250814083106_auto_migration.sql new file mode 100644 index 00000000..1e80bf43 --- /dev/null +++ b/cmd/migration/migrations/20250814083106_auto_migration.sql @@ -0,0 +1,2 @@ +-- Modify "User" table +ALTER TABLE "User" ALTER COLUMN "Status_Code" TYPE character varying(11); diff --git a/cmd/migration/migrations/20250814085334_auto_migration.sql b/cmd/migration/migrations/20250814085334_auto_migration.sql new file mode 100644 index 00000000..e2b2a560 --- /dev/null +++ b/cmd/migration/migrations/20250814085334_auto_migration.sql @@ -0,0 +1,2 @@ +-- Modify "User" table +ALTER TABLE "User" ALTER COLUMN "CreatedAt" TYPE timestamptz; diff --git a/cmd/migration/migrations/atlas.sum b/cmd/migration/migrations/atlas.sum new file mode 100644 index 00000000..493c211f --- /dev/null +++ b/cmd/migration/migrations/atlas.sum @@ -0,0 +1,4 @@ +h1:6uh16WKu8m2VzaFju4mLFjCrEA9FJmxEYP/bKU9bLV4= +20250814082958_auto_migration.sql h1:r1gxPLhQuUmRZhfBomI2gGVA1hR7B4eXF3bYJGA9uVE= +20250814083106_auto_migration.sql h1:CeUsjDrrfxEl+VGDX+azPCXSu88HKZr+VTzAba4p/Vk= +20250814085334_auto_migration.sql h1:RokGeINUPr9iFKuQRyx9HHVVm5HS6fxyTN91gcG1Hgc= diff --git a/go.mod b/go.mod index 21d0e33e..3d7e86f5 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,9 @@ toolchain go1.23.11 require ( github.com/karincake/apem v0.0.16-g github.com/karincake/dodol v0.0.1 + github.com/karincake/getuk v0.1.0 github.com/karincake/lepet v0.0.1 + golang.org/x/crypto v0.40.0 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/postgres v1.5.7 gorm.io/gorm v1.25.10 @@ -26,11 +28,8 @@ require ( github.com/mattn/go-isatty v0.0.19 // indirect github.com/nxadm/tail v1.4.11 // indirect github.com/rs/zerolog v1.33.0 // indirect - golang.org/x/crypto v0.40.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/net v0.42.0 // indirect golang.org/x/sys v0.34.0 // indirect golang.org/x/text v0.27.0 // indirect ) - -replace github.com/karincake/apem => D:/Kuli/Sabbi/external/apem diff --git a/go.sum b/go.sum index 7d1e7a14..1550a098 100644 --- a/go.sum +++ b/go.sum @@ -20,8 +20,12 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/karincake/apem v0.0.16-g h1:jPIr/YiaJhVSftdA1PyB2tlDiQtFeTVZohO1qf0qpw0= +github.com/karincake/apem v0.0.16-g/go.mod h1:cQP2sJfDrLRIiwWoaLWw/z8uAya+DWu/FpmYeinMQXM= github.com/karincake/dodol v0.0.1 h1:jUXmJh1r0Ei4fmHPZ6IUkoplW/V9d27L63JEl6zudL0= github.com/karincake/dodol v0.0.1/go.mod h1:2f1NcvkvY0J3GMUkwILNDYVvRUpz0W3lpPp/Ha/Ld24= +github.com/karincake/getuk v0.1.0 h1:jcIsASrr0UDE528GN7Ua6n9UFyRgUypsWh8Or8wzCO0= +github.com/karincake/getuk v0.1.0/go.mod h1:NVnvxSGAkQ/xuq99FzWACvY5efyKPLFla1cKB8czm7c= github.com/karincake/lepet v0.0.1 h1:eq/cwn5BBg0jWZ1c/MmvhFIBma0zBpVs2LwkfDOncy4= github.com/karincake/lepet v0.0.1/go.mod h1:U84w7olXO3BPJw2Hu6MBonFmJmPKaFjtyAj1HTu3z1A= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= diff --git a/internal/domain/base-entities/core/entity.go b/internal/domain/base-entities/core/entity.go index 6a88e7af..fc8ab1f9 100644 --- a/internal/domain/base-entities/core/entity.go +++ b/internal/domain/base-entities/core/entity.go @@ -1,14 +1,17 @@ package core -import "gorm.io/gorm" +import ( + "time" + + "gorm.io/gorm" +) type Base struct { - CreatedAt string `json:"createdAt"` - UpdatedAt string `json:"updatedAt"` + CreatedAt time.Time `json:"createdAt" gorm:"type:timestamptz"` + UpdatedAt string `json:"updatedAt" gorm:"type:timestamptz"` DeteledAt gorm.DeletedAt `json:"deletedAt,omitempty"` } -// type Main struct { Id uint `json:"id" gorm:"primaryKey"` // depends on the system architecture Base diff --git a/internal/domain/main-entities/user/entity.go b/internal/domain/main-entities/user/entity.go index ac83a6b2..58aea3af 100644 --- a/internal/domain/main-entities/user/entity.go +++ b/internal/domain/main-entities/user/entity.go @@ -9,6 +9,6 @@ type User struct { ecore.Main // adjust this according to the needs Name string `json:"name" gorm:"not null;size:25"` Password string `json:"password" gorm:"not null;size:255"` - Status_Code erc.StatusCode `json:"status_code" gorm:"not null;size:10"` - FailedLoginCount uint `json:"failedLoginCount"` + Status_Code erc.StatusCode `json:"status_code" gorm:"not null;size:12"` + FailedLoginCount uint8 `json:"failedLoginCount" gorm:"type:smallint"` } diff --git a/internal/interface/migration/migration.go b/internal/interface/migration/migration.go index 13abe812..4de7d5d2 100644 --- a/internal/interface/migration/migration.go +++ b/internal/interface/migration/migration.go @@ -2,45 +2,124 @@ package migration import ( "flag" - "fmt" "log" "os" - "reflect" + "os/exec" "gopkg.in/yaml.v3" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/schema" + + eu "simrs-vx/internal/domain/main-entities/user" ) -type DbConf struct { - Dsn string - Dialect string +type Config struct { + DbCfg DbConf `yaml:"dbCfg"` +} + +type DbConf struct { + DSN string `yaml:"dsn"` + MaxOpenConns int `yaml:"maxOpenConns"` + MaxIdleConns int `yaml:"maxIdleConns"` + MaxIdleTime int `yaml:"maxIdleTime"` } -// Migrate all tables at once, one time only for exam purpose func Migrate() { - // use default config file location or use flat + // Your existing config loading code... cfgFile := "./config.yml" flag.StringVar(&cfgFile, "config-file", "./config.yml", "Configuration path (default=./config.yaml)") flag.Parse() - // read the config file yamlFile, err := os.ReadFile(cfgFile) if err != nil { log.Fatalf("%v", err) } - // parse into config struct - var dbConf DbConf - err = yaml.Unmarshal(yamlFile, &dbConf) + var cfg Config + err = yaml.Unmarshal(yamlFile, &cfg) if err != nil { log.Fatal(err) } - log.Print("config is loaded successfully") - // create database connection - db, err := gorm.Open(postgres.Open(dbConf.Dsn), &gorm.Config{ + argsWithProg := os.Args + if len(argsWithProg) > 1 { + switch argsWithProg[1] { + case "apply": + atlasApply(cfg.DbCfg.DSN) + case "generate": + atlasGenerate(cfg.DbCfg.DSN) + case "status": + atlasStatus(cfg.DbCfg.DSN) + case "gorm": + // Fallback to GORM if needed + gormMigrate(cfg.DbCfg) + default: + log.Println("Unknown command. Use: apply, generate, status, or gorm") + } + } else { + // Default: apply Atlas migrations + atlasApply(cfg.DbCfg.DSN) + } +} + +func atlasApply(url string) { + log.Println("Applying Atlas migrations...") + cmd := exec.Command("atlas", "migrate", "apply", + "--dir", "file://migrations", + "--url", url) + + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + log.Printf("Atlas migration failed: %v", err) + log.Println("Try running 'go run migration.go generate' first") + } else { + log.Println("Atlas migrations applied successfully") + } +} + +func atlasGenerate(url string) { + log.Println("Generating Atlas migration from GORM models...") + + // First, create tables with GORM to get the schema + log.Println("Step 1: Creating schema with GORM...") + gormCreateSchema(url) + + // Then inspect and create migration + log.Println("Step 2: Generating Atlas migration...") + cmd := exec.Command("atlas", "migrate", "diff", "auto_migration", + "--dir", "file://migrations", + "--dev-url", "docker://postgres/17/dev?search_path=public", + "--to", url) + + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + log.Printf("Atlas generation failed: %v", err) + } else { + log.Println("Atlas migration generated successfully") + } +} + +func atlasStatus(url string) { + log.Println("Checking Atlas migration status...") + cmd := exec.Command("atlas", "migrate", "status", + "--dir", "file://migrations", + "--url", url) + + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + log.Printf("Atlas status failed: %v", err) + } +} + +func gormCreateSchema(dsn string) { + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ NamingStrategy: schema.NamingStrategy{ SingularTable: true, NoLowerCase: true, @@ -49,45 +128,20 @@ func Migrate() { if err != nil { log.Fatal(err) } - log.Print("database-connection is established successfully") - // migrate all the tables modelList := []any{ - // &single.Single{}}, // example - + &eu.User{}, + // Add other models here } - argsWithProg := os.Args - if len(argsWithProg) > 1 { - if argsWithProg[1] == "gradually" { - for _, v := range modelList { - name := "" - if t := reflect.TypeOf(v); t.Kind() == reflect.Ptr { - name = "*" + t.Elem().Name() - } else { - name = t.Name() - } - fmt.Println("Migrating ", name) - db.AutoMigrate(v) - } - } else { - for _, v := range modelList { - name := "" - if t := reflect.TypeOf(v); t.Kind() == reflect.Ptr { - name = "*" + t.Elem().Name() - } else { - name = t.Name() - } - if name == argsWithProg[1] { - fmt.Println("Migrating ", name) - db.AutoMigrate(v) - } - } - } - } else { - fmt.Println("Migrating all tables") - db.AutoMigrate(modelList...) + if err := db.AutoMigrate(modelList...); err != nil { + log.Fatal(err) } - - log.Printf("migration is complete") + log.Println("Schema created with GORM") +} + +func gormMigrate(dbConf DbConf) { + // Your original GORM migrate code as fallback + log.Println("Using GORM AutoMigrate...") + // ... your existing GORM code here }