- Rename AutoEnhancer to Discoverer with clear element discovery focus - Implement unified enhancement pipeline in Enhancer: * Phase 1: Element Discovery (configurable, respects existing insertr classes) * Phase 2: ID Generation via engine * Phase 3: Content Injection via engine - Add EnhancementConfig and DiscoveryConfig for flexible configuration - Update all method names and references (discoverNode, DiscoveryResult, etc.) - Support both manual class insertion and automatic discovery - Maintain single enhance command interface while providing unified internal pipeline - Update all constructors to use new configuration-based approach This establishes the clean Discoverer + Enhancer architecture discussed, with discovery as configurable first phase and enhancement as unified pipeline.
310 lines
7.6 KiB
Go
310 lines
7.6 KiB
Go
package content
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/insertr/insertr/internal/engine"
|
|
)
|
|
|
|
// 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 engine.ContentClient, backupDir string, devMode bool) *SiteManager {
|
|
if backupDir == "" {
|
|
backupDir = "./insertr-backups"
|
|
}
|
|
|
|
return &SiteManager{
|
|
sites: make(map[string]*SiteConfig),
|
|
enhancer: NewDefaultEnhancer(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,
|
|
}
|
|
}
|