package content import ( "fmt" "log" "os" "path/filepath" "sync" "time" ) // SiteConfig represents configuration for a registered site type SiteConfig struct { SiteID string `yaml:"site_id"` Path string `yaml:"path"` Domain string `yaml:"domain,omitempty"` AutoEnhance bool `yaml:"auto_enhance"` BackupOriginals bool `yaml:"backup_originals"` } // SiteManager handles registration and enhancement of static sites type SiteManager struct { sites map[string]*SiteConfig enhancer *Enhancer mutex sync.RWMutex backupDir string devMode bool } // NewSiteManager creates a new site manager func NewSiteManager(contentClient ContentClient, backupDir string, devMode bool) *SiteManager { if backupDir == "" { backupDir = "./insertr-backups" } return &SiteManager{ sites: make(map[string]*SiteConfig), enhancer: NewEnhancer(contentClient, ""), // siteID will be set per operation backupDir: backupDir, devMode: devMode, } } // RegisterSite adds a site to the manager func (sm *SiteManager) RegisterSite(config *SiteConfig) error { sm.mutex.Lock() defer sm.mutex.Unlock() // Validate site configuration if config.SiteID == "" { return fmt.Errorf("site_id is required") } if config.Path == "" { return fmt.Errorf("path is required for site %s", config.SiteID) } // Check if path exists if _, err := os.Stat(config.Path); os.IsNotExist(err) { return fmt.Errorf("site path does not exist: %s", config.Path) } // Convert to absolute path absPath, err := filepath.Abs(config.Path) if err != nil { return fmt.Errorf("failed to resolve absolute path for %s: %w", config.Path, err) } config.Path = absPath sm.sites[config.SiteID] = config log.Printf("📁 Registered site %s at %s", config.SiteID, config.Path) return nil } // RegisterSites bulk registers multiple sites from configuration func (sm *SiteManager) RegisterSites(configs []*SiteConfig) error { for _, config := range configs { if err := sm.RegisterSite(config); err != nil { return fmt.Errorf("failed to register site %s: %w", config.SiteID, err) } } return nil } // GetSite returns a registered site configuration func (sm *SiteManager) GetSite(siteID string) (*SiteConfig, bool) { sm.mutex.RLock() defer sm.mutex.RUnlock() site, exists := sm.sites[siteID] return site, exists } // GetAllSites returns all registered sites func (sm *SiteManager) GetAllSites() map[string]*SiteConfig { sm.mutex.RLock() defer sm.mutex.RUnlock() // Return a copy to prevent external modification result := make(map[string]*SiteConfig) for id, site := range sm.sites { result[id] = site } return result } // IsAutoEnhanceEnabled checks if a site has auto-enhancement enabled func (sm *SiteManager) IsAutoEnhanceEnabled(siteID string) bool { // Never auto-enhance in development mode - use manual enhance button instead if sm.devMode { return false } sm.mutex.RLock() defer sm.mutex.RUnlock() site, exists := sm.sites[siteID] return exists && site.AutoEnhance } // EnhanceSite performs in-place enhancement of a registered site func (sm *SiteManager) EnhanceSite(siteID string) error { sm.mutex.RLock() site, exists := sm.sites[siteID] sm.mutex.RUnlock() if !exists { return fmt.Errorf("site %s is not registered", siteID) } log.Printf("🔄 Enhancing site %s at %s", siteID, site.Path) // Create backup if enabled if site.BackupOriginals { if err := sm.createBackup(siteID, site.Path); err != nil { log.Printf("⚠️ Failed to create backup for site %s: %v", siteID, err) // Continue with enhancement even if backup fails } } // Perform in-place enhancement if err := sm.enhancer.EnhanceInPlace(site.Path, siteID); err != nil { return fmt.Errorf("failed to enhance site %s: %w", siteID, err) } log.Printf("✅ Successfully enhanced site %s", siteID) return nil } // EnhanceAllSites enhances all registered sites that have auto-enhancement enabled func (sm *SiteManager) EnhanceAllSites() error { sm.mutex.RLock() sites := make([]*SiteConfig, 0, len(sm.sites)) for _, site := range sm.sites { if site.AutoEnhance { sites = append(sites, site) } } sm.mutex.RUnlock() var errors []error for _, site := range sites { if err := sm.EnhanceSite(site.SiteID); err != nil { errors = append(errors, err) } } if len(errors) > 0 { return fmt.Errorf("enhancement failed for some sites: %v", errors) } return nil } // createBackup creates a timestamped backup of the site func (sm *SiteManager) createBackup(siteID, sitePath string) error { // Create backup directory structure timestamp := time.Now().Format("20060102-150405") backupPath := filepath.Join(sm.backupDir, siteID, timestamp) if err := os.MkdirAll(backupPath, 0755); err != nil { return fmt.Errorf("failed to create backup directory: %w", err) } // Copy HTML files to backup return filepath.Walk(sitePath, func(path string, info os.FileInfo, err error) error { if err != nil { return err } // Only backup HTML files if !info.IsDir() && filepath.Ext(path) == ".html" { relPath, err := filepath.Rel(sitePath, path) if err != nil { return err } backupFilePath := filepath.Join(backupPath, relPath) // Create directory structure in backup if err := os.MkdirAll(filepath.Dir(backupFilePath), 0755); err != nil { return err } // Copy file content, err := os.ReadFile(path) if err != nil { return err } return os.WriteFile(backupFilePath, content, info.Mode()) } return nil }) } // RestoreFromBackup restores a site from a specific backup func (sm *SiteManager) RestoreFromBackup(siteID, timestamp string) error { sm.mutex.RLock() site, exists := sm.sites[siteID] sm.mutex.RUnlock() if !exists { return fmt.Errorf("site %s is not registered", siteID) } backupPath := filepath.Join(sm.backupDir, siteID, timestamp) if _, err := os.Stat(backupPath); os.IsNotExist(err) { return fmt.Errorf("backup not found: %s", backupPath) } log.Printf("🔄 Restoring site %s from backup %s", siteID, timestamp) // Copy backup files back to site directory return filepath.Walk(backupPath, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() { relPath, err := filepath.Rel(backupPath, path) if err != nil { return err } targetPath := filepath.Join(site.Path, relPath) // Create directory structure if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil { return err } // Copy file content, err := os.ReadFile(path) if err != nil { return err } return os.WriteFile(targetPath, content, info.Mode()) } return nil }) } // ListBackups returns available backups for a site func (sm *SiteManager) ListBackups(siteID string) ([]string, error) { backupSitePath := filepath.Join(sm.backupDir, siteID) if _, err := os.Stat(backupSitePath); os.IsNotExist(err) { return []string{}, nil // No backups exist } entries, err := os.ReadDir(backupSitePath) if err != nil { return nil, fmt.Errorf("failed to read backup directory: %w", err) } var backups []string for _, entry := range entries { if entry.IsDir() { backups = append(backups, entry.Name()) } } return backups, nil } // GetStats returns statistics about registered sites func (sm *SiteManager) GetStats() map[string]interface{} { sm.mutex.RLock() defer sm.mutex.RUnlock() autoEnhanceCount := 0 for _, site := range sm.sites { if site.AutoEnhance { autoEnhanceCount++ } } return map[string]interface{}{ "total_sites": len(sm.sites), "auto_enhance_sites": autoEnhanceCount, "backup_directory": sm.backupDir, } }