224 lines
5.9 KiB
Go
224 lines
5.9 KiB
Go
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, "")
|
|
}
|