- Problem: Element ID collisions between similar elements (logo h1 vs hero h1) causing content to be injected into wrong elements - Root cause: Enhancer used naive tag+class matching instead of parser's sophisticated semantic analysis for element identification Systematic solution: - Enhanced parser architecture with exported utilities (GetClasses, ContainsClass) - Added FindElementInDocument() with content-based semantic matching - Replaced naive findAndInjectNodes() with parser-based element matching - Removed code duplication between parser and enhancer packages Backend improvements: - Moved ID generation to backend for single source of truth - Added ElementContext struct for frontend-backend communication - Updated API handlers to support context-based content ID generation Frontend improvements: - Enhanced getElementMetadata() to extract semantic context - Updated save flow to handle both enhanced and non-enhanced elements - Improved API client to use backend-generated content IDs Result: - Unique content IDs: navbar-logo-200530 vs hero-title-a1de7b - Precise element matching using content validation - Single source of truth for DOM utilities in parser package - Eliminated 40+ lines of duplicate code while fixing core bug
164 lines
5.1 KiB
Go
164 lines
5.1 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"sort"
|
|
|
|
"github.com/insertr/insertr/internal/content"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
var (
|
|
timestamp string
|
|
latest bool
|
|
clean bool
|
|
)
|
|
|
|
var restoreCmd = &cobra.Command{
|
|
Use: "restore [site-id]",
|
|
Short: "Restore a site from backup",
|
|
Long: `Restore a registered site from a timestamped backup.
|
|
|
|
Examples:
|
|
insertr restore demo # List available backups
|
|
insertr restore demo --clean # Restore from oldest backup (cleanest)
|
|
insertr restore demo --latest # Restore from newest backup
|
|
insertr restore demo --timestamp 20250910-224704 # Restore from specific backup`,
|
|
Args: cobra.ExactArgs(1),
|
|
Run: runRestore,
|
|
}
|
|
|
|
func init() {
|
|
restoreCmd.Flags().StringVarP(×tamp, "timestamp", "t", "", "specific backup timestamp to restore from")
|
|
restoreCmd.Flags().BoolVar(&latest, "latest", false, "restore from most recent backup")
|
|
restoreCmd.Flags().BoolVar(&clean, "clean", false, "restore from oldest backup (cleanest state)")
|
|
|
|
// Bind flags to viper
|
|
viper.BindPFlag("restore.timestamp", restoreCmd.Flags().Lookup("timestamp"))
|
|
viper.BindPFlag("restore.latest", restoreCmd.Flags().Lookup("latest"))
|
|
viper.BindPFlag("restore.clean", restoreCmd.Flags().Lookup("clean"))
|
|
}
|
|
|
|
func runRestore(cmd *cobra.Command, args []string) {
|
|
siteID := args[0]
|
|
|
|
// Initialize content client (we don't actually need it for restore, but SiteManager expects it)
|
|
contentClient := content.NewMockClient()
|
|
|
|
// Initialize site manager
|
|
siteManager := content.NewSiteManager(contentClient, "./insertr-backups", false)
|
|
|
|
// Load sites from configuration to register them
|
|
if siteConfigs := viper.Get("server.sites"); siteConfigs != nil {
|
|
if configs, ok := siteConfigs.([]interface{}); ok {
|
|
var sites []*content.SiteConfig
|
|
for _, configInterface := range configs {
|
|
if configMap, ok := configInterface.(map[string]interface{}); ok {
|
|
site := &content.SiteConfig{}
|
|
if id, ok := configMap["site_id"].(string); ok {
|
|
site.SiteID = id
|
|
}
|
|
if path, ok := configMap["path"].(string); ok {
|
|
site.Path = path
|
|
}
|
|
if domain, ok := configMap["domain"].(string); ok {
|
|
site.Domain = domain
|
|
}
|
|
if autoEnhance, ok := configMap["auto_enhance"].(bool); ok {
|
|
site.AutoEnhance = autoEnhance
|
|
}
|
|
if backupOriginals, ok := configMap["backup_originals"].(bool); ok {
|
|
site.BackupOriginals = backupOriginals
|
|
}
|
|
sites = append(sites, site)
|
|
}
|
|
}
|
|
|
|
if err := siteManager.RegisterSites(sites); err != nil {
|
|
log.Fatalf("Failed to register sites: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// List available backups
|
|
backups, err := siteManager.ListBackups(siteID)
|
|
if err != nil {
|
|
log.Fatalf("Failed to list backups: %v", err)
|
|
}
|
|
|
|
if len(backups) == 0 {
|
|
fmt.Printf("❌ No backups found for site '%s'\n", siteID)
|
|
fmt.Printf("💡 Backups are created automatically during enhancement when backup_originals is enabled\n")
|
|
return
|
|
}
|
|
|
|
// Sort backups chronologically
|
|
sort.Strings(backups)
|
|
|
|
// Handle different restore modes
|
|
var targetTimestamp string
|
|
|
|
if timestamp != "" {
|
|
// Specific timestamp provided
|
|
targetTimestamp = timestamp
|
|
found := false
|
|
for _, backup := range backups {
|
|
if backup == targetTimestamp {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
fmt.Printf("❌ Backup timestamp '%s' not found for site '%s'\n", targetTimestamp, siteID)
|
|
fmt.Printf("📋 Available backups:\n")
|
|
for i, backup := range backups {
|
|
if i == 0 {
|
|
fmt.Printf(" %s (oldest/cleanest)\n", backup)
|
|
} else if i == len(backups)-1 {
|
|
fmt.Printf(" %s (newest)\n", backup)
|
|
} else {
|
|
fmt.Printf(" %s\n", backup)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
} else if clean {
|
|
// Restore from oldest backup (cleanest)
|
|
targetTimestamp = backups[0]
|
|
fmt.Printf("🧹 Restoring from oldest backup (cleanest state): %s\n", targetTimestamp)
|
|
} else if latest {
|
|
// Restore from newest backup
|
|
targetTimestamp = backups[len(backups)-1]
|
|
fmt.Printf("🔄 Restoring from newest backup: %s\n", targetTimestamp)
|
|
} else {
|
|
// No specific option - list available backups
|
|
fmt.Printf("📋 Available backups for site '%s':\n", siteID)
|
|
for i, backup := range backups {
|
|
if i == 0 {
|
|
fmt.Printf(" %s (oldest/cleanest) ← use --clean\n", backup)
|
|
} else if i == len(backups)-1 {
|
|
fmt.Printf(" %s (newest) ← use --latest\n", backup)
|
|
} else {
|
|
fmt.Printf(" %s\n", backup)
|
|
}
|
|
}
|
|
fmt.Printf("\nUsage:\n")
|
|
fmt.Printf(" insertr restore %s --clean # restore from oldest backup\n", siteID)
|
|
fmt.Printf(" insertr restore %s --latest # restore from newest backup\n", siteID)
|
|
fmt.Printf(" insertr restore %s --timestamp %s # restore from specific backup\n", siteID, backups[0])
|
|
return
|
|
}
|
|
|
|
// Perform restore
|
|
fmt.Printf("🔄 Restoring site '%s' from backup %s...\n", siteID, targetTimestamp)
|
|
|
|
if err := siteManager.RestoreFromBackup(siteID, targetTimestamp); err != nil {
|
|
log.Fatalf("❌ Restore failed: %v", err)
|
|
}
|
|
|
|
fmt.Printf("✅ Successfully restored site '%s' from backup %s\n", siteID, targetTimestamp)
|
|
fmt.Printf("💡 Site files have been restored to their state from %s\n", targetTimestamp)
|
|
}
|