package validation import ( "context" "fmt" queryUtils "api-service/internal/utils/query" "github.com/jmoiron/sqlx" ) // ============================================================================= // DYNAMIC VALIDATION RULE // ============================================================================= // ValidationRule mendefinisikan aturan untuk memeriksa duplikat atau kondisi lain. // Struct ini membuat validator dapat digunakan kembali untuk tabel apa pun. type ValidationRule struct { // TableName adalah nama tabel yang akan diperiksa. TableName string // UniqueColumns adalah daftar kolom yang, jika digabungkan, harus unik. // Contoh: []string{"email"} atau []string{"first_name", "last_name", "dob"} UniqueColumns []string // Conditions adalah filter tambahan yang harus dipenuhi. // Ini sangat berguna untuk aturan bisnis, seperti "status != 'deleted'". // Gunakan queryUtils.DynamicFilter untuk fleksibilitas penuh. Conditions []queryUtils.DynamicFilter // ExcludeIDColumn dan ExcludeIDValue digunakan untuk operasi UPDATE, // untuk memastikan bahwa record tidak membandingkan dirinya sendiri. ExcludeIDColumn string ExcludeIDValue interface{} } // NewUniqueFieldRule adalah helper untuk membuat aturan validasi unik untuk satu kolom. // Ini adalah cara cepat untuk membuat aturan yang paling umum. func NewUniqueFieldRule(tableName, uniqueColumn string, additionalConditions ...queryUtils.DynamicFilter) ValidationRule { return ValidationRule{ TableName: tableName, UniqueColumns: []string{uniqueColumn}, Conditions: additionalConditions, } } // ============================================================================= // DYNAMIC VALIDATOR // ============================================================================= // DynamicValidator menyediakan metode untuk menjalankan validasi berdasarkan ValidationRule. // Ini sepenuhnya generik dan tidak terikat pada tabel atau model tertentu. type DynamicValidator struct { qb *queryUtils.QueryBuilder } // NewDynamicValidator membuat instance DynamicValidator baru. func NewDynamicValidator(qb *queryUtils.QueryBuilder) *DynamicValidator { return &DynamicValidator{qb: qb} } // Validate menjalankan validasi terhadap aturan yang diberikan. // `data` adalah map yang berisi nilai untuk kolom yang akan diperiksa (biasanya dari request body). // Mengembalikan `true` jika ada duplikat yang ditemukan (validasi gagal), `false` jika tidak ada duplikat (validasi berhasil). func (dv *DynamicValidator) Validate(ctx context.Context, db *sqlx.DB, rule ValidationRule, data map[string]interface{}) (bool, error) { // LOGGING: Log validation start fmt.Printf("[VALIDATION] Starting validation for table: %s, unique columns: %v, data: %v\n", rule.TableName, rule.UniqueColumns, data) if len(rule.UniqueColumns) == 0 { fmt.Printf("[VALIDATION] ERROR: ValidationRule must have at least one UniqueColumn\n") return false, fmt.Errorf("ValidationRule must have at least one UniqueColumn") } // 1. Kumpulkan semua filter dari aturan var allFilters []queryUtils.DynamicFilter // Tambahkan kondisi tambahan (misalnya, status != 'deleted') allFilters = append(allFilters, rule.Conditions...) fmt.Printf("[VALIDATION] Added %d condition filters\n", len(rule.Conditions)) // 2. Bangun filter untuk kolom unik berdasarkan data yang diberikan for _, colName := range rule.UniqueColumns { value, exists := data[colName] if !exists { // Jika data untuk kolom unik tidak ada, ini adalah kesalahan pemrograman. fmt.Printf("[VALIDATION] ERROR: data for unique column '%s' not found in provided data map\n", colName) return false, fmt.Errorf("data for unique column '%s' not found in provided data map", colName) } allFilters = append(allFilters, queryUtils.DynamicFilter{ Column: colName, Operator: queryUtils.OpEqual, Value: value, }) fmt.Printf("[VALIDATION] Added filter for column '%s' with value: %v\n", colName, value) } // 3. Tambahkan filter pengecualian ID (untuk operasi UPDATE) if rule.ExcludeIDColumn != "" { allFilters = append(allFilters, queryUtils.DynamicFilter{ Column: rule.ExcludeIDColumn, Operator: queryUtils.OpNotEqual, Value: rule.ExcludeIDValue, }) fmt.Printf("[VALIDATION] Added exclude filter for column '%s' with value: %v\n", rule.ExcludeIDColumn, rule.ExcludeIDValue) } // 4. Bangun dan eksekusi query untuk menghitung jumlah record yang cocok query := queryUtils.DynamicQuery{ From: rule.TableName, Filters: []queryUtils.FilterGroup{{Filters: allFilters, LogicOp: "AND"}}, } fmt.Printf("[VALIDATION] Built query with %d total filters\n", len(allFilters)) count, err := dv.qb.ExecuteCount(ctx, db, query) if err != nil { fmt.Printf("[VALIDATION] ERROR: failed to execute validation query for table %s: %v\n", rule.TableName, err) return false, fmt.Errorf("failed to execute validation query for table %s: %w", rule.TableName, err) } fmt.Printf("[VALIDATION] Query executed successfully, count result: %d\n", count) // 5. Kembalikan hasil result := count > 0 fmt.Printf("[VALIDATION] Validation result: isDuplicate=%t (count > 0: %d > 0 = %t)\n", result, count, result) return result, nil } // ============================================================================= // CONTOH PENGGUNAAN (UNTUK DITEMPATKAN DI HANDLER ANDA) // ============================================================================= /* // --- Cara Penggunaan di RetribusiHandler --- // 1. Tambahkan DynamicValidator ke struct handler type RetribusiHandler struct { // ... validator *validation.DynamicValidator } // 2. Inisialisasi di constructor func NewRetribusiHandler() *RetribusiHandler { qb := queryUtils.NewQueryBuilder(queryUtils.DBTypePostgreSQL).SetAllowedColumns(...) return &RetribusiHandler{ // ... validator: validation.NewDynamicValidator(qb), } } // 3. Gunakan di CreateRetribusi func (h *RetribusiHandler) CreateRetribusi(c *gin.Context) { var req retribusi.RetribusiCreateRequest // ... bind dan validasi request ... // Siapkan aturan validasi: KodeTarif harus unik di antara record yang tidak dihapus. rule := validation.NewUniqueFieldRule( "data_retribusi", // Nama tabel "Kode_tarif", // Kolom yang harus unik queryUtils.DynamicFilter{ // Kondisi tambahan Column: "status", Operator: queryUtils.OpNotEqual, Value: "deleted", }, ) // Siapkan data dari request untuk divalidasi dataToValidate := map[string]interface{}{ "Kode_tarif": req.KodeTarif, } // Eksekusi validasi isDuplicate, err := h.validator.Validate(ctx, dbConn, rule, dataToValidate) if err != nil { h.logAndRespondError(c, "Failed to validate Kode Tarif", err, http.StatusInternalServerError) return } if isDuplicate { h.respondError(c, "Kode Tarif already exists", fmt.Errorf("duplicate Kode Tarif: %s", req.KodeTarif), http.StatusConflict) return } // ... lanjutkan proses create ... } // 4. Gunakan di UpdateRetribusi func (h *RetribusiHandler) UpdateRetribusi(c *gin.Context) { id := c.Param("id") var req retribusi.RetribusiUpdateRequest // ... bind dan validasi request ... // Siapkan aturan validasi: KodeTarif harus unik, kecuali untuk record dengan ID ini. rule := validation.ValidationRule{ TableName: "data_retribusi", UniqueColumns: []string{"Kode_tarif"}, Conditions: []queryUtils.DynamicFilter{ {Column: "status", Operator: queryUtils.OpNotEqual, Value: "deleted"}, }, ExcludeIDColumn: "id", // Kecualikan berdasarkan kolom 'id' ExcludeIDValue: id, // ...dengan nilai ID dari parameter } dataToValidate := map[string]interface{}{ "Kode_tarif": req.KodeTarif, } isDuplicate, err := h.validator.Validate(ctx, dbConn, rule, dataToValidate) if err != nil { h.logAndRespondError(c, "Failed to validate Kode Tarif", err, http.StatusInternalServerError) return } if isDuplicate { h.respondError(c, "Kode Tarif already exists", fmt.Errorf("duplicate Kode Tarif: %s", req.KodeTarif), http.StatusConflict) return } // ... lanjutkan proses update ... } // --- Contoh Penggunaan untuk Kasus Lain --- // Contoh: Validasi kombinasi unik untuk tabel 'users' // (email dan company_id harus unik bersama-sama) func (h *UserHandler) CreateUser(c *gin.Context) { // ... rule := validation.ValidationRule{ TableName: "users", UniqueColumns: []string{"email", "company_id"}, // Unik komposit } dataToValidate := map[string]interface{}{ "email": req.Email, "company_id": req.CompanyID, } isDuplicate, err := h.validator.Validate(ctx, dbConn, rule, dataToValidate) // ... handle error dan duplicate } */