package controllers import ( "net/http" "time" "github.com/gin-gonic/gin" "gorm.io/gorm" ) // EditorPreviewController handles real-time editor preview operations type EditorPreviewController struct { DB *gorm.DB } // NewEditorPreviewController creates a new editor preview controller func NewEditorPreviewController(db *gorm.DB) *EditorPreviewController { return &EditorPreviewController{DB: db} } // PreviewState represents the current editor state type PreviewState struct { SessionID string `json:"session_id"` PageType string `json:"page_type"` Elements []ElementPreviewConfig `json:"elements"` UpdatedAt time.Time `json:"updated_at"` UserID uint `json:"user_id,omitempty"` } // ElementPreviewConfig represents a single element's preview configuration type ElementPreviewConfig struct { ElementName string `json:"element_name"` Variant string `json:"variant"` Visible bool `json:"visible"` DisplayOrder int `json:"display_order"` CustomStyles map[string]interface{} `json:"custom_styles,omitempty"` } // GetPreviewState retrieves the current preview state for a session // GET /api/v1/editor/preview/:session_id func (c *EditorPreviewController) GetPreviewState(ctx *gin.Context) { sessionID := ctx.Param("session_id") // In a real implementation, you would fetch from cache/session store // For now, return a stub response ctx.JSON(http.StatusOK, gin.H{ "session_id": sessionID, "page_type": "homepage", "elements": []ElementPreviewConfig{}, "updated_at": time.Now(), }) } // UpdatePreviewState updates the preview state for a session // POST /api/v1/editor/preview/:session_id func (c *EditorPreviewController) UpdatePreviewState(ctx *gin.Context) { sessionID := ctx.Param("session_id") var state PreviewState if err := ctx.ShouldBindJSON(&state); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } state.SessionID = sessionID state.UpdatedAt = time.Now() // In a real implementation, you would: // 1. Store in Redis/cache with TTL (e.g., 1 hour) // 2. Associate with user session // 3. Validate element configurations ctx.JSON(http.StatusOK, gin.H{ "success": true, "state": state, }) } // ApplyPreviewChanges commits preview changes to the database // POST /api/v1/editor/preview/:session_id/apply func (c *EditorPreviewController) ApplyPreviewChanges(ctx *gin.Context) { sessionID := ctx.Param("session_id") var payload struct { PageType string `json:"page_type" binding:"required"` Elements []ElementPreviewConfig `json:"elements" binding:"required"` } if err := ctx.ShouldBindJSON(&payload); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } // Start transaction tx := c.DB.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() // Delete existing configurations for this page type if err := tx.Exec("DELETE FROM page_element_configs WHERE page_type = ?", payload.PageType).Error; err != nil { tx.Rollback() ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to clear existing configurations"}) return } // Insert new configurations for _, elem := range payload.Elements { config := map[string]interface{}{ "page_type": payload.PageType, "element_name": elem.ElementName, "variant": elem.Variant, "visible": elem.Visible, "display_order": elem.DisplayOrder, "created_at": time.Now(), "updated_at": time.Now(), } if err := tx.Table("page_element_configs").Create(config).Error; err != nil { tx.Rollback() ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save configuration"}) return } } // Commit transaction if err := tx.Commit().Error; err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to commit changes"}) return } // Clear preview state from cache // In a real implementation: cache.Delete(sessionID) ctx.JSON(http.StatusOK, gin.H{ "success": true, "session_id": sessionID, "message": "Changes applied successfully", "applied_at": time.Now(), }) } // DiscardPreviewChanges discards preview changes without saving // DELETE /api/v1/editor/preview/:session_id func (c *EditorPreviewController) DiscardPreviewChanges(ctx *gin.Context) { sessionID := ctx.Param("session_id") // In a real implementation: cache.Delete(sessionID) ctx.JSON(http.StatusOK, gin.H{ "success": true, "session_id": sessionID, "message": "Preview discarded", }) } // ValidatePreviewConfig validates element configurations before saving // POST /api/v1/editor/preview/validate func (c *EditorPreviewController) ValidatePreviewConfig(ctx *gin.Context) { var configs []ElementPreviewConfig if err := ctx.ShouldBindJSON(&configs); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } // Validation rules errors := []string{} elementNames := make(map[string]bool) for i, config := range configs { // Check for duplicate element names if elementNames[config.ElementName] { errors = append(errors, "Duplicate element name: "+config.ElementName) } elementNames[config.ElementName] = true // Validate variant is not empty if config.Variant == "" { errors = append(errors, "Element "+config.ElementName+" has empty variant") } // Validate display order if config.DisplayOrder < 0 { errors = append(errors, "Element "+config.ElementName+" has invalid display order") } // Check for gaps in display order if config.DisplayOrder != i { errors = append(errors, "Display order has gaps or is not sequential") } } if len(errors) > 0 { ctx.JSON(http.StatusBadRequest, gin.H{ "valid": false, "errors": errors, }) return } ctx.JSON(http.StatusOK, gin.H{ "valid": true, "message": "Configuration is valid", }) } // GetAvailableVariants returns available variants for an element // GET /api/v1/editor/variants/:element_name func (c *EditorPreviewController) GetAvailableVariants(ctx *gin.Context) { elementName := ctx.Param("element_name") // Define available variants for each element // In a real implementation, this could come from database or config variants := map[string][]map[string]interface{}{ "hero": { {"value": "grid", "label": "Grid Layout", "description": "3-card grid hero section"}, {"value": "scroller", "label": "Horizontal Scroller", "description": "Scrollable cards"}, {"value": "swiper", "label": "Swiper", "description": "Swipeable carousel"}, {"value": "swiper_full", "label": "Full-width Swiper", "description": "Full viewport carousel"}, }, "matches": { {"value": "default", "label": "Default", "description": "Standard match display"}, {"value": "compact", "label": "Compact", "description": "Compact match cards"}, }, "table": { {"value": "default", "label": "Default", "description": "Standard table layout"}, {"value": "compact", "label": "Compact", "description": "Compact table view"}, }, "videos": { {"value": "default", "label": "Default", "description": "Standard video grid"}, {"value": "carousel", "label": "Carousel", "description": "Video carousel"}, }, "gallery": { {"value": "default", "label": "Default", "description": "Standard gallery grid"}, {"value": "masonry", "label": "Masonry", "description": "Masonry layout"}, }, "sponsors": { {"value": "grid", "label": "Grid", "description": "Grid layout"}, {"value": "slider", "label": "Slider", "description": "Horizontal slider"}, }, "newsletter": { {"value": "default", "label": "Default", "description": "Standard newsletter form"}, {"value": "inline", "label": "Inline", "description": "Inline compact form"}, }, } if variantList, ok := variants[elementName]; ok { ctx.JSON(http.StatusOK, gin.H{ "element_name": elementName, "variants": variantList, }) } else { ctx.JSON(http.StatusOK, gin.H{ "element_name": elementName, "variants": []map[string]interface{}{{"value": "default", "label": "Default"}}, }) } }