Split monolithic engine.go (776 lines) into specialized files: - engine.go: Core orchestration (142 lines, 82% reduction) - collection.go: Collection processing and management (445 lines) - content.go: Content injection and extraction (152 lines) - discovery.go: Element discovery and DOM traversal (85 lines) Benefits: - Single responsibility principle applied to each file - Better code organization and navigation - Improved testability of individual components - Easier team development and code reviews - Maintained full API compatibility with no breaking changes
143 lines
4.7 KiB
Go
143 lines
4.7 KiB
Go
package engine
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/insertr/insertr/internal/db"
|
|
"golang.org/x/net/html"
|
|
)
|
|
|
|
// AuthProvider represents authentication provider information
|
|
type AuthProvider struct {
|
|
Type string // "mock", "jwt", "authentik"
|
|
}
|
|
|
|
// ContentEngine is the unified content processing engine
|
|
type ContentEngine struct {
|
|
idGenerator *IDGenerator
|
|
client db.ContentRepository
|
|
authProvider *AuthProvider
|
|
injector *Injector
|
|
}
|
|
|
|
// NewContentEngine creates a new content processing engine
|
|
func NewContentEngine(client db.ContentRepository) *ContentEngine {
|
|
authProvider := &AuthProvider{Type: "mock"} // default
|
|
return &ContentEngine{
|
|
idGenerator: NewIDGenerator(),
|
|
client: client,
|
|
authProvider: authProvider,
|
|
injector: NewInjector(client, "", nil), // siteID will be set per operation
|
|
}
|
|
}
|
|
|
|
// NewContentEngineWithAuth creates a new content processing engine with auth config
|
|
func NewContentEngineWithAuth(client db.ContentRepository, authProvider *AuthProvider) *ContentEngine {
|
|
if authProvider == nil {
|
|
authProvider = &AuthProvider{Type: "mock"}
|
|
}
|
|
return &ContentEngine{
|
|
idGenerator: NewIDGenerator(),
|
|
client: client,
|
|
authProvider: authProvider,
|
|
injector: NewInjectorWithAuth(client, "", authProvider, nil), // siteID will be set per operation
|
|
}
|
|
}
|
|
|
|
// ProcessContent processes HTML content according to the specified mode
|
|
func (e *ContentEngine) ProcessContent(input ContentInput) (*ContentResult, error) {
|
|
// 1. Parse HTML
|
|
doc, err := html.Parse(strings.NewReader(string(input.HTML)))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing HTML: %w", err)
|
|
}
|
|
|
|
// 2. Find insertr and collection elements
|
|
insertrElements, collectionElements := e.findEditableElements(doc)
|
|
|
|
// 3. Process regular .insertr elements
|
|
generatedIDs := make(map[string]string)
|
|
processedElements := make([]ProcessedElement, len(insertrElements))
|
|
|
|
for i, elem := range insertrElements {
|
|
// Generate structural ID (always deterministic)
|
|
id := e.idGenerator.Generate(elem.Node, input.FilePath)
|
|
|
|
// Database-first approach: Check if content already exists
|
|
existingContent, err := e.client.GetContent(context.Background(), input.SiteID, id)
|
|
contentExists := (err == nil && existingContent != nil)
|
|
|
|
generatedIDs[fmt.Sprintf("element_%d", i)] = id
|
|
|
|
processedElements[i] = ProcessedElement{
|
|
Node: elem.Node,
|
|
ID: id,
|
|
Generated: !contentExists, // Mark as generated only if new to database
|
|
Tag: elem.Node.Data,
|
|
Classes: GetClasses(elem.Node),
|
|
}
|
|
|
|
// Add/update content attributes to the node (only content-id now)
|
|
e.addContentAttributes(elem.Node, id)
|
|
|
|
// Store content only for truly new elements (database-first check)
|
|
if !contentExists && (input.Mode == Enhancement || input.Mode == ContentInjection) {
|
|
// Extract content and template from the unprocessed element
|
|
htmlContent := e.extractHTMLContent(elem.Node)
|
|
originalTemplate := e.extractOriginalTemplate(elem.Node)
|
|
|
|
// Store in database via content client
|
|
_, err := e.client.CreateContent(context.Background(), input.SiteID, id, htmlContent, originalTemplate, "system")
|
|
if err != nil {
|
|
// Log error but don't fail the enhancement - content just won't be stored
|
|
fmt.Printf("⚠️ Failed to store content for %s: %v\n", id, err)
|
|
} else {
|
|
fmt.Printf("✅ Created new content: %s (html)\n", id)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 4. Process .insertr-add collection elements
|
|
for _, collectionElem := range collectionElements {
|
|
// Generate structural ID for the collection container
|
|
collectionID := e.idGenerator.Generate(collectionElem.Node, input.FilePath)
|
|
|
|
// Add data-collection-id attribute to the collection container
|
|
SetAttribute(collectionElem.Node, "data-collection-id", collectionID)
|
|
|
|
// Process collection during enhancement or content injection
|
|
if input.Mode == Enhancement || input.Mode == ContentInjection {
|
|
err := e.processCollection(collectionElem.Node, collectionID, input.SiteID)
|
|
if err != nil {
|
|
fmt.Printf("⚠️ Failed to process collection %s: %v\n", collectionID, err)
|
|
} else {
|
|
fmt.Printf("✅ Processed collection: %s\n", collectionID)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 6. Inject content if required by mode
|
|
if input.Mode == Enhancement || input.Mode == ContentInjection {
|
|
err = e.injectContent(processedElements, input.SiteID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("injecting content: %w", err)
|
|
}
|
|
}
|
|
|
|
// TODO: Implement collection-specific content injection here if needed
|
|
|
|
// 6. Inject editor assets for enhancement mode (development)
|
|
if input.Mode == Enhancement {
|
|
injector := NewInjectorWithAuth(e.client, input.SiteID, e.authProvider, nil)
|
|
injector.InjectEditorAssets(doc, true, "")
|
|
}
|
|
|
|
return &ContentResult{
|
|
Document: doc,
|
|
Elements: processedElements,
|
|
GeneratedIDs: generatedIDs,
|
|
}, nil
|
|
}
|