package validation import ( "context" "database/sql" "fmt" "time" ) // ValidationConfig holds configuration for duplicate validation type ValidationConfig struct { TableName string IDColumn string StatusColumn string DateColumn string ActiveStatuses []string AdditionalFields map[string]interface{} } // DuplicateValidator provides methods for validating duplicate entries type DuplicateValidator struct { db *sql.DB } // NewDuplicateValidator creates a new instance of DuplicateValidator func NewDuplicateValidator(db *sql.DB) *DuplicateValidator { return &DuplicateValidator{db: db} } // ValidateDuplicate checks for duplicate entries based on the provided configuration func (dv *DuplicateValidator) ValidateDuplicate(ctx context.Context, config ValidationConfig, identifier interface{}) error { query := fmt.Sprintf(` SELECT COUNT(*) FROM %s WHERE %s = $1 AND %s = ANY($2) AND DATE(%s) = CURRENT_DATE `, config.TableName, config.IDColumn, config.StatusColumn, config.DateColumn) var count int err := dv.db.QueryRowContext(ctx, query, identifier, config.ActiveStatuses).Scan(&count) if err != nil { return fmt.Errorf("failed to check duplicate: %w", err) } if count > 0 { return fmt.Errorf("data with ID %v already exists with active status today", identifier) } return nil } // ValidateDuplicateWithCustomFields checks for duplicates with additional custom fields func (dv *DuplicateValidator) ValidateDuplicateWithCustomFields(ctx context.Context, config ValidationConfig, fields map[string]interface{}) error { whereClause := fmt.Sprintf("%s = ANY($1) AND DATE(%s) = CURRENT_DATE", config.StatusColumn, config.DateColumn) args := []interface{}{config.ActiveStatuses} argIndex := 2 // Add additional field conditions for fieldName, fieldValue := range config.AdditionalFields { whereClause += fmt.Sprintf(" AND %s = $%d", fieldName, argIndex) args = append(args, fieldValue) argIndex++ } // Add dynamic fields for fieldName, fieldValue := range fields { whereClause += fmt.Sprintf(" AND %s = $%d", fieldName, argIndex) args = append(args, fieldValue) argIndex++ } query := fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE %s", config.TableName, whereClause) var count int err := dv.db.QueryRowContext(ctx, query, args...).Scan(&count) if err != nil { return fmt.Errorf("failed to check duplicate with custom fields: %w", err) } if count > 0 { return fmt.Errorf("duplicate entry found with the specified criteria") } return nil } // ValidateOncePerDay ensures only one submission per day for a given identifier func (dv *DuplicateValidator) ValidateOncePerDay(ctx context.Context, tableName, idColumn, dateColumn string, identifier interface{}) error { query := fmt.Sprintf(` SELECT COUNT(*) FROM %s WHERE %s = $1 AND DATE(%s) = CURRENT_DATE `, tableName, idColumn, dateColumn) var count int err := dv.db.QueryRowContext(ctx, query, identifier).Scan(&count) if err != nil { return fmt.Errorf("failed to check daily submission: %w", err) } if count > 0 { return fmt.Errorf("only one submission allowed per day for ID %v", identifier) } return nil } // GetLastSubmissionTime returns the last submission time for a given identifier func (dv *DuplicateValidator) GetLastSubmissionTime(ctx context.Context, tableName, idColumn, dateColumn string, identifier interface{}) (*time.Time, error) { query := fmt.Sprintf(` SELECT %s FROM %s WHERE %s = $1 ORDER BY %s DESC LIMIT 1 `, dateColumn, tableName, idColumn, dateColumn) var lastTime time.Time err := dv.db.QueryRowContext(ctx, query, identifier).Scan(&lastTime) if err != nil { if err == sql.ErrNoRows { return nil, nil // No previous submission } return nil, fmt.Errorf("failed to get last submission time: %w", err) } return &lastTime, nil } // DefaultRetribusiConfig returns default configuration for retribusi validation func DefaultRetribusiConfig() ValidationConfig { return ValidationConfig{ TableName: "data_retribusi", IDColumn: "id", StatusColumn: "status", DateColumn: "date_created", ActiveStatuses: []string{"active", "draft"}, } }