feat: implement server-hosted static site enhancement with real-time content updates
- Add SiteManager for registering and managing static sites with file-based enhancement - Implement EnhanceInPlace method for in-place file modification using database content - Integrate automatic file enhancement triggers in UpdateContent API handler - Add comprehensive site configuration support in insertr.yaml with auto-enhancement - Extend serve command to automatically register and manage configured sites - Add backup system for original files before enhancement - Support multi-site hosting with individual auto-enhancement settings - Update documentation for server-hosted enhancement workflow This enables real-time content deployment where database content changes immediately update static files without requiring rebuilds or redeployment. The database remains the single source of truth while maintaining static file performance benefits.
This commit is contained in:
@@ -214,3 +214,109 @@ func (e *Enhancer) writeHTML(doc *html.Node, outputPath string) error {
|
||||
// Write HTML
|
||||
return html.Render(file, doc)
|
||||
}
|
||||
|
||||
// EnhanceInPlace performs in-place enhancement of static site files
|
||||
func (e *Enhancer) EnhanceInPlace(sitePath string, siteID string) error {
|
||||
// Update the injector with the correct siteID
|
||||
e.injector.siteID = siteID
|
||||
|
||||
// Use existing parser logic to discover elements
|
||||
result, err := e.parser.ParseDirectory(sitePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing directory: %w", err)
|
||||
}
|
||||
|
||||
if len(result.Elements) == 0 {
|
||||
fmt.Printf("📄 No insertr elements found in %s\n", sitePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Group elements by file for efficient processing
|
||||
fileElements := make(map[string][]parser.Element)
|
||||
for _, elem := range result.Elements {
|
||||
fileElements[elem.FilePath] = append(fileElements[elem.FilePath], elem)
|
||||
}
|
||||
|
||||
// Process each file in-place
|
||||
enhancedCount := 0
|
||||
for filePath, elements := range fileElements {
|
||||
if err := e.enhanceFileInPlace(filePath, elements); err != nil {
|
||||
fmt.Printf("⚠️ Failed to enhance %s: %v\n", filepath.Base(filePath), err)
|
||||
} else {
|
||||
enhancedCount++
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("✅ Enhanced %d files with %d elements in site %s\n",
|
||||
enhancedCount, len(result.Elements), siteID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// enhanceFileInPlace modifies an HTML file in-place with database content
|
||||
func (e *Enhancer) enhanceFileInPlace(filePath string, elements []parser.Element) error {
|
||||
// Read original file
|
||||
htmlContent, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading file: %w", err)
|
||||
}
|
||||
|
||||
// Parse HTML
|
||||
doc, err := html.Parse(strings.NewReader(string(htmlContent)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing HTML: %w", err)
|
||||
}
|
||||
|
||||
// Convert parser elements to injector format with content IDs
|
||||
elementIDs := make([]ElementWithID, 0, len(elements))
|
||||
for _, elem := range elements {
|
||||
// Find the corresponding node in the parsed document
|
||||
node := e.findNodeInDocument(doc, elem)
|
||||
if node != nil {
|
||||
elementIDs = append(elementIDs, ElementWithID{
|
||||
Element: &Element{
|
||||
Node: node,
|
||||
Type: string(elem.Type),
|
||||
Tag: elem.Tag,
|
||||
},
|
||||
ContentID: elem.ContentID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Use existing bulk injection logic for efficiency
|
||||
if len(elementIDs) > 0 {
|
||||
if err := e.injector.InjectBulkContent(elementIDs); err != nil {
|
||||
return fmt.Errorf("injecting content: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Write enhanced HTML back to the same file (in-place update)
|
||||
return e.writeHTML(doc, filePath)
|
||||
}
|
||||
|
||||
// findNodeInDocument finds a specific node in the HTML document tree
|
||||
func (e *Enhancer) findNodeInDocument(doc *html.Node, elem parser.Element) *html.Node {
|
||||
// This is a simplified approach - in a production system we might need
|
||||
// more sophisticated node matching based on attributes, position, etc.
|
||||
return e.findNodeByTagAndClass(doc, elem.Tag, "insertr")
|
||||
}
|
||||
|
||||
// findNodeByTagAndClass recursively searches for a node with specific tag and class
|
||||
func (e *Enhancer) findNodeByTagAndClass(node *html.Node, targetTag, targetClass string) *html.Node {
|
||||
if node.Type == html.ElementNode && node.Data == targetTag {
|
||||
classes := getClasses(node)
|
||||
if containsClass(classes, targetClass) {
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
||||
// Search children
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
if result := e.findNodeByTagAndClass(child, targetTag, targetClass); result != nil {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user