feat: Complete HTML-first architecture implementation with API integration

- Replace value field with html_content for direct HTML storage
- Add original_template field for style detection preservation
- Remove all markdown processing from injector (delete markdown.go)
- Fix critical content extraction/injection bugs in engine
- Add missing UpdateContent PUT handler for content persistence
- Fix API client field names and add updateContent() method
- Resolve content type validation (only allow text/link types)
- Add UUID-based ID generation to prevent collisions
- Complete first-pass processing workflow for unprocessed elements
- Verify end-to-end: Enhancement → Database → API → Editor → Persistence

All 37 files updated for HTML-first content management system.
Phase 3a implementation complete and production ready.
This commit is contained in:
2025-09-20 16:42:00 +02:00
parent bb5ea6f873
commit 2177055c76
37 changed files with 1189 additions and 737 deletions

View File

@@ -12,6 +12,14 @@ import (
"github.com/insertr/insertr/internal/engine"
)
// Helper function to convert sql.NullString to string
func getStringFromNullString(ns sql.NullString) string {
if ns.Valid {
return ns.String
}
return ""
}
// DatabaseClient implements ContentClient for direct database access
type DatabaseClient struct {
db *db.Database
@@ -132,20 +140,22 @@ func (d *DatabaseClient) convertToContentItem(content interface{}) engine.Conten
case "sqlite3":
c := content.(sqlite.Content)
return engine.ContentItem{
ID: c.ID,
SiteID: c.SiteID,
Value: c.Value,
Type: c.Type,
UpdatedAt: time.Unix(c.UpdatedAt, 0).Format(time.RFC3339),
ID: c.ID,
SiteID: c.SiteID,
HTMLContent: c.HtmlContent,
OriginalTemplate: getStringFromNullString(c.OriginalTemplate),
Type: c.Type,
UpdatedAt: time.Unix(c.UpdatedAt, 0).Format(time.RFC3339),
}
case "postgresql":
c := content.(postgresql.Content)
return engine.ContentItem{
ID: c.ID,
SiteID: c.SiteID,
Value: c.Value,
Type: c.Type,
UpdatedAt: time.Unix(c.UpdatedAt, 0).Format(time.RFC3339),
ID: c.ID,
SiteID: c.SiteID,
HTMLContent: c.HtmlContent,
OriginalTemplate: getStringFromNullString(c.OriginalTemplate),
Type: c.Type,
UpdatedAt: time.Unix(c.UpdatedAt, 0).Format(time.RFC3339),
}
}
return engine.ContentItem{} // Should never happen
@@ -171,3 +181,61 @@ func (d *DatabaseClient) convertToContentItemList(contentList interface{}) []eng
}
return []engine.ContentItem{} // Should never happen
}
// CreateContent creates a new content item
func (c *DatabaseClient) CreateContent(siteID, contentID, htmlContent, originalTemplate, contentType, lastEditedBy string) (*engine.ContentItem, error) {
switch c.db.GetDBType() {
case "sqlite3":
content, err := c.db.GetSQLiteQueries().CreateContent(context.Background(), sqlite.CreateContentParams{
ID: contentID,
SiteID: siteID,
HtmlContent: htmlContent,
OriginalTemplate: toNullString(originalTemplate),
Type: contentType,
LastEditedBy: lastEditedBy,
})
if err != nil {
return nil, err
}
return &engine.ContentItem{
ID: content.ID,
SiteID: content.SiteID,
HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
Type: content.Type,
LastEditedBy: content.LastEditedBy,
}, nil
case "postgresql":
content, err := c.db.GetPostgreSQLQueries().CreateContent(context.Background(), postgresql.CreateContentParams{
ID: contentID,
SiteID: siteID,
HtmlContent: htmlContent,
OriginalTemplate: toNullString(originalTemplate),
Type: contentType,
LastEditedBy: lastEditedBy,
})
if err != nil {
return nil, err
}
return &engine.ContentItem{
ID: content.ID,
SiteID: content.SiteID,
HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
Type: content.Type,
LastEditedBy: content.LastEditedBy,
}, nil
default:
return nil, fmt.Errorf("unsupported database type: %s", c.db.GetDBType())
}
}
// Helper function to convert string to sql.NullString
func toNullString(s string) sql.NullString {
if s == "" {
return sql.NullString{Valid: false}
}
return sql.NullString{String: s, Valid: true}
}