package controllers import ( "encoding/json" "fmt" ) // StyleValidator validates and normalizes style objects before saving type StyleValidator struct{} // ValidateAndNormalizeStyles ensures style data is properly structured func (sv *StyleValidator) ValidateAndNormalizeStyles(settings map[string]interface{}) error { if settings == nil { return nil } // If styles exist as a top-level key, ensure it's a valid object if stylesRaw, exists := settings["styles"]; exists { switch styles := stylesRaw.(type) { case map[string]interface{}: // Valid object - ensure all values are valid CSS values for key, val := range styles { if !sv.isValidCSSValue(val) { return fmt.Errorf("invalid CSS value for property '%s': %v", key, val) } } case string: // Try to parse as JSON var styleMap map[string]interface{} if err := json.Unmarshal([]byte(styles), &styleMap); err != nil { return fmt.Errorf("styles must be a valid JSON object or map") } // Replace with parsed object settings["styles"] = styleMap default: return fmt.Errorf("styles must be an object, got %T", styles) } } // Validate customCSS if present if customCSS, exists := settings["customCSS"]; exists { if _, ok := customCSS.(string); !ok { return fmt.Errorf("customCSS must be a string, got %T", customCSS) } } return nil } // isValidCSSValue checks if a value is a valid CSS value type func (sv *StyleValidator) isValidCSSValue(val interface{}) bool { switch val.(type) { case string, int, int64, float64, bool: return true default: return false } } // MergeStyles merges new styles into existing settings preserving other fields func (sv *StyleValidator) MergeStyles(existingSettings map[string]interface{}, newStyles map[string]interface{}) map[string]interface{} { if existingSettings == nil { existingSettings = make(map[string]interface{}) } // If newStyles contains a "styles" key, merge it if newStylesMap, exists := newStyles["styles"]; exists { if existingStylesRaw, exists := existingSettings["styles"]; exists { // Merge with existing styles if existingStyles, ok := existingStylesRaw.(map[string]interface{}); ok { if newStylesObj, ok := newStylesMap.(map[string]interface{}); ok { // Merge newStylesObj into existingStyles for k, v := range newStylesObj { existingStyles[k] = v } existingSettings["styles"] = existingStyles } else { // Replace entirely if type mismatch existingSettings["styles"] = newStylesMap } } else { // Replace if existing was not a map existingSettings["styles"] = newStylesMap } } else { // No existing styles - set directly existingSettings["styles"] = newStyles["styles"] } } // Merge other top-level keys for k, v := range newStyles { if k != "styles" { existingSettings[k] = v } } return existingSettings } // ExtractStylesForSave prepares settings object for database save // Ensures styles are properly nested under settings.styles func (sv *StyleValidator) ExtractStylesForSave(input map[string]interface{}) map[string]interface{} { settings := make(map[string]interface{}) // Copy all non-style fields for k, v := range input { if k != "styles" && k != "customCSS" { settings[k] = v } } // Nest styles under styles key if stylesRaw, exists := input["styles"]; exists { settings["styles"] = stylesRaw } // Also nest customCSS if present if customCSS, exists := input["customCSS"]; exists { settings["customCSS"] = customCSS } return settings }