cargo-platform/migrations/generatemodel.go
2026-08-16 01:40:21 +07:00

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, "")
}