Refactor database layer to eliminate type switching and simplify architecture

- Replace type switching with clean repository pattern using sqlc-generated code
- Move ContentRepository interface and domain models to db package
- Create separate SQLiteRepository and PostgreSQLRepository implementations
- Remove unnecessary RepositoryAdapter and ContentClient interface duplication
- Update all clients (HTTP, Mock) to implement db.ContentRepository directly
- Add context.Context parameters to all repository methods (Go best practice)
- Eliminate duplicate domain models and type conversions
- Remove type aliases - use db package types directly throughout codebase
- Update engine, content managers, and API handlers to use repositories directly

Benefits:
- Zero runtime type switching overhead
- Single source of truth for domain models
- Clean package boundaries and separation of concerns
- Standard Go interface patterns with context support
- Easier testing with mockable repository interface
- Maintainable: adding new database types requires no changes to existing code
This commit is contained in:
2025-10-08 19:34:21 +02:00
parent 38c2897ece
commit 01b921bfa3
16 changed files with 785 additions and 712 deletions

View File

@@ -1,544 +0,0 @@
package engine
import (
"context"
"database/sql"
"fmt"
"github.com/insertr/insertr/internal/db"
"github.com/insertr/insertr/internal/db/postgresql"
"github.com/insertr/insertr/internal/db/sqlite"
)
// Helper function to convert sql.NullString to string
func getStringFromNullString(ns sql.NullString) string {
if ns.Valid {
return ns.String
}
return ""
}
// DatabaseClient implements ContentClient interface using the database
type DatabaseClient struct {
database *db.Database
}
// NewDatabaseClient creates a new database client
func NewDatabaseClient(database *db.Database) *DatabaseClient {
return &DatabaseClient{
database: database,
}
}
// GetContent retrieves a single content item
func (c *DatabaseClient) GetContent(siteID, contentID string) (*ContentItem, error) {
switch c.database.GetDBType() {
case "sqlite3":
content, err := c.database.GetSQLiteQueries().GetContent(context.Background(), sqlite.GetContentParams{
ID: contentID,
SiteID: siteID,
})
if err != nil {
return nil, err
}
return &ContentItem{
ID: content.ID,
SiteID: content.SiteID,
HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
LastEditedBy: content.LastEditedBy,
}, nil
case "postgresql":
content, err := c.database.GetPostgreSQLQueries().GetContent(context.Background(), postgresql.GetContentParams{
ID: contentID,
SiteID: siteID,
})
if err != nil {
return nil, err
}
return &ContentItem{
ID: content.ID,
SiteID: content.SiteID,
HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
LastEditedBy: content.LastEditedBy,
}, nil
default:
return nil, fmt.Errorf("unsupported database type: %s", c.database.GetDBType())
}
}
// GetBulkContent retrieves multiple content items
func (c *DatabaseClient) GetBulkContent(siteID string, contentIDs []string) (map[string]ContentItem, error) {
switch c.database.GetDBType() {
case "sqlite3":
contents, err := c.database.GetSQLiteQueries().GetBulkContent(context.Background(), sqlite.GetBulkContentParams{
SiteID: siteID,
Ids: contentIDs,
})
if err != nil {
return nil, err
}
items := make(map[string]ContentItem)
for _, content := range contents {
items[content.ID] = ContentItem{
ID: content.ID,
SiteID: content.SiteID,
HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
LastEditedBy: content.LastEditedBy,
}
}
return items, nil
case "postgresql":
contents, err := c.database.GetPostgreSQLQueries().GetBulkContent(context.Background(), postgresql.GetBulkContentParams{
SiteID: siteID,
Ids: contentIDs,
})
if err != nil {
return nil, err
}
items := make(map[string]ContentItem)
for _, content := range contents {
items[content.ID] = ContentItem{
ID: content.ID,
SiteID: content.SiteID,
HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
LastEditedBy: content.LastEditedBy,
}
}
return items, nil
default:
return nil, fmt.Errorf("unsupported database type: %s", c.database.GetDBType())
}
}
// GetAllContent retrieves all content items for a site
func (c *DatabaseClient) GetAllContent(siteID string) (map[string]ContentItem, error) {
switch c.database.GetDBType() {
case "sqlite3":
contents, err := c.database.GetSQLiteQueries().GetAllContent(context.Background(), siteID)
if err != nil {
return nil, err
}
items := make(map[string]ContentItem)
for _, content := range contents {
items[content.ID] = ContentItem{
ID: content.ID,
SiteID: content.SiteID,
HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
LastEditedBy: content.LastEditedBy,
}
}
return items, nil
case "postgresql":
contents, err := c.database.GetPostgreSQLQueries().GetAllContent(context.Background(), siteID)
if err != nil {
return nil, err
}
items := make(map[string]ContentItem)
for _, content := range contents {
items[content.ID] = ContentItem{
ID: content.ID,
SiteID: content.SiteID,
HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
LastEditedBy: content.LastEditedBy,
}
}
return items, nil
default:
return nil, fmt.Errorf("unsupported database type: %s", c.database.GetDBType())
}
}
// CreateContent creates a new content item
func (c *DatabaseClient) CreateContent(siteID, contentID, htmlContent, originalTemplate, lastEditedBy string) (*ContentItem, error) {
switch c.database.GetDBType() {
case "sqlite3":
content, err := c.database.GetSQLiteQueries().CreateContent(context.Background(), sqlite.CreateContentParams{
ID: contentID,
SiteID: siteID,
HtmlContent: htmlContent,
OriginalTemplate: ToNullString(originalTemplate),
LastEditedBy: lastEditedBy,
})
if err != nil {
return nil, err
}
return &ContentItem{
ID: content.ID,
SiteID: content.SiteID,
HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
LastEditedBy: content.LastEditedBy,
}, nil
case "postgresql":
content, err := c.database.GetPostgreSQLQueries().CreateContent(context.Background(), postgresql.CreateContentParams{
ID: contentID,
SiteID: siteID,
HtmlContent: htmlContent,
OriginalTemplate: ToNullString(originalTemplate),
LastEditedBy: lastEditedBy,
})
if err != nil {
return nil, err
}
return &ContentItem{
ID: content.ID,
SiteID: content.SiteID,
HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
LastEditedBy: content.LastEditedBy,
}, nil
default:
return nil, fmt.Errorf("unsupported database type: %s", c.database.GetDBType())
}
}
// GetCollection retrieves a collection container
func (c *DatabaseClient) GetCollection(siteID, collectionID string) (*CollectionItem, error) {
switch c.database.GetDBType() {
case "sqlite3":
collection, err := c.database.GetSQLiteQueries().GetCollection(context.Background(), sqlite.GetCollectionParams{
ID: collectionID,
SiteID: siteID,
})
if err != nil {
return nil, err
}
return &CollectionItem{
ID: collection.ID,
SiteID: collection.SiteID,
ContainerHTML: collection.ContainerHtml,
LastEditedBy: collection.LastEditedBy,
}, nil
case "postgresql":
collection, err := c.database.GetPostgreSQLQueries().GetCollection(context.Background(), postgresql.GetCollectionParams{
ID: collectionID,
SiteID: siteID,
})
if err != nil {
return nil, err
}
return &CollectionItem{
ID: collection.ID,
SiteID: collection.SiteID,
ContainerHTML: collection.ContainerHtml,
LastEditedBy: collection.LastEditedBy,
}, nil
default:
return nil, fmt.Errorf("unsupported database type: %s", c.database.GetDBType())
}
}
// CreateCollection creates a new collection container
func (c *DatabaseClient) CreateCollection(siteID, collectionID, containerHTML, lastEditedBy string) (*CollectionItem, error) {
switch c.database.GetDBType() {
case "sqlite3":
collection, err := c.database.GetSQLiteQueries().CreateCollection(context.Background(), sqlite.CreateCollectionParams{
ID: collectionID,
SiteID: siteID,
ContainerHtml: containerHTML,
LastEditedBy: lastEditedBy,
})
if err != nil {
return nil, err
}
return &CollectionItem{
ID: collection.ID,
SiteID: collection.SiteID,
ContainerHTML: collection.ContainerHtml,
LastEditedBy: collection.LastEditedBy,
}, nil
case "postgresql":
collection, err := c.database.GetPostgreSQLQueries().CreateCollection(context.Background(), postgresql.CreateCollectionParams{
ID: collectionID,
SiteID: siteID,
ContainerHtml: containerHTML,
LastEditedBy: lastEditedBy,
})
if err != nil {
return nil, err
}
return &CollectionItem{
ID: collection.ID,
SiteID: collection.SiteID,
ContainerHTML: collection.ContainerHtml,
LastEditedBy: collection.LastEditedBy,
}, nil
default:
return nil, fmt.Errorf("unsupported database type: %s", c.database.GetDBType())
}
}
// GetCollectionItems retrieves all items in a collection with template information
func (c *DatabaseClient) GetCollectionItems(siteID, collectionID string) ([]CollectionItemWithTemplate, error) {
switch c.database.GetDBType() {
case "sqlite3":
items, err := c.database.GetSQLiteQueries().GetCollectionItemsWithTemplate(context.Background(), sqlite.GetCollectionItemsWithTemplateParams{
CollectionID: collectionID,
SiteID: siteID,
})
if err != nil {
return nil, err
}
result := make([]CollectionItemWithTemplate, len(items))
for i, item := range items {
result[i] = CollectionItemWithTemplate{
ItemID: item.ItemID,
CollectionID: item.CollectionID,
SiteID: item.SiteID,
TemplateID: int(item.TemplateID),
HTMLContent: item.HtmlContent,
Position: int(item.Position),
LastEditedBy: item.LastEditedBy,
TemplateName: item.TemplateName,
HTMLTemplate: item.HtmlTemplate,
IsDefault: item.IsDefault != 0, // SQLite uses INTEGER for boolean
}
}
return result, nil
case "postgresql":
items, err := c.database.GetPostgreSQLQueries().GetCollectionItemsWithTemplate(context.Background(), postgresql.GetCollectionItemsWithTemplateParams{
CollectionID: collectionID,
SiteID: siteID,
})
if err != nil {
return nil, err
}
result := make([]CollectionItemWithTemplate, len(items))
for i, item := range items {
result[i] = CollectionItemWithTemplate{
ItemID: item.ItemID,
CollectionID: item.CollectionID,
SiteID: item.SiteID,
TemplateID: int(item.TemplateID),
HTMLContent: item.HtmlContent,
Position: int(item.Position),
LastEditedBy: item.LastEditedBy,
TemplateName: item.TemplateName,
HTMLTemplate: item.HtmlTemplate,
IsDefault: item.IsDefault, // PostgreSQL uses BOOLEAN
}
}
return result, nil
default:
return nil, fmt.Errorf("unsupported database type: %s", c.database.GetDBType())
}
}
// CreateCollectionTemplate creates a new template for a collection
func (c *DatabaseClient) CreateCollectionTemplate(siteID, collectionID, name, htmlTemplate string, isDefault bool) (*CollectionTemplateItem, error) {
switch c.database.GetDBType() {
case "sqlite3":
var isDefaultInt int64
if isDefault {
isDefaultInt = 1
}
template, err := c.database.GetSQLiteQueries().CreateCollectionTemplate(context.Background(), sqlite.CreateCollectionTemplateParams{
CollectionID: collectionID,
SiteID: siteID,
Name: name,
HtmlTemplate: htmlTemplate,
IsDefault: isDefaultInt,
})
if err != nil {
return nil, err
}
return &CollectionTemplateItem{
TemplateID: int(template.TemplateID),
CollectionID: template.CollectionID,
SiteID: template.SiteID,
Name: template.Name,
HTMLTemplate: template.HtmlTemplate,
IsDefault: template.IsDefault != 0,
}, nil
case "postgresql":
template, err := c.database.GetPostgreSQLQueries().CreateCollectionTemplate(context.Background(), postgresql.CreateCollectionTemplateParams{
CollectionID: collectionID,
SiteID: siteID,
Name: name,
HtmlTemplate: htmlTemplate,
IsDefault: isDefault,
})
if err != nil {
return nil, err
}
return &CollectionTemplateItem{
TemplateID: int(template.TemplateID),
CollectionID: template.CollectionID,
SiteID: template.SiteID,
Name: template.Name,
HTMLTemplate: template.HtmlTemplate,
IsDefault: template.IsDefault,
}, nil
default:
return nil, fmt.Errorf("unsupported database type: %s", c.database.GetDBType())
}
}
// GetCollectionTemplates retrieves all templates for a collection
func (c *DatabaseClient) GetCollectionTemplates(siteID, collectionID string) ([]CollectionTemplateItem, error) {
switch c.database.GetDBType() {
case "sqlite3":
templates, err := c.database.GetSQLiteQueries().GetCollectionTemplates(context.Background(), sqlite.GetCollectionTemplatesParams{
CollectionID: collectionID,
SiteID: siteID,
})
if err != nil {
return nil, err
}
result := make([]CollectionTemplateItem, len(templates))
for i, template := range templates {
result[i] = CollectionTemplateItem{
TemplateID: int(template.TemplateID),
CollectionID: template.CollectionID,
SiteID: template.SiteID,
Name: template.Name,
HTMLTemplate: template.HtmlTemplate,
IsDefault: template.IsDefault != 0, // SQLite uses INTEGER for boolean
}
}
return result, nil
case "postgresql":
templates, err := c.database.GetPostgreSQLQueries().GetCollectionTemplates(context.Background(), postgresql.GetCollectionTemplatesParams{
CollectionID: collectionID,
SiteID: siteID,
})
if err != nil {
return nil, err
}
result := make([]CollectionTemplateItem, len(templates))
for i, template := range templates {
result[i] = CollectionTemplateItem{
TemplateID: int(template.TemplateID),
CollectionID: template.CollectionID,
SiteID: template.SiteID,
Name: template.Name,
HTMLTemplate: template.HtmlTemplate,
IsDefault: template.IsDefault, // PostgreSQL uses BOOLEAN
}
}
return result, nil
default:
return nil, fmt.Errorf("unsupported database type: %s", c.database.GetDBType())
}
}
// CreateCollectionItem creates a new collection item
func (c *DatabaseClient) CreateCollectionItem(siteID, collectionID, itemID string, templateID int, htmlContent string, position int, lastEditedBy string) (*CollectionItemWithTemplate, error) {
switch c.database.GetDBType() {
case "sqlite3":
item, err := c.database.GetSQLiteQueries().CreateCollectionItem(context.Background(), sqlite.CreateCollectionItemParams{
ItemID: itemID,
CollectionID: collectionID,
SiteID: siteID,
TemplateID: int64(templateID),
HtmlContent: htmlContent,
Position: int64(position),
LastEditedBy: lastEditedBy,
})
if err != nil {
return nil, err
}
return &CollectionItemWithTemplate{
ItemID: item.ItemID,
CollectionID: item.CollectionID,
SiteID: item.SiteID,
TemplateID: int(item.TemplateID),
HTMLContent: item.HtmlContent,
Position: int(item.Position),
LastEditedBy: item.LastEditedBy,
}, nil
case "postgresql":
item, err := c.database.GetPostgreSQLQueries().CreateCollectionItem(context.Background(), postgresql.CreateCollectionItemParams{
ItemID: itemID,
CollectionID: collectionID,
SiteID: siteID,
TemplateID: int32(templateID),
HtmlContent: htmlContent,
Position: int32(position),
LastEditedBy: lastEditedBy,
})
if err != nil {
return nil, err
}
return &CollectionItemWithTemplate{
ItemID: item.ItemID,
CollectionID: item.CollectionID,
SiteID: item.SiteID,
TemplateID: int(item.TemplateID),
HTMLContent: item.HtmlContent,
Position: int(item.Position),
LastEditedBy: item.LastEditedBy,
}, nil
default:
return nil, fmt.Errorf("unsupported database type: %s", c.database.GetDBType())
}
}
// CreateCollectionItemAtomic creates a collection item with all its content entries atomically
func (c *DatabaseClient) CreateCollectionItemAtomic(
siteID, collectionID string,
templateID int,
lastEditedBy string,
) (*CollectionItemWithTemplate, error) {
// Get template HTML for processing
templates, err := c.GetCollectionTemplates(siteID, collectionID)
if err != nil {
return nil, fmt.Errorf("failed to get templates: %w", err)
}
var templateHTML string
for _, template := range templates {
if template.TemplateID == templateID {
templateHTML = template.HTMLTemplate
break
}
}
if templateHTML == "" {
return nil, fmt.Errorf("template %d not found", templateID)
}
// Use unified engine approach (no more TemplateProcessor)
engine := NewContentEngine(c)
// Create collection item using unified engine method
return engine.CreateCollectionItemFromTemplate(
siteID, collectionID, templateID, templateHTML, lastEditedBy,
)
}

View File

@@ -1,9 +1,11 @@
package engine
import (
"context"
"fmt"
"strings"
"github.com/insertr/insertr/internal/db"
"golang.org/x/net/html"
)
@@ -15,13 +17,13 @@ type AuthProvider struct {
// ContentEngine is the unified content processing engine
type ContentEngine struct {
idGenerator *IDGenerator
client ContentClient
client db.ContentRepository
authProvider *AuthProvider
injector *Injector
}
// NewContentEngine creates a new content processing engine
func NewContentEngine(client ContentClient) *ContentEngine {
func NewContentEngine(client db.ContentRepository) *ContentEngine {
authProvider := &AuthProvider{Type: "mock"} // default
return &ContentEngine{
idGenerator: NewIDGenerator(),
@@ -32,7 +34,7 @@ func NewContentEngine(client ContentClient) *ContentEngine {
}
// NewContentEngineWithAuth creates a new content processing engine with auth config
func NewContentEngineWithAuth(client ContentClient, authProvider *AuthProvider) *ContentEngine {
func NewContentEngineWithAuth(client db.ContentRepository, authProvider *AuthProvider) *ContentEngine {
if authProvider == nil {
authProvider = &AuthProvider{Type: "mock"}
}
@@ -64,7 +66,7 @@ func (e *ContentEngine) ProcessContent(input ContentInput) (*ContentResult, erro
id := e.idGenerator.Generate(elem.Node, input.FilePath)
// Database-first approach: Check if content already exists
existingContent, err := e.client.GetContent(input.SiteID, id)
existingContent, err := e.client.GetContent(context.Background(), input.SiteID, id)
contentExists := (err == nil && existingContent != nil)
generatedIDs[fmt.Sprintf("element_%d", i)] = id
@@ -87,7 +89,7 @@ func (e *ContentEngine) ProcessContent(input ContentInput) (*ContentResult, erro
originalTemplate := e.extractOriginalTemplate(elem.Node)
// Store in database via content client
_, err := e.client.CreateContent(input.SiteID, id, htmlContent, originalTemplate, "system")
_, 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)
@@ -343,7 +345,7 @@ func (e *ContentEngine) injectContent(elements []ProcessedElement, siteID string
elem := &elements[i]
// Try to get content from database
contentItem, err := e.client.GetContent(siteID, elem.ID)
contentItem, err := e.client.GetContent(context.Background(), siteID, elem.ID)
if err != nil {
// Content not found is not an error - element just won't have injected content
continue
@@ -467,14 +469,14 @@ func (e *ContentEngine) getPlaceholderForElement(elementType string) string {
// processCollection handles collection detection, persistence and reconstruction
func (e *ContentEngine) processCollection(collectionNode *html.Node, collectionID, siteID string) error {
// 1. Check if collection exists in database
existingCollection, err := e.client.GetCollection(siteID, collectionID)
existingCollection, err := e.client.GetCollection(context.Background(), siteID, collectionID)
collectionExists := (err == nil && existingCollection != nil)
if !collectionExists {
// 2. New collection: extract container HTML and create collection record
containerHTML := e.extractOriginalTemplate(collectionNode)
_, err := e.client.CreateCollection(siteID, collectionID, containerHTML, "system")
_, err := e.client.CreateCollection(context.Background(), siteID, collectionID, containerHTML, "system")
if err != nil {
return fmt.Errorf("failed to create collection %s: %w", collectionID, err)
}
@@ -494,7 +496,7 @@ func (e *ContentEngine) processCollection(collectionNode *html.Node, collectionI
}
// Get final item count for logging
existingItems, _ := e.client.GetCollectionItems(siteID, collectionID)
existingItems, _ := e.client.GetCollectionItems(context.Background(), siteID, collectionID)
fmt.Printf("✅ Reconstructed collection: %s from database (%d items)\n", collectionID, len(existingItems))
}
@@ -515,7 +517,7 @@ func (e *ContentEngine) extractAndStoreTemplatesAndItems(collectionNode *html.No
if len(templateElements) == 0 {
// No existing children - create a default empty template
_, err := e.client.CreateCollectionTemplate(siteID, collectionID, "default", "<div>New item</div>", true)
_, err := e.client.CreateCollectionTemplate(context.Background(), siteID, collectionID, "default", "<div>New item</div>", true)
if err != nil {
return fmt.Errorf("failed to create default template: %w", err)
}
@@ -530,7 +532,7 @@ func (e *ContentEngine) extractAndStoreTemplatesAndItems(collectionNode *html.No
templateName := fmt.Sprintf("template-%d", i+1)
isDefault := (i == 0) // First template is default
template, err := e.client.CreateCollectionTemplate(siteID, collectionID, templateName, templateHTML, isDefault)
template, err := e.client.CreateCollectionTemplate(context.Background(), siteID, collectionID, templateName, templateHTML, isDefault)
if err != nil {
return fmt.Errorf("failed to create template %s: %w", templateName, err)
}
@@ -557,13 +559,13 @@ func (e *ContentEngine) extractAndStoreTemplatesAndItems(collectionNode *html.No
// reconstructCollectionItems rebuilds collection items from database and adds them to DOM
func (e *ContentEngine) reconstructCollectionItems(collectionNode *html.Node, collectionID, siteID string) error {
// Get all items for this collection from database
items, err := e.client.GetCollectionItems(siteID, collectionID)
items, err := e.client.GetCollectionItems(context.Background(), siteID, collectionID)
if err != nil {
return fmt.Errorf("failed to get collection items: %w", err)
}
// Get templates for this collection
templates, err := e.client.GetCollectionTemplates(siteID, collectionID)
templates, err := e.client.GetCollectionTemplates(context.Background(), siteID, collectionID)
if err != nil {
return fmt.Errorf("failed to get collection templates: %w", err)
}
@@ -652,7 +654,7 @@ func (e *ContentEngine) processChildElementsAsContent(childElement *html.Node, s
actualContent := ExtractTextContent(n)
// Store as individual content entry (unified .insertr approach)
_, err := e.client.CreateContent(siteID, contentID, actualContent, "", "system")
_, err := e.client.CreateContent(context.Background(), siteID, contentID, actualContent, "", "system")
if err != nil {
fmt.Printf("⚠️ Failed to create content %s: %v\n", contentID, err)
return
@@ -737,7 +739,7 @@ func (e *ContentEngine) CreateCollectionItemFromTemplate(
templateID int,
templateHTML string,
lastEditedBy string,
) (*CollectionItemWithTemplate, error) {
) (*db.CollectionItemWithTemplate, error) {
// Create virtual element from template (like enhancement path)
virtualElement, err := e.createVirtualElementFromTemplate(templateHTML)
if err != nil {
@@ -763,7 +765,7 @@ func (e *ContentEngine) CreateCollectionItemFromTemplate(
}
// Create collection item with structural template
collectionItem, err := e.client.CreateCollectionItem(
collectionItem, err := e.client.CreateCollectionItem(context.Background(),
siteID, collectionID, itemID, templateID, structuralTemplate, 0, lastEditedBy,
)
if err != nil {
@@ -838,7 +840,7 @@ func (e *ContentEngine) storeChildrenAsCollectionItems(collectionNode *html.Node
templateID := templateIDs[i%len(templateIDs)]
// Store structural template in collection_items (content lives in content table)
_, err = e.client.CreateCollectionItem(siteID, collectionID, itemID, templateID, structuralTemplate, i+1, "system")
_, err = e.client.CreateCollectionItem(context.Background(), siteID, collectionID, itemID, templateID, structuralTemplate, i+1, "system")
if err != nil {
return fmt.Errorf("failed to create collection item %s: %w", itemID, err)
}

View File

@@ -1,22 +1,24 @@
package engine
import (
"context"
"fmt"
"log"
"strings"
"github.com/insertr/insertr/internal/db"
"golang.org/x/net/html"
)
// Injector handles content injection into HTML elements
type Injector struct {
client ContentClient
client db.ContentRepository
siteID string
authProvider *AuthProvider
}
// NewInjector creates a new content injector
func NewInjector(client ContentClient, siteID string) *Injector {
func NewInjector(client db.ContentRepository, siteID string) *Injector {
return &Injector{
client: client,
siteID: siteID,
@@ -25,7 +27,7 @@ func NewInjector(client ContentClient, siteID string) *Injector {
}
// NewInjectorWithAuth creates a new content injector with auth provider
func NewInjectorWithAuth(client ContentClient, siteID string, authProvider *AuthProvider) *Injector {
func NewInjectorWithAuth(client db.ContentRepository, siteID string, authProvider *AuthProvider) *Injector {
if authProvider == nil {
authProvider = &AuthProvider{Type: "mock"}
}
@@ -39,7 +41,7 @@ func NewInjectorWithAuth(client ContentClient, siteID string, authProvider *Auth
// InjectContent replaces element content with database values and adds content IDs
func (i *Injector) InjectContent(element *Element, contentID string) error {
// Fetch content from database/API
contentItem, err := i.client.GetContent(i.siteID, contentID)
contentItem, err := i.client.GetContent(context.Background(), i.siteID, contentID)
if err != nil {
return fmt.Errorf("fetching content for %s: %w", contentID, err)
}
@@ -68,7 +70,7 @@ func (i *Injector) InjectBulkContent(elements []ElementWithID) error {
}
// Bulk fetch content
contentMap, err := i.client.GetBulkContent(i.siteID, contentIDs)
contentMap, err := i.client.GetBulkContent(context.Background(), i.siteID, contentIDs)
if err != nil {
return fmt.Errorf("bulk fetching content: %w", err)
}

View File

@@ -48,73 +48,3 @@ type ContentEntry struct {
HTMLContent string
Template string
}
// ContentClient interface for accessing content data
// This will be implemented by database clients, HTTP clients, and mock clients
type ContentClient interface {
GetContent(siteID, contentID string) (*ContentItem, error)
GetBulkContent(siteID string, contentIDs []string) (map[string]ContentItem, error)
GetAllContent(siteID string) (map[string]ContentItem, error)
CreateContent(siteID, contentID, htmlContent, originalTemplate, lastEditedBy string) (*ContentItem, error)
// Collection operations
GetCollection(siteID, collectionID string) (*CollectionItem, error)
CreateCollection(siteID, collectionID, containerHTML, lastEditedBy string) (*CollectionItem, error)
GetCollectionItems(siteID, collectionID string) ([]CollectionItemWithTemplate, error)
GetCollectionTemplates(siteID, collectionID string) ([]CollectionTemplateItem, error)
CreateCollectionTemplate(siteID, collectionID, name, htmlTemplate string, isDefault bool) (*CollectionTemplateItem, error)
CreateCollectionItem(siteID, collectionID, itemID string, templateID int, htmlContent string, position int, lastEditedBy string) (*CollectionItemWithTemplate, error)
CreateCollectionItemAtomic(siteID, collectionID string, templateID int, lastEditedBy string) (*CollectionItemWithTemplate, error)
}
// ContentItem represents a piece of content from the database
type ContentItem struct {
ID string `json:"id"`
SiteID string `json:"site_id"`
HTMLContent string `json:"html_content"`
OriginalTemplate string `json:"original_template"`
UpdatedAt string `json:"updated_at"`
LastEditedBy string `json:"last_edited_by,omitempty"`
}
// ContentResponse represents the API response structure
type ContentResponse struct {
Content []ContentItem `json:"content"`
Error string `json:"error,omitempty"`
}
// CollectionItem represents a collection container from the database
type CollectionItem struct {
ID string `json:"id"`
SiteID string `json:"site_id"`
ContainerHTML string `json:"container_html"`
UpdatedAt string `json:"updated_at"`
LastEditedBy string `json:"last_edited_by,omitempty"`
}
// CollectionTemplateItem represents a collection template from the database
type CollectionTemplateItem struct {
TemplateID int `json:"template_id"`
CollectionID string `json:"collection_id"`
SiteID string `json:"site_id"`
Name string `json:"name"`
HTMLTemplate string `json:"html_template"`
IsDefault bool `json:"is_default"`
}
// CollectionItemWithTemplate represents a collection item with its template information
type CollectionItemWithTemplate struct {
ItemID string `json:"item_id"`
CollectionID string `json:"collection_id"`
SiteID string `json:"site_id"`
TemplateID int `json:"template_id"`
HTMLContent string `json:"html_content"`
Position int `json:"position"`
UpdatedAt string `json:"updated_at"`
LastEditedBy string `json:"last_edited_by"`
// Template information
TemplateName string `json:"template_name"`
HTMLTemplate string `json:"html_template"`
IsDefault bool `json:"is_default"`
}

View File

@@ -1,7 +1,6 @@
package engine
import (
"database/sql"
"strings"
"golang.org/x/net/html"
@@ -38,7 +37,6 @@ func getAttribute(node *html.Node, key string) string {
return ""
}
// hasOnlyTextContent checks if a node contains only text content (no nested HTML elements)
// DEPRECATED: Use hasEditableContent for more sophisticated detection
func hasOnlyTextContent(node *html.Node) bool {
@@ -303,7 +301,6 @@ func hasInsertrClass(node *html.Node) bool {
return false
}
// isSelfClosing checks if an element is typically self-closing
func isSelfClosing(node *html.Node) bool {
if node.Type != html.ElementNode {
@@ -376,24 +373,6 @@ func FindViableChildren(node *html.Node) []*html.Node {
return findViableChildren(node)
}
// SQL utility functions for consistent null string handling
// ToNullString converts a string to sql.NullString
func ToNullString(s string) sql.NullString {
if s == "" {
return sql.NullString{Valid: false}
}
return sql.NullString{String: s, Valid: true}
}
// FromNullString converts sql.NullString to string
func FromNullString(ns sql.NullString) string {
if ns.Valid {
return ns.String
}
return ""
}
// Text extraction utility functions
// ExtractTextContent extracts all text content from an HTML node recursively