diff --git a/COMMANDS.md b/COMMANDS.md index d3aebec..d3b51e5 100644 --- a/COMMANDS.md +++ b/COMMANDS.md @@ -104,23 +104,9 @@ When running `insertr serve`, the server automatically: - **Auto-updates files** when content changes via API - **Creates backups** of original files (if enabled) -**Development vs Production Behavior:** - -**Development Mode** (`--dev-mode`): -- ✅ Content changes saved to database -- ❌ File enhancement **disabled** (prevents live-reload loops) -- ✅ Editor loads content dynamically from API -- ✅ Perfect for `just dev` workflow - -**Production Mode** (no `--dev-mode`): -- ✅ Content changes saved to database -- ✅ File enhancement **enabled** -- ✅ Static files updated immediately -- ✅ Changes live instantly - -**Live Enhancement Process (Production):** +**Live Enhancement Process:** 1. Content updated via API → Database updated -2. If site has `auto_enhance: true` AND not in dev mode → File enhancement triggered +2. If site has `auto_enhance: true` → File enhancement triggered 3. Static files updated in-place → Changes immediately live ### API Endpoints diff --git a/README.md b/README.md index 16cfea9..592aab6 100644 --- a/README.md +++ b/README.md @@ -436,33 +436,14 @@ server: backup_originals: true ``` -### **Development vs Production Modes** - -**Development Mode** (recommended for development): -```bash -# Development: Content updates save to database only -# NO file enhancement to prevent live-reload loops -./insertr serve --dev-mode -just dev # Uses dev mode automatically -``` - -**Production Mode** (for deployment): -```bash -# Production: Content updates trigger immediate file enhancement -./insertr serve # No --dev-mode flag -``` - ### **Quick Start** ```bash # 1. Configure sites in insertr.yaml -# 2. Start development server (no file enhancement) +# 2. Start the server ./insertr serve --dev-mode -# 3. Editor loads content from database dynamically -# 4. No unwanted file modifications or page reloads during development - -# 5. For production deployment: -./insertr serve # Enables automatic file enhancement +# 3. Your sites are automatically registered and enhanced +# 4. Content changes via editor immediately update static files ``` ## ⚙️ Configuration diff --git a/cmd/serve.go b/cmd/serve.go index 96fa14d..0388355 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -124,25 +124,30 @@ func runServe(cmd *cobra.Command, args []string) { router.HandleFunc("/health", api.HealthMiddleware()) // API routes - apiRouter := router.PathPrefix("/api/content").Subrouter() + apiRouter := router.PathPrefix("/api").Subrouter() - // Content endpoints matching the expected API contract - apiRouter.HandleFunc("/bulk", contentHandler.GetBulkContent).Methods("GET") - apiRouter.HandleFunc("/{id}", contentHandler.GetContent).Methods("GET") - apiRouter.HandleFunc("/{id}", contentHandler.UpdateContent).Methods("PUT") - apiRouter.HandleFunc("", contentHandler.GetAllContent).Methods("GET") - apiRouter.HandleFunc("", contentHandler.CreateContent).Methods("POST") + // Content endpoints + contentRouter := apiRouter.PathPrefix("/content").Subrouter() + contentRouter.HandleFunc("/bulk", contentHandler.GetBulkContent).Methods("GET") + contentRouter.HandleFunc("/{id}", contentHandler.GetContent).Methods("GET") + contentRouter.HandleFunc("/{id}", contentHandler.UpdateContent).Methods("PUT") + contentRouter.HandleFunc("", contentHandler.GetAllContent).Methods("GET") + contentRouter.HandleFunc("", contentHandler.CreateContent).Methods("POST") // Version control endpoints - apiRouter.HandleFunc("/{id}/versions", contentHandler.GetContentVersions).Methods("GET") - apiRouter.HandleFunc("/{id}/rollback", contentHandler.RollbackContent).Methods("POST") + contentRouter.HandleFunc("/{id}/versions", contentHandler.GetContentVersions).Methods("GET") + contentRouter.HandleFunc("/{id}/rollback", contentHandler.RollbackContent).Methods("POST") + + // Site enhancement endpoint + apiRouter.HandleFunc("/enhance", contentHandler.EnhanceSite).Methods("POST") // Handle CORS preflight requests explicitly - apiRouter.HandleFunc("/{id}", api.CORSPreflightHandler).Methods("OPTIONS") - apiRouter.HandleFunc("", api.CORSPreflightHandler).Methods("OPTIONS") - apiRouter.HandleFunc("/bulk", api.CORSPreflightHandler).Methods("OPTIONS") - apiRouter.HandleFunc("/{id}/versions", api.CORSPreflightHandler).Methods("OPTIONS") - apiRouter.HandleFunc("/{id}/rollback", api.CORSPreflightHandler).Methods("OPTIONS") + contentRouter.HandleFunc("/{id}", api.CORSPreflightHandler).Methods("OPTIONS") + contentRouter.HandleFunc("", api.CORSPreflightHandler).Methods("OPTIONS") + contentRouter.HandleFunc("/bulk", api.CORSPreflightHandler).Methods("OPTIONS") + contentRouter.HandleFunc("/{id}/versions", api.CORSPreflightHandler).Methods("OPTIONS") + contentRouter.HandleFunc("/{id}/rollback", api.CORSPreflightHandler).Methods("OPTIONS") + apiRouter.HandleFunc("/enhance", api.CORSPreflightHandler).Methods("OPTIONS") // Start server addr := fmt.Sprintf(":%d", port) diff --git a/internal/api/handlers.go b/internal/api/handlers.go index e34aa3c..e29f7fa 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -40,6 +40,53 @@ func (h *ContentHandler) SetSiteManager(siteManager *content.SiteManager) { h.siteManager = siteManager } +// EnhanceSite handles POST /api/enhance - manual site enhancement trigger +func (h *ContentHandler) EnhanceSite(w http.ResponseWriter, r *http.Request) { + siteID := r.URL.Query().Get("site_id") + if siteID == "" { + http.Error(w, "site_id parameter is required", http.StatusBadRequest) + return + } + + if h.siteManager == nil { + http.Error(w, "Site manager not available", http.StatusServiceUnavailable) + return + } + + // Check if site is registered + site, exists := h.siteManager.GetSite(siteID) + if !exists { + http.Error(w, fmt.Sprintf("Site %s is not registered", siteID), http.StatusNotFound) + return + } + + // Perform enhancement + err := h.siteManager.EnhanceSite(siteID) + if err != nil { + log.Printf("❌ Manual enhancement failed for site %s: %v", siteID, err) + http.Error(w, fmt.Sprintf("Enhancement failed: %v", err), http.StatusInternalServerError) + return + } + + // Get enhancement statistics + stats := h.siteManager.GetStats() + + // Return success response with details + response := map[string]interface{}{ + "success": true, + "site_id": siteID, + "site_path": site.Path, + "message": fmt.Sprintf("Successfully enhanced site %s", siteID), + "stats": stats, + "timestamp": time.Now().Format(time.RFC3339), + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + + log.Printf("✅ Manual enhancement completed for site %s", siteID) +} + // GetContent handles GET /api/content/{id} func (h *ContentHandler) GetContent(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) diff --git a/internal/content/site_manager.go b/internal/content/site_manager.go index cb31c8c..4ca9dc4 100644 --- a/internal/content/site_manager.go +++ b/internal/content/site_manager.go @@ -105,23 +105,12 @@ func (sm *SiteManager) GetAllSites() map[string]*SiteConfig { } // IsAutoEnhanceEnabled checks if a site has auto-enhancement enabled -// Returns false in development mode to prevent unwanted file modifications and live-reload loops func (sm *SiteManager) IsAutoEnhanceEnabled(siteID string) bool { - sm.mutex.RLock() - defer sm.mutex.RUnlock() - - // Disable auto-enhancement in development mode to prevent file modification conflicts with live-reload + // Never auto-enhance in development mode - use manual enhance button instead if sm.devMode { return false } - site, exists := sm.sites[siteID] - return exists && site.AutoEnhance -} - -// ForceEnhanceEnabled allows testing production behavior in development mode -// This method bypasses the dev_mode check for testing purposes -func (sm *SiteManager) ForceEnhanceEnabled(siteID string) bool { sm.mutex.RLock() defer sm.mutex.RUnlock() diff --git a/lib/src/core/auth.js b/lib/src/core/auth.js index 2cb1875..037800c 100644 --- a/lib/src/core/auth.js +++ b/lib/src/core/auth.js @@ -330,11 +330,15 @@ export class InsertrAuth { Visitor Mode + `; document.body.insertAdjacentHTML('beforeend', statusHtml); this.statusIndicator = document.getElementById('insertr-status'); + this.setupEnhanceButton(); this.updateStatusIndicator(); } @@ -357,6 +361,9 @@ export class InsertrAuth { statusText.textContent = 'Authenticated'; statusDot.className = 'insertr-status-dot insertr-status-authenticated'; } + + // Update enhance button visibility + this.updateEnhanceButtonVisibility(); } /** @@ -398,6 +405,28 @@ export class InsertrAuth { body.insertr-hide-gates .insertr-gate { display: none !important; } + + /* Enhance button styles */ + .insertr-enhance-btn { + background: #2563eb; + color: white; + border: none; + padding: 4px 8px; + border-radius: 4px; + font-size: 11px; + margin-left: 8px; + cursor: pointer; + transition: background 0.2s ease; + } + + .insertr-enhance-btn:hover { + background: #1d4ed8; + } + + .insertr-enhance-btn:disabled { + background: #9ca3af; + cursor: not-allowed; + } `; const styleSheet = document.createElement('style'); @@ -556,4 +585,85 @@ export class InsertrAuth { console.log('✅ OAuth authentication successful'); }, 1000); } + + /** + * Setup enhance button functionality + */ + setupEnhanceButton() { + const enhanceBtn = document.getElementById('insertr-enhance-btn'); + if (!enhanceBtn) return; + + enhanceBtn.addEventListener('click', async () => { + await this.enhanceFiles(); + }); + + // Show enhance button only in development/authenticated mode + this.updateEnhanceButtonVisibility(); + } + + /** + * Update enhance button visibility based on authentication state + */ + updateEnhanceButtonVisibility() { + const enhanceBtn = document.getElementById('insertr-enhance-btn'); + if (!enhanceBtn) return; + + // Show enhance button when authenticated (indicates dev mode) + if (this.state.isAuthenticated) { + enhanceBtn.style.display = 'inline-block'; + } else { + enhanceBtn.style.display = 'none'; + } + } + + /** + * Trigger manual file enhancement + */ + async enhanceFiles() { + const enhanceBtn = document.getElementById('insertr-enhance-btn'); + if (!enhanceBtn) return; + + // Get site ID from window context or configuration + const siteId = window.insertrConfig?.siteId || this.options.siteId || 'demo'; + + try { + // Show loading state + enhanceBtn.textContent = '⏳ Enhancing...'; + enhanceBtn.disabled = true; + + // Call enhance API + const response = await fetch(`/api/enhance?site_id=${siteId}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${this.state.currentUser?.token || 'mock-token'}` + } + }); + + if (!response.ok) { + throw new Error(`Enhancement failed: ${response.status} ${response.statusText}`); + } + + const result = await response.json(); + console.log('✅ Files enhanced successfully:', result); + + // Show success state briefly + enhanceBtn.textContent = '✅ Enhanced!'; + + // Optional: Trigger page reload to show enhanced files + setTimeout(() => { + window.location.reload(); + }, 1000); + + } catch (error) { + console.error('❌ Enhancement failed:', error); + enhanceBtn.textContent = '❌ Failed'; + + // Reset button after error + setTimeout(() => { + enhanceBtn.textContent = '🔄 Enhance'; + enhanceBtn.disabled = false; + }, 2000); + } + } } \ No newline at end of file