Fix demo site auto-enhancement and content persistence

- Restructure demo directory from test-sites/ to demos/ with flattened layout
- Add auto-enhancement on server startup for all sites with auto_enhance: true
- Fix inconsistent content ID generation that prevented dan-eden-portfolio content persistence
- Update server configuration to enhance from source to separate output directories
- Remove manual enhancement from justfile in favor of automatic server enhancement
- Clean up legacy test files and unused restore command
- Update build system to use CDN endpoint instead of file copying
This commit is contained in:
2025-09-17 00:07:40 +02:00
parent 1fa607c47c
commit 71561316da
73 changed files with 190 additions and 4827 deletions

View File

@@ -102,7 +102,7 @@ When running `insertr serve`, the server automatically:
- **Registers sites** from `insertr.yaml` configuration
- **Enhances static files** with latest database content
- **Auto-updates files** when content changes via API
- **Creates backups** of original files (if enabled)
**Live Enhancement Process:**
1. Content updated via API → Database updated

View File

@@ -458,13 +458,11 @@ server:
path: "/var/www/mysite"
domain: "mysite.example.com"
auto_enhance: true
backup_originals: true
- site_id: "blog"
path: "/var/www/blog"
domain: "blog.example.com"
auto_enhance: true
backup_originals: true
```
### **Quick Start**
@@ -493,7 +491,6 @@ server:
path: "./demo-site"
domain: "localhost:3000"
auto_enhance: true
backup_originals: true
# API configuration (for remote content API)
api:

View File

@@ -140,7 +140,7 @@ internal/
### **Production Ready When**:
- ✅ Multi-site support with proper site isolation
- ✅ Authentication and authorization working
- ✅ Database migrations and backup strategy
- ✅ Database migrations strategy
- ✅ CDN hosting for insertr.js library
- ✅ Deployment documentation and examples

View File

@@ -51,7 +51,7 @@ func runEnhance(cmd *cobra.Command, args []string) {
outputDir := viper.GetString("cli.output")
// Auto-derive site_id for demo paths or validate for production
if strings.Contains(inputDir, "/demos/") {
if strings.Contains(inputDir, "/demos/") || strings.Contains(inputDir, "./demos/") {
// Auto-derive site_id from demo path
siteID = content.DeriveOrValidateSiteID(inputDir, siteID)
} else {

View File

@@ -1,163 +0,0 @@
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(&timestamp, "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)
}

View File

@@ -52,7 +52,6 @@ func init() {
rootCmd.AddCommand(enhanceCmd)
rootCmd.AddCommand(serveCmd)
rootCmd.AddCommand(restoreCmd)
}
func initConfig() {

View File

@@ -73,7 +73,7 @@ func runServe(cmd *cobra.Command, args []string) {
contentClient := content.NewDatabaseClient(database)
// Initialize site manager
siteManager := content.NewSiteManager(contentClient, "./insertr-backups", devMode)
siteManager := content.NewSiteManager(contentClient, devMode)
// Load sites from configuration
if siteConfigs := viper.Get("server.sites"); siteConfigs != nil {
@@ -88,15 +88,15 @@ func runServe(cmd *cobra.Command, args []string) {
if path, ok := configMap["path"].(string); ok {
site.Path = path
}
if sourcePath, ok := configMap["source_path"].(string); ok {
site.SourcePath = sourcePath
}
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
}
if site.SiteID != "" && site.Path != "" {
sites = append(sites, site)
}
@@ -108,6 +108,14 @@ func runServe(cmd *cobra.Command, args []string) {
}
}
// Auto-enhance sites if enabled
if devMode {
log.Printf("🔄 Auto-enhancing sites in development mode...")
if err := siteManager.EnhanceAllSites(); err != nil {
log.Printf("⚠️ Some sites failed to enhance: %v", err)
}
}
// Initialize handlers
contentHandler := api.NewContentHandler(database, authService)
contentHandler.SetSiteManager(siteManager)

View File

@@ -8,7 +8,7 @@ Successfully established a comprehensive testing infrastructure for insertr CMS
### ✅ Directory Structure
```
test-sites/
demos/
├── simple/ # Simple vanilla CSS sites
│ └── dan-eden-portfolio/ # ✅ COMPLETE
├── framework-based/ # CSS framework sites
@@ -59,7 +59,7 @@ test-sites/
### ✅ Developer Experience
- Simple enhancement workflow: download → add classes → enhance → serve
- Automatic backup of originals
- Clear feedback on enhancement results
## Comparison with Demo Site
@@ -126,7 +126,7 @@ just demo dan-eden
just list-demos
# Test demo infrastructure
node test-sites/scripts/test-demo.js
node demos/scripts/test-demo.js
```
### **Demo Sites Available**

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -10,7 +10,7 @@ database:
# Demo-specific configuration
demo:
site_id: "dan-eden" # Unique site ID for Dan Eden demo
site_id: "dan-eden-portfolio" # Unique site ID for Dan Eden demo
inject_demo_gate: true # Auto-inject demo gate if no gates exist
mock_auth: true # Use mock authentication for demos
api_endpoint: "http://localhost:8080/api/content"
@@ -18,8 +18,8 @@ demo:
# CLI enhancement configuration
cli:
site_id: "dan-eden" # Site ID for this demo
output: "./dan-eden-demo" # Output directory for enhanced files
site_id: "dan-eden-portfolio" # Site ID for this demo
output: "./demos/dan-eden-portfolio_enhanced" # Output directory for enhanced files
inject_demo_gate: true # Inject demo gate in development mode
# Authentication configuration (for demo)

View File

@@ -4,16 +4,16 @@
<title>About - Acme Consulting Services</title>
<link rel="stylesheet" href="assets/style.css"/>
</head>
<script src="http://localhost:8080/insertr.js" data-insertr-injected="true" data-site-id="demo" data-api-endpoint="http://localhost:8080/api/content" data-mock-auth="true" data-debug="true"></script></head>
<body>
<!-- Navigation -->
<nav class="navbar">
<div class="container">
<h1 class="logo insertr" data-content-id="about-logo-bf9558" data-content-type="text">Acme Consulting</h1>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="about.html">About</a></li>
<li><a href="contact.html">Contact</a></li>
<li class="insertr" data-content-id="about-li-0babbf" data-content-type="text"><a href="index.html">Home</a></li>
<li class="insertr" data-content-id="about-li-2-0babbf" data-content-type="text"><a href="about.html">About</a></li>
<li class="insertr" data-content-id="about-li-3-0babbf" data-content-type="text"><a href="contact.html">Contact</a></li>
</ul>
</div>
@@ -32,11 +32,11 @@
<div class="container">
<h2 class="insertr" data-content-id="about-h2-246854" data-content-type="text">Our Story</h2>
<div class="insertr-group">
<p>Founded in 2020, Acme Consulting emerged from a simple observation: small businesses needed access to the same high-quality strategic advice that large corporations receive, but in a format that was accessible, affordable, and actionable.</p>
<p class="insertr" data-content-id="about-p-b2f44a" data-content-type="markdown">Founded in 2020, Acme Consulting emerged from a simple observation: small businesses needed access to the same high-quality strategic advice that large corporations receive, but in a format that was accessible, affordable, and actionable.</p>
<p>Our founders, with combined experience of over 30 years in business strategy, operations, and technology, recognized that the traditional consulting model wasn&#39;t serving the needs of growing businesses. We set out to change that.</p>
<p class="insertr" data-content-id="about-p-2-b2f44a" data-content-type="markdown">Our founders, with combined experience of over 30 years in business strategy, operations, and technology, recognized that the traditional consulting model wasn&#39;t serving the needs of growing businesses. We set out to change that.</p>
<p>Today, we&#39;ve helped over **200 businesses** streamline their operations, clarify their strategy, and achieve sustainable growth. Our approach combines proven methodologies with a deep understanding of the unique challenges facing small to medium-sized businesses.</p>
<p class="insertr" data-content-id="about-p-3-b2f44a" data-content-type="markdown">Today, we&#39;ve helped over **200 businesses** streamline their operations, clarify their strategy, and achieve sustainable growth. Our approach combines proven methodologies with a deep understanding of the unique challenges facing small to medium-sized businesses.</p>
</div>
</div>
</section>
@@ -49,21 +49,21 @@
<div class="services-grid" style="margin-top: 3rem;">
<div class="service-card">
<div class="insertr">
<div class="insertr" data-content-id="about-div-dac2cd" data-content-type="markdown">
<h3>Sarah Chen</h3>
<p><strong>Founder &amp; CEO</strong></p>
<p>Former <strong>McKinsey consultant</strong> with 15 years of experience in strategy and operations. MBA from Stanford.</p>
</div>
</div>
<div class="service-card">
<div class="insertr">
<div class="insertr" data-content-id="about-div-2-dac2cd" data-content-type="markdown">
<h3>Michael Rodriguez</h3>
<p><strong>Head of Operations</strong></p>
<p>20 years in manufacturing and supply chain optimization. Expert in <strong>lean methodologies</strong> and process improvement.</p>
</div>
</div>
<div class="service-card">
<div class="insertr">
<div class="insertr" data-content-id="about-div-3-dac2cd" data-content-type="markdown">
<h3>Emma Thompson</h3>
<p><strong>Digital Strategy Lead</strong></p>
<p>Former tech startup founder turned consultant. Specializes in <em>digital transformation</em> and technology adoption.</p>
@@ -101,8 +101,8 @@
<!-- Test 1: .insertr container expansion (should make each p individually editable) -->
<div style="margin-bottom: 2rem;">
<h3>Test 1: Container Expansion (.insertr)</h3>
<div class="insertr" style="border: 2px dashed #ccc; padding: 1rem;">
<h3 class="insertr" data-content-id="about-h3-ea6b29" data-content-type="text">Test 1: Container Expansion (.insertr)</h3>
<div class="insertr" style="border: 2px dashed #ccc; padding: 1rem;" data-content-id="about-div-4-e2aa93" data-content-type="markdown">
<p>This paragraph should be individually editable with a textarea.</p>
<p>This second paragraph should also be individually editable.</p>
<p>Each paragraph should get its own modal when clicked.</p>
@@ -111,11 +111,11 @@
<!-- Test 2: .insertr-group collective editing (should edit all together) -->
<div>
<h3>Test 2: Group Editing (.insertr-group)</h3>
<h3 class="insertr" data-content-id="about-h3-2-ea6b29" data-content-type="text">Test 2: Group Editing (.insertr-group)</h3>
<div class="insertr-group" style="border: 2px solid #007cba; padding: 1rem;">
<p>This paragraph is part of a <strong>group</strong>.</p>
<p>Clicking anywhere should open one markdown editor with <em>rich formatting</em>.</p>
<p>All content should be <strong>editable together</strong> as markdown with proper <em>HTML conversion</em>.</p>
<p class="insertr" data-content-id="about-p-4-dcfaf1" data-content-type="markdown">This paragraph is part of a <strong>group</strong>.</p>
<p class="insertr" data-content-id="about-p-5-dcfaf1" data-content-type="markdown">Clicking anywhere should open one markdown editor with <em>rich formatting</em>.</p>
<p class="insertr" data-content-id="about-p-6-dcfaf1" data-content-type="markdown">All content should be <strong>editable together</strong> as markdown with proper <em>HTML conversion</em>.</p>
</div>
</div>
</div>

View File

@@ -4,16 +4,15 @@
<title>Acme Consulting Services - Live Reload Test</title>
<link rel="stylesheet" href="assets/style.css"/>
</head>
<body>
<!-- Navigation -->
<nav class="navbar">
<div class="container">
<h1 class="logo insertr" data-content-id="index-logo-c176ba" data-content-type="text">Acme Consulting</h1>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="about.html">About</a></li>
<li><a href="contact.html">Contact</a></li>
<li class="insertr" data-content-id="index-li-bf7136" data-content-type="text"><a href="index.html">Home</a></li>
<li class="insertr" data-content-id="index-li-2-bf7136" data-content-type="text"><a href="about.html">About</a></li>
<li class="insertr" data-content-id="index-li-3-bf7136" data-content-type="text"><a href="contact.html">Contact</a></li>
</ul>
</div>
@@ -78,8 +77,7 @@
</div>
</footer>
<!-- Insertr JavaScript Library -->
<script type="text/javascript" src="insertr.js"></script>
</body></html>

View File

@@ -10,7 +10,7 @@ database:
# Demo-specific configuration
demo:
site_id: "demo" # Unique site ID for default demo
site_id: "default" # Unique site ID for default demo
inject_demo_gate: true # Auto-inject demo gate if no gates exist
mock_auth: true # Use mock authentication for demos
api_endpoint: "http://localhost:8080/api/content"
@@ -18,8 +18,8 @@ demo:
# CLI enhancement configuration
cli:
site_id: "demo" # Site ID for this demo
output: "./demo_enhanced" # Output directory for enhanced files
site_id: "default" # Site ID for this demo
output: "./demos/default_enhanced" # Output directory for enhanced files
inject_demo_gate: true # Inject demo gate in development mode
# Authentication configuration (for demo)

View File

@@ -13,7 +13,7 @@ console.log('=====================================\n');
// Test 1: Check if enhanced sites exist
console.log('📁 Checking enhanced test sites...');
const danEdenPath = './test-sites/simple/dan-eden-portfolio-enhanced';
const danEdenPath = './demos/simple/dan-eden-portfolio-enhanced';
if (fs.existsSync(danEdenPath)) {
console.log('✅ Dan Eden enhanced site exists');
@@ -32,7 +32,7 @@ if (fs.existsSync(danEdenPath)) {
}
} else {
console.log('❌ Dan Eden enhanced site not found');
console.log(' Run: just enhance-test-sites');
console.log(' Run: just enhance-demos');
}
console.log('\n🎯 Demo Commands Available:');

19
demos/simple/index.html Normal file
View File

@@ -0,0 +1,19 @@
<!DOCTYPE html><html><head>
<title>Simple Test</title>
<script src="http://localhost:8080/insertr.js" data-insertr-injected="true" data-site-id="simple" data-api-endpoint="http://localhost:8080/api/content" data-mock-auth="true" data-debug="true"></script></head>
<body>
<h1 class="insertr" data-content-id="index-h1-e0f926" data-content-type="text">Welcome, you!!</h1>
<p class="insertr" data-content-id="index-p-b376ed" data-content-type="markdown">This is a <strong>test</strong> paragraph with <a href="/">a link</a>.</p>
<div>
<h2 class="insertr" data-content-id="index-h2-d8622b" data-content-type="text">Section Title</h2>
<p class="insertr" data-content-id="index-p-2-daa8f5" data-content-type="markdown">Another paragraph here.</p>
<button class="insertr" data-content-id="index-button-41ef19" data-content-type="link">Click Me</button>
</div>
<div class="insertr-demo-gate" style="position: fixed; top: 20px; right: 20px; z-index: 9999; font-family: -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, Roboto, sans-serif;">
<button class="insertr-gate insertr-demo-gate-btn insertr" style="background: #4f46e5; color: white; border: none; padding: 10px 16px; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3); transition: all 0.2s ease; display: flex; align-items: center; gap: 8px; user-select: none;" onmouseover="this.style.background=&#39;#4338ca&#39;; this.style.transform=&#39;translateY(-2px)&#39;; this.style.boxShadow=&#39;0 6px 16px rgba(79, 70, 229, 0.4)&#39;" onmouseout="this.style.background=&#39;#4f46e5&#39;; this.style.transform=&#39;translateY(0)&#39;; this.style.boxShadow=&#39;0 4px 12px rgba(79, 70, 229, 0.3)&#39;" data-content-id="index-insertr-gate-61c9aa" data-content-type="link">
<span style="font-size: 16px;">✏️</span>
<span>Edit Site</span>
</button>
</div></body></html>

View File

@@ -19,7 +19,7 @@ demo:
# CLI enhancement configuration
cli:
site_id: "simple" # Site ID for this demo
output: "./simple_enhanced" # Output directory for enhanced files
output: "./demos/simple_enhanced" # Output directory for enhanced files
inject_demo_gate: true # Inject demo gate in development mode
# Authentication configuration (for demo)

View File

@@ -12,31 +12,31 @@ database:
server:
port: 8080 # HTTP API server port
sites: # Registered sites for file-based enhancement
- site_id: "demo"
path: "./test-sites/demo-site_enhanced"
- site_id: "default"
path: "./demos/default_enhanced"
source_path: "./demos/default"
domain: "localhost:3000"
auto_enhance: true
backup_originals: true
- site_id: "simple"
path: "./test-sites/simple/test-simple_enhanced"
path: "./demos/simple_enhanced"
source_path: "./demos/simple"
domain: "localhost:3000"
auto_enhance: true
backup_originals: true
- site_id: "dan-eden"
path: "./test-sites/simple/dan-eden-portfolio_enhanced"
- site_id: "dan-eden-portfolio"
path: "./demos/dan-eden-portfolio_enhanced"
source_path: "./demos/dan-eden-portfolio"
domain: "localhost:3000"
auto_enhance: true
backup_originals: true
# Example additional site configuration:
# - site_id: "mysite"
# path: "/var/www/mysite"
# domain: "mysite.example.com"
# auto_enhance: true
# backup_originals: true
# CLI enhancement configuration
cli:
site_id: "demo" # Default site ID for CLI operations
site_id: "default" # Default site ID for CLI operations
output: "./dist" # Default output directory for enhanced files
inject_demo_gate: true # Inject demo gate in development mode if no gates exist

View File

@@ -192,6 +192,11 @@ func (e *Enhancer) enhanceWithEngine(htmlContent []byte, filePath string) ([]byt
return []byte(buf.String()), nil
}
// SetSiteID sets the site ID for the enhancer
func (e *Enhancer) SetSiteID(siteID string) {
e.siteID = siteID
}
// EnhanceInPlace performs in-place enhancement of static site files
func (e *Enhancer) EnhanceInPlace(sitePath string, siteID string) error {
// Use the provided siteID (derivation should happen at CLI level)
@@ -204,7 +209,7 @@ func (e *Enhancer) EnhanceInPlace(sitePath string, siteID string) error {
// DeriveOrValidateSiteID automatically derives site_id for demo paths or validates for production
func DeriveOrValidateSiteID(sitePath string, configSiteID string) string {
// Check if this is a demo path
if strings.Contains(sitePath, "/demos/") {
if strings.Contains(sitePath, "/demos/") || strings.Contains(sitePath, "./demos/") {
return deriveDemoSiteID(sitePath)
}
@@ -222,10 +227,10 @@ func deriveDemoSiteID(sitePath string) string {
}
absPath = filepath.Clean(absPath)
// Look for patterns in demo paths:
// demos/demo-site_enhanced -> "demo"
// demos/simple/test-simple_enhanced -> "simple"
// demos/simple/dan-eden-portfolio -> "dan-eden"
// Flattened structure - just use directory name after demos/
// demos/default -> "default"
// demos/simple -> "simple"
// demos/dan-eden-portfolio -> "dan-eden-portfolio"
parts := strings.Split(absPath, string(filepath.Separator))
@@ -240,41 +245,14 @@ func deriveDemoSiteID(sitePath string) string {
if demosIndex == -1 || demosIndex >= len(parts)-1 {
// Fallback if demos not found in path
return "demo"
return "default"
}
// Get the segment after demos/
nextSegment := parts[demosIndex+1]
// Get the segment after demos/ and clean it
dirName := parts[demosIndex+1]
dirName = strings.TrimSuffix(dirName, "_enhanced")
// Handle different demo directory patterns:
if strings.HasPrefix(nextSegment, "demo-site") {
return "demo"
}
if nextSegment == "simple" && len(parts) > demosIndex+2 {
// For demos/simple/something, use the something part
finalSegment := parts[demosIndex+2]
// Extract meaningful name from directory
if strings.HasPrefix(finalSegment, "test-") {
// test-simple_enhanced -> simple
return "simple"
}
if strings.Contains(finalSegment, "dan-eden") {
return "dan-eden"
}
// Generic case: use the directory name as-is
return strings.TrimSuffix(finalSegment, "_enhanced")
}
// Default case: use the immediate subdirectory of demos/
result := strings.TrimSuffix(nextSegment, "_enhanced")
// Clean up common suffixes
result = strings.TrimSuffix(result, "-site")
return result
return dirName
}
// copyFile copies a file from src to dst

View File

@@ -5,41 +5,35 @@ import (
"log"
"os"
"path/filepath"
"strings"
"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"`
SiteID string `yaml:"site_id"`
Path string `yaml:"path"` // Served path (enhanced output)
SourcePath string `yaml:"source_path"` // Source path (for enhancement)
Domain string `yaml:"domain,omitempty"`
AutoEnhance bool `yaml:"auto_enhance"`
}
// SiteManager handles registration and enhancement of static sites
type SiteManager struct {
sites map[string]*SiteConfig
enhancer *Enhancer
mutex sync.RWMutex
backupDir string
devMode bool
sites map[string]*SiteConfig
enhancer *Enhancer
mutex sync.RWMutex
devMode bool
}
// NewSiteManager creates a new site manager
func NewSiteManager(contentClient engine.ContentClient, backupDir string, devMode bool) *SiteManager {
if backupDir == "" {
backupDir = "./insertr-backups"
}
func NewSiteManager(contentClient engine.ContentClient, devMode bool) *SiteManager {
return &SiteManager{
sites: make(map[string]*SiteConfig),
enhancer: NewDefaultEnhancer(contentClient, ""), // siteID will be set per operation
backupDir: backupDir,
devMode: devMode,
sites: make(map[string]*SiteConfig),
enhancer: NewDefaultEnhancer(contentClient, ""), // siteID will be set per operation
devMode: devMode,
}
}
@@ -56,9 +50,17 @@ func (sm *SiteManager) RegisterSite(config *SiteConfig) error {
return fmt.Errorf("path is required for site %s", config.SiteID)
}
// Check if path exists
// Check if path exists, auto-create enhancement directories
if _, err := os.Stat(config.Path); os.IsNotExist(err) {
return fmt.Errorf("site path does not exist: %s", config.Path)
// Auto-create directory if it appears to be an enhancement target
if strings.HasSuffix(config.Path, "_enhanced") {
log.Printf("📁 Creating enhancement directory: %s", config.Path)
if err := os.MkdirAll(config.Path, 0755); err != nil {
return fmt.Errorf("failed to create enhancement directory %s: %w", config.Path, err)
}
} else {
return fmt.Errorf("site path does not exist: %s", config.Path)
}
}
// Convert to absolute path
@@ -115,7 +117,7 @@ func (sm *SiteManager) IsAutoEnhanceEnabled(siteID string) bool {
return exists && site.AutoEnhance
}
// EnhanceSite performs in-place enhancement of a registered site
// EnhanceSite performs enhancement from source to output directory
func (sm *SiteManager) EnhanceSite(siteID string) error {
sm.mutex.RLock()
site, exists := sm.sites[siteID]
@@ -125,18 +127,25 @@ func (sm *SiteManager) EnhanceSite(siteID string) error {
return fmt.Errorf("site %s is not registered", siteID)
}
log.Printf("🔄 Enhancing site %s at %s", siteID, site.Path)
// Use source path if available, otherwise use main path (for backwards compatibility)
sourcePath := site.SourcePath
if sourcePath == "" {
sourcePath = site.Path
}
outputPath := 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
}
log.Printf("🔄 Enhancing site %s from %s to %s", siteID, sourcePath, outputPath)
// Create output directory if it doesn't exist
if err := os.MkdirAll(outputPath, 0755); err != nil {
return fmt.Errorf("failed to create output directory %s: %w", outputPath, err)
}
// Perform in-place enhancement
if err := sm.enhancer.EnhanceInPlace(site.Path, siteID); err != nil {
// Set site ID on enhancer
sm.enhancer.SetSiteID(siteID)
// Perform enhancement from source to output
if err := sm.enhancer.EnhanceDirectory(sourcePath, outputPath); err != nil {
return fmt.Errorf("failed to enhance site %s: %w", siteID, err)
}
@@ -169,121 +178,6 @@ func (sm *SiteManager) EnhanceAllSites() error {
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()
@@ -299,6 +193,5 @@ func (sm *SiteManager) GetStats() map[string]interface{} {
return map[string]interface{}{
"total_sites": len(sm.sites),
"auto_enhance_sites": autoEnhanceCount,
"backup_directory": sm.backupDir,
}
}

View File

@@ -33,12 +33,8 @@ func (g *IDGenerator) Generate(node *html.Node, filePath string) string {
tag := strings.ToLower(node.Data)
primaryClass := g.getPrimaryClass(node)
// 3. Position context (simple)
elementKey := g.getElementKey(fileName, tag, primaryClass)
index := g.getElementIndex(elementKey)
// 4. Build readable prefix
prefix := g.buildPrefix(fileName, tag, primaryClass, index)
// 3. Build readable prefix (deterministic, no runtime counting)
prefix := g.buildDeterministicPrefix(fileName, tag, primaryClass)
// 5. Add collision-resistant suffix
signature := g.createSignature(node, filePath)
@@ -84,7 +80,22 @@ func (g *IDGenerator) getElementIndex(elementKey string) int {
return g.elementCounts[elementKey]
}
// buildPrefix creates human-readable prefix for the ID
// buildDeterministicPrefix creates human-readable prefix without runtime counting
func (g *IDGenerator) buildDeterministicPrefix(fileName, tag, primaryClass string) string {
var parts []string
parts = append(parts, fileName)
if primaryClass != "" {
parts = append(parts, primaryClass)
} else {
parts = append(parts, tag)
}
// No runtime index - rely on hash for uniqueness
return strings.Join(parts, "-")
}
// buildPrefix creates human-readable prefix for the ID (legacy method)
func (g *IDGenerator) buildPrefix(fileName, tag, primaryClass string, index int) string {
var parts []string
parts = append(parts, fileName)

View File

@@ -17,12 +17,7 @@ dev: build-lib build
echo "================================================"
echo ""
# Enhance demo site if needed
if [ ! -d "./test-sites/demo-site_enhanced" ]; then
echo "🔧 Demo site not ready - enhancing now..."
./insertr enhance test-sites/demo-site --output test-sites/demo-site_enhanced --config test-sites/demo-site/insertr.yaml
echo "✅ Demo site enhanced!"
fi
# Note: Sites are auto-enhanced by the server on startup
echo ""
echo "🔌 Starting Insertr server with all sites..."
@@ -45,9 +40,9 @@ dev: build-lib build
echo ""
echo "🌐 All sites available at:"
echo " Demo site: http://localhost:8080/sites/demo/"
echo " Default site: http://localhost:8080/sites/default/"
echo " Simple site: http://localhost:8080/sites/simple/"
echo " Dan Eden site: http://localhost:8080/sites/dan-eden/"
echo " Dan Eden site: http://localhost:8080/sites/dan-eden-portfolio/"
echo ""
echo "📝 Full-stack ready - edit content with real-time persistence!"
echo "🔄 Press Ctrl+C to shutdown"
@@ -60,7 +55,7 @@ dev: build-lib build
dev-about: build-lib build
#!/usr/bin/env bash
echo "🚀 Starting full-stack development..."
echo "🌐 About page available at: http://localhost:8080/sites/demo/about.html"
echo "🌐 About page available at: http://localhost:8080/sites/default/about.html"
INSERTR_DATABASE_PATH=./insertr.db ./insertr serve --dev-mode
# Check project status and validate setup
@@ -70,9 +65,9 @@ check:
# Simple demo launcher - all sites now served from main server
demo:
@echo "🌐 All demo sites are served from the main server:"
@echo " http://localhost:8080/sites/demo/ - Main demo site"
@echo " http://localhost:8080/sites/default/ - Main demo site"
@echo " http://localhost:8080/sites/simple/ - Simple test site"
@echo " http://localhost:8080/sites/dan-eden/ - Dan Eden portfolio"
@echo " http://localhost:8080/sites/dan-eden-portfolio/ - Dan Eden portfolio"
@echo ""
@echo "🚀 To start the development server:"
@echo " just dev"
@@ -104,7 +99,7 @@ help:
# Enhance demo site (build-time content injection)
enhance input="test-sites/demo-site" output="dist":
enhance input="demos/default" output="dist":
./insertr enhance {{input}} --output {{output}} --mock
# === Content API Server Commands ===
@@ -124,7 +119,7 @@ health port="8080":
@echo "🔍 Checking API server health..."
@curl -s http://localhost:{{port}}/health | jq . || echo "❌ Server not responding at localhost:{{port}}"
# Clean all build artifacts and backups
# Clean all build artifacts
clean:
rm -rf lib/dist
rm -rf insertr
@@ -134,7 +129,7 @@ clean:
rm -rf lib/node_modules
rm -f dev.db
rm -f insertr.db
@echo "🧹 Cleaned all build artifacts and backups"
@echo "🧹 Cleaned all build artifacts"
@@ -163,7 +158,7 @@ status:
@echo "\n🔧 Unified binary:"
@ls -la insertr main.go cmd/ internal/ 2>/dev/null || echo " Missing unified binary components"
@echo "\n🌐 Demo site:"
@ls -la test-sites/demo-site/index.html test-sites/demo-site/about.html 2>/dev/null || echo " Missing demo files"
@ls -la demos/demo-site/index.html demos/demo-site/about.html 2>/dev/null || echo " Missing demo files"
@echo ""
@echo "🚀 Development Commands:"
@echo " just dev - Full-stack development (recommended)"
@@ -191,53 +186,44 @@ clean-demos:
echo "========================================="
# Demo directories
if [ -d "./test-sites/demo-site_enhanced" ]; then
rm -rf "./test-sites/demo-site_enhanced"
echo "🗑️ Removed: demo-site_enhanced"
if [ -d "./demos/default_enhanced" ]; then
rm -rf "./demos/default_enhanced"
echo "🗑️ Removed: default_enhanced"
fi
if [ -d "./test-sites/simple/dan-eden-portfolio_enhanced" ]; then
rm -rf "./test-sites/simple/dan-eden-portfolio_enhanced"
if [ -d "./demos/simple_enhanced" ]; then
rm -rf "./demos/simple_enhanced"
echo "🗑️ Removed: simple_enhanced"
fi
if [ -d "./demos/dan-eden-portfolio_enhanced" ]; then
rm -rf "./demos/dan-eden-portfolio_enhanced"
echo "🗑️ Removed: dan-eden-portfolio_enhanced"
fi
if [ -d "./test-sites/simple/test-simple_enhanced" ]; then
rm -rf "./test-sites/simple/test-simple_enhanced"
echo "🗑️ Removed: test-simple_enhanced"
fi
# Clean up any temporary directories
if [ -d "./test-sites/simple/dan-eden-portfolio-temp" ]; then
rm -rf "./test-sites/simple/dan-eden-portfolio-temp"
if [ -d "./demos/dan-eden-portfolio-temp" ]; then
rm -rf "./demos/dan-eden-portfolio-temp"
echo "🗑️ Removed: dan-eden-portfolio-temp"
fi
if [ -d "./test-sites/simple/test-simple-temp" ]; then
rm -rf "./test-sites/simple/test-simple-temp"
echo "🗑️ Removed: test-simple-temp"
if [ -d "./demos/simple-temp" ]; then
rm -rf "./demos/simple-temp"
echo "🗑️ Removed: simple-temp"
fi
# Legacy directories (cleanup from old workflow)
for legacy_dir in dan-eden-portfolio-auto-enhanced dan-eden-portfolio-full dan-eden-portfolio-auto dan-eden-portfolio-auto-v2 dan-eden-portfolio-auto-enhanced test-simple-auto-enhanced test-simple-full dan-eden-portfolio-enhanced test-simple-enhanced; do
if [ -d "./test-sites/simple/${legacy_dir}" ]; then
rm -rf "./test-sites/simple/${legacy_dir}"
for legacy_dir in demo-site demo-site_enhanced demo_enhanced simple/test-simple simple/dan-eden-portfolio; do
if [ -d "./demos/${legacy_dir}" ]; then
rm -rf "./demos/${legacy_dir}"
echo "🗑️ Removed: ${legacy_dir} (legacy)"
fi
done
if [ -d "./test-sites/simple/dan-eden-portfolio-full" ]; then
rm -rf "./test-sites/simple/dan-eden-portfolio-full"
echo "🗑️ Removed: dan-eden-portfolio-full"
fi
if [ -d "./test-sites/simple/test-simple-auto-enhanced" ]; then
rm -rf "./test-sites/simple/test-simple-auto-enhanced"
echo "🗑️ Removed: test-simple-auto-enhanced"
fi
if [ -d "./test-sites/simple/test-simple-full" ]; then
rm -rf "./test-sites/simple/test-simple-full"
echo "🗑️ Removed: test-simple-full"
# Clean up legacy directories in simple subdirectory
if [ -d "./demos/simple" ] && [ -z "$(find ./demos/simple -maxdepth 1 -name '*.html' -o -name '*.yaml')" ]; then
# If simple directory exists but contains no HTML/YAML files, it's legacy
rm -rf "./demos/simple"/* 2>/dev/null || true
fi

View File

@@ -10,9 +10,8 @@
"src/"
],
"scripts": {
"build": "rollup -c && npm run copy:demo",
"build": "rollup -c",
"build:only": "rollup -c",
"copy:demo": "cp dist/insertr.js ../test-sites/demo-site/insertr.js",
"watch": "rollup -c -w",
"dev": "rollup -c -w"
},

View File

@@ -2,20 +2,7 @@ import { nodeResolve } from '@rollup/plugin-node-resolve';
import terser from '@rollup/plugin-terser';
import { execSync } from 'child_process';
// Simple copy plugin to auto-copy to demo-site during development
function copyToDemo() {
return {
name: 'copy-to-demo',
writeBundle() {
try {
execSync('cp dist/insertr.js ../test-sites/demo-site/insertr.js');
console.log('📄 Copied to test-sites/demo-site/insertr.js');
} catch (error) {
console.warn('⚠️ Failed to copy to test-sites/demo-site:', error.message);
}
}
};
}
// No longer needed - insertr.js served via CDN endpoint
export default [
// Development build
@@ -27,8 +14,7 @@ export default [
name: 'Insertr'
},
plugins: [
nodeResolve(),
copyToDemo()
nodeResolve()
]
},
// Production build (minified)

View File

@@ -39,8 +39,8 @@ const commands = {
// Check files exist
const requiredFiles = [
'test-sites/demo-site/index.html',
'test-sites/demo-site/about.html',
'demos/demo-site/index.html',
'demos/demo-site/about.html',
'lib/dist/insertr.js',
'lib/dist/insertr.min.js',
'cmd/serve.go',
@@ -77,8 +77,8 @@ const commands = {
console.log('\n📊 Project stats:');
// Count editable elements
const indexContent = fs.readFileSync('test-sites/demo-site/index.html', 'utf8');
const aboutContent = fs.readFileSync('test-sites/demo-site/about.html', 'utf8');
const indexContent = fs.readFileSync('demos/demo-site/index.html', 'utf8');
const aboutContent = fs.readFileSync('demos/demo-site/about.html', 'utf8');
const insertrMatches = (indexContent + aboutContent).match(/class="insertr"/g) || [];
console.log(` 📝 Editable elements: ${insertrMatches.length}`);

View File

@@ -1,88 +0,0 @@
{
"site": {
"id": "acme-consulting",
"domain": "acmeconsulting.example.com",
"owner": "client@acmeconsulting.com",
"created": "2024-01-15T10:30:00Z",
"updated": "2024-01-29T14:22:00Z"
},
"content": {
"nav-logo": {
"type": "simple",
"value": "Acme Consulting",
"updated": "2024-01-15T10:30:00Z",
"updatedBy": "client@acmeconsulting.com"
},
"hero-content": {
"type": "rich",
"value": "# Transform Your Business with Expert Consulting\n\nWe help small businesses grow through strategic planning, process optimization, and digital transformation. Our team brings 15+ years of experience to drive your success.\n\n[Get Started Today](contact.html)",
"updated": "2024-01-20T09:15:00Z",
"updatedBy": "client@acmeconsulting.com"
},
"services-title": {
"type": "rich",
"value": "## Our Services\n\nComprehensive solutions tailored to your business needs",
"updated": "2024-01-15T10:30:00Z",
"updatedBy": "client@acmeconsulting.com"
},
"service-strategy": {
"type": "rich",
"value": "### Strategic Planning\n\nDevelop clear roadmaps and actionable strategies that align with your business goals and drive sustainable growth.",
"updated": "2024-01-15T10:30:00Z",
"updatedBy": "client@acmeconsulting.com"
},
"service-operations": {
"type": "rich",
"value": "### Operations Optimization\n\nStreamline processes, reduce costs, and improve efficiency through proven methodologies and best practices.",
"updated": "2024-01-15T10:30:00Z",
"updatedBy": "client@acmeconsulting.com"
},
"service-digital": {
"type": "rich",
"value": "### Digital Transformation\n\nModernize your technology stack and digital presence to compete effectively in today's marketplace.",
"updated": "2024-01-15T10:30:00Z",
"updatedBy": "client@acmeconsulting.com"
},
"testimonial-content": {
"type": "rich",
"value": "> \"Acme Consulting transformed our operations completely. We saw a 40% increase in efficiency within 6 months of implementing their recommendations.\"\n> \n> — Sarah Johnson, CEO of TechStart Inc.",
"updated": "2024-01-25T16:45:00Z",
"updatedBy": "client@acmeconsulting.com"
},
"cta-content": {
"type": "rich",
"value": "## Ready to Transform Your Business?\n\nContact us today for a free consultation and discover how we can help you achieve your goals.\n\n[Schedule Consultation](contact.html)",
"updated": "2024-01-15T10:30:00Z",
"updatedBy": "client@acmeconsulting.com"
},
"footer-info": {
"type": "simple",
"value": "© 2024 Acme Consulting Services. All rights reserved.\n📧 info@acmeconsulting.com | 📞 (555) 123-4567",
"updated": "2024-01-15T10:30:00Z",
"updatedBy": "client@acmeconsulting.com"
}
},
"permissions": {
"client@acmeconsulting.com": {
"role": "editor",
"canEdit": ["*"],
"canDelete": false,
"canCreatePages": false
},
"developer@example.com": {
"role": "admin",
"canEdit": ["*"],
"canDelete": true,
"canCreatePages": true
}
},
"history": [
{
"contentId": "testimonial-content",
"timestamp": "2024-01-25T16:45:00Z",
"user": "client@acmeconsulting.com",
"action": "update",
"previousValue": "> \"Working with Acme Consulting was a game-changer for our business.\"\n> \n> — Sarah Johnson, CEO of TechStart Inc."
}
]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,14 +0,0 @@
<!DOCTYPE html><html><head>
<title>Simple Test</title>
</head>
<body>
<h1 class="insertr" data-content-id="index-h1-e0f926" data-content-type="text">Welcome</h1>
<p class="insertr" data-content-id="index-p-b376ed" data-content-type="markdown">This is a <strong>test</strong> paragraph with <a href="/">a link</a>.</p>
<div class="insertr" data-content-id="index-div-e90881" data-content-type="markdown">
<h2>Section Title</h2>
<p>Another paragraph here.</p>
<button>Click Me</button>
</div>
</body></html>

View File

@@ -1,27 +0,0 @@
# Insertr Configuration for Simple Demo Site
# Specific configuration for the simple test site demo
# Global settings
dev_mode: true # Development mode for demos
# Database configuration
database:
path: "./insertr.db" # Shared database with main config
# Demo-specific configuration
demo:
site_id: "simple" # Unique site ID for simple demo
inject_demo_gate: true # Auto-inject demo gate if no gates exist
mock_auth: true # Use mock authentication for demos
api_endpoint: "http://localhost:8080/api/content"
demo_port: 3000 # Port for live-server
# CLI enhancement configuration
cli:
site_id: "simple" # Site ID for this demo
output: "./simple-demo" # Output directory for enhanced files
inject_demo_gate: true # Inject demo gate in development mode
# Authentication configuration (for demo)
auth:
provider: "mock" # Mock auth for demos

View File

@@ -1,15 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Simple Test</title>
</head>
<body>
<h1>Welcome</h1>
<p>This is a <strong>test</strong> paragraph with <a href="/">a link</a>.</p>
<div>
<h2>Section Title</h2>
<p>Another paragraph here.</p>
<button>Click Me</button>
</div>
</body>
</html>

View File

@@ -1,69 +0,0 @@
#!/bin/bash
# Test script for unified content engine architecture
echo "🔧 Testing Unified Content Engine Architecture"
echo
# Test data
HTML_MARKUP='<h2 class="hero-title">Welcome to Our Site</h2>'
SITE_ID="demo"
FILE_PATH="index.html"
CONTENT_VALUE="Welcome to Our Amazing Website"
CONTENT_TYPE="text"
echo "📝 Test Data:"
echo " HTML Markup: $HTML_MARKUP"
echo " Site ID: $SITE_ID"
echo " File Path: $FILE_PATH"
echo " Content: $CONTENT_VALUE"
echo
# Create JSON payload
JSON_PAYLOAD=$(cat <<EOF
{
"html_markup": "$HTML_MARKUP",
"file_path": "$FILE_PATH",
"site_id": "$SITE_ID",
"value": "$CONTENT_VALUE",
"type": "$CONTENT_TYPE"
}
EOF
)
echo "🌐 Testing API endpoint..."
echo "POST http://localhost:8080/api/content"
echo
# Test the API
RESPONSE=$(curl -s -X POST \
http://localhost:8080/api/content \
-H "Content-Type: application/json" \
-H "Authorization: Bearer mock-token" \
-d "$JSON_PAYLOAD" 2>/dev/null)
if [ $? -eq 0 ] && [ -n "$RESPONSE" ]; then
echo "✅ API Response:"
echo "$RESPONSE" | jq '.' 2>/dev/null || echo "$RESPONSE"
echo
# Extract ID from response if possible
CONTENT_ID=$(echo "$RESPONSE" | jq -r '.id' 2>/dev/null)
if [ "$CONTENT_ID" != "null" ] && [ -n "$CONTENT_ID" ]; then
echo "🎯 Generated Content ID: $CONTENT_ID"
echo
# Test retrieval
echo "🔍 Testing content retrieval..."
GET_RESPONSE=$(curl -s "http://localhost:8080/api/content/$CONTENT_ID?site_id=$SITE_ID" 2>/dev/null)
echo "GET Response:"
echo "$GET_RESPONSE" | jq '.' 2>/dev/null || echo "$GET_RESPONSE"
fi
else
echo "❌ API Request Failed or Server Not Running"
echo "Response: $RESPONSE"
echo
echo "💡 Start the server with: just dev"
fi
echo
echo "🏁 Test Complete"