package migrations import ( "database/sql" "fmt" "log" "os" "path/filepath" "strings" "gorm.io/gen" "gorm.io/gen/field" "gorm.io/gorm" ) // Struktur untuk menampung metadata Foreign Key dari database type ForeignKeyMeta struct { TableName string ColumnName string RefTable string RefColumn string } func GenerateModel(table *string, db *gorm.DB) { currentDir, err := os.Getwd() if err != nil { log.Fatalf("Gagal mendapatkan working directory: %v", err) } // Folder penampung sementara kueri (akan dihapus di akhir) queryOutPath := filepath.Join(currentDir, "internal", "domain", "query_utils") // Target folder Struct Model murni Anda modelOutPath := filepath.Join(currentDir, "internal", "domain") _ = os.MkdirAll(queryOutPath, os.ModePerm) _ = os.MkdirAll(modelOutPath, os.ModePerm) sqlDB, err := db.DB() if err != nil { log.Fatalf("Gagal mengambil instance sql.DB: %v", err) } fkMetas := fetchForeignKeys(sqlDB) log.Printf("Berhasil mendeteksi %d relasi Foreign Key fisik di database.\n", len(fkMetas)) g := gen.NewGenerator(gen.Config{ OutPath: queryOutPath, ModelPkgPath: modelOutPath, Mode: gen.WithoutContext, FieldWithIndexTag: true, FieldWithTypeTag: true, FieldNullable: true, FieldSignable: true, }) g.UseDB(db) rows, err := sqlDB.Query("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'") if err != nil { log.Fatalf("Gagal mengambil daftar tabel: %v", err) } defer rows.Close() var tableNames []string for rows.Next() { var tName string if err := rows.Scan(&tName); err == nil && !strings.HasPrefix(tName, "gorm_") { tableNames = append(tableNames, tName) } } // Map untuk mengumpulkan seluruh konfigurasi gen.ModelOpt dari tiap tabel tableRelationOpts := make(map[string][]gen.ModelOpt) // Isi opsi relasi berdasarkan foreign key yang terdeteksi for _, tName := range tableNames { var relationOptions []gen.ModelOpt for _, fk := range fkMetas { // A. Relasi BELONGS TO if fk.TableName == tName { baseName := snakeToPascalCase(strings.TrimSuffix(fk.ColumnName, "_id")) if baseName == "" { baseName = snakeToPascalCase(fk.RefTable) } // KUSTOMISASI 1: Tambahkan akhiran _Relation pada properti propertyName := fmt.Sprintf("%s_Relation", baseName) relationOptions = append(relationOptions, gen.FieldRelate( field.BelongsTo, propertyName, g.GenerateModel(fk.RefTable), &field.RelateConfig{ GORMTag: field.GormTag{ "foreignKey": []string{fk.ColumnName}, "references": []string{fk.RefColumn}, }, }, )) } // B. Relasi HAS MANY if fk.RefTable == tName { // KUSTOMISASI 1: Tambahkan akhiran _Relation pada properti jamak propertyName := fmt.Sprintf("%ss_Relation", snakeToPascalCase(fk.TableName)) relationOptions = append(relationOptions, gen.FieldRelate( field.HasMany, propertyName, g.GenerateModel(fk.TableName), &field.RelateConfig{ GORMTag: field.GormTag{ "foreignKey": []string{fk.ColumnName}, "references": []string{fk.RefColumn}, }, }, )) } } tableRelationOpts[tName] = relationOptions } var finalModels []any allTableModels := make(map[string]any) for _, tName := range tableNames { opts := tableRelationOpts[tName] compiledModel := g.GenerateModel(tName, opts...) allTableModels[tName] = compiledModel finalModels = append(finalModels, compiledModel) } if table != nil && *table != "" { if specModel, ok := allTableModels[*table]; ok { g.ApplyBasic(specModel) } else { log.Fatalf("Tabel %s tidak ditemukan di database!", *table) } } else { g.ApplyBasic(finalModels...) } // Eksekusi pembuatan file standar GORM Gen (.gen.go) g.Execute() // Bersihkan utility kueri yang tidak dibutuhkan _ = os.RemoveAll(queryOutPath) // KUSTOMISASI 2: Hilangkan ekstensi ".gen.go" menjadi ".go" murni err = renameGeneratedFiles(modelOutPath) if err != nil { log.Printf("Peringatan: Gagal mengubah nama beberapa file model: %v\n", err) } log.Println("Selesai! Model berhasil dibuat dengan format nama tanpa .gen dan field bertanda _Relation.") } // Fungsi untuk mencari berkas .gen.go dan mengubah namanya menjadi .go murni func renameGeneratedFiles(dir string) error { files, err := os.ReadDir(dir) if err != nil { return err } for _, file := range files { if !file.IsDir() && strings.HasSuffix(file.Name(), ".gen.go") { oldPath := filepath.Join(dir, file.Name()) newName := strings.Replace(file.Name(), ".gen.go", ".go", 1) newPath := filepath.Join(dir, newName) // Proses penggantian nama file fisik if err := os.Rename(oldPath, newPath); err != nil { return err } } } return nil } // Fungsi pembantu menarik metadata FK Postgres func fetchForeignKeys(db *sql.DB) []ForeignKeyMeta { query := ` SELECT kcu.table_name AS table_name, kcu.column_name AS column_name, ccu.table_name AS referenced_table_name, ccu.column_name AS referenced_column_name FROM information_schema.table_constraints AS tc JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name AND ccu.table_schema = tc.table_schema WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema = 'public'; ` rows, err := db.Query(query) if err != nil { log.Printf("Gagal membaca metadata Foreign Key: %v\n", err) return nil } defer rows.Close() var metas []ForeignKeyMeta for rows.Next() { var m ForeignKeyMeta if err := rows.Scan(&m.TableName, &m.ColumnName, &m.RefTable, &m.RefColumn); err == nil { metas = append(metas, m) } } return metas } func snakeToPascalCase(s string) string { parts := strings.Split(s, "_") for i, part := range parts { if len(part) > 0 { parts[i] = strings.ToUpper(part[:1]) + part[1:] } } return strings.Join(parts, "") }