Files
insertr/internal/content/site_manager.go
Joakim f73e21ce6e feat: add manual file enhancement with development mode support
- Add manual enhance API endpoint (POST /api/enhance?site_id={site}) for triggering file enhancement
- Implement enhance button in JavaScript library status indicator (🔄 Enhance)
- Disable auto-enhancement in development mode to prevent live-reload conflicts
- Add dev mode parameter to SiteManager to control enhancement behavior
- Update API routing structure to support /api/enhance endpoint
- Include enhance button styling and user feedback (loading, success, error states)
- Button triggers file enhancement and page reload to show updated static files

Development workflow improvements:
- Content edits → Immediate editor preview (no unwanted page reloads)
- Manual enhance button → Intentional file updates + reload for testing
- Production mode maintains automatic enhancement on content changes

This resolves the live-reload conflict where automatic file enhancement
was causing unwanted page reloads during content editing in development.
2025-09-10 23:38:46 +02:00

308 lines
7.5 KiB
Go

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,
}
}