diff --git a/SERVER_UPDATE.md b/SERVER_UPDATE.md
index 650666d..83ce195 100644
--- a/SERVER_UPDATE.md
+++ b/SERVER_UPDATE.md
@@ -4,229 +4,263 @@
### **What We Discovered**
Our frontend has evolved to a sophisticated **HTML-first approach** with:
-- Style-aware editing with automatic style detection
+- StyleAware editor with automatic style detection from nested elements
- HTML preservation with perfect attribute fidelity
-- Rich content editing capabilities
-- Template-based style preservation
+- Rich content editing capabilities with formatting toolbar
+- Template-based style preservation using CLASSES.md methodology
However, our **server API is still text-focused**, creating a fundamental mismatch between frontend capabilities and backend storage.
-### **Core Issues Identified**
-1. **Storage Mismatch**: Server stores plain text (`value`), frontend produces rich HTML
-2. **Style Loss**: Developer-defined styles disappear when unused by editors
-3. **Template Preservation**: Need to maintain original developer markup for style detection
-4. **Dual Mode Challenge**: Development iteration vs. production stability requirements
+### **Core Requirements Identified**
+1. **HTML-First Storage**: Replace `value` with `html_content` field for direct HTML storage
+2. **Template Preservation**: Store `original_template` for consistent style detection
+3. **Enhancer-First Workflow**: Enhancer stores content on first pass, ignores processed elements
+4. **No Markdown Processing**: Remove all markdown logic from injector - HTML only
+5. **StyleAware Editor Compatibility**: API must match library expectations
+6. **Dev Convenience**: Option to clean DB for fresh development iterations
-## 🏗️ Proposed Architecture Changes
+## 🏗️ Implementation Strategy
-### **1. HTML-First Database Schema**
+### **1. HTML-First Database Schema (Direct Replacement)**
-**Updated Schema (No Backwards Compatibility Required):**
+**Updated Schema:**
```sql
+-- SQLite schema
CREATE TABLE content (
- id TEXT PRIMARY KEY,
+ id TEXT NOT NULL,
site_id TEXT NOT NULL,
- html_content TEXT NOT NULL, -- Rich HTML (for BOTH editing AND injection)
- original_markup TEXT, -- Developer template markup (for style detection)
- template_locked BOOLEAN DEFAULT FALSE, -- Development vs Production mode
+ html_content TEXT NOT NULL, -- Rich HTML content (innerHTML)
+ original_template TEXT, -- Original element markup for style detection (outerHTML)
type TEXT NOT NULL,
- created_at INTEGER NOT NULL,
- updated_at INTEGER NOT NULL,
- last_edited_by TEXT NOT NULL,
- UNIQUE(site_id, id)
+ created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
+ updated_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
+ last_edited_by TEXT DEFAULT 'system' NOT NULL,
+ PRIMARY KEY (id, site_id)
);
-CREATE TABLE content_versions (
- version_id INTEGER PRIMARY KEY AUTOINCREMENT,
- content_id TEXT NOT NULL,
+-- PostgreSQL schema
+CREATE TABLE content (
+ id TEXT NOT NULL,
site_id TEXT NOT NULL,
- html_content TEXT NOT NULL, -- Version HTML content
- original_markup TEXT, -- Template at time of version
+ html_content TEXT NOT NULL, -- Rich HTML content (innerHTML)
+ original_template TEXT, -- Original element markup for style detection (outerHTML)
type TEXT NOT NULL,
- created_at INTEGER NOT NULL,
- created_by TEXT NOT NULL
+ created_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL,
+ updated_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL,
+ last_edited_by TEXT DEFAULT 'system' NOT NULL,
+ PRIMARY KEY (id, site_id)
);
```
**Key Changes:**
- ✅ **Removed `value` field** - HTML serves both editing and injection needs
-- ✅ **Added `original_markup`** - Preserves developer templates for style detection
-- ✅ **Added `template_locked`** - Controls template update behavior
-- ✅ **Unified storage** - Same HTML content used for build injection and editing
+- ✅ **Added `html_content`** - Direct HTML storage for content editing and injection
+- ✅ **Added `original_template`** - Preserves developer templates for StyleAware editor style detection
+- ✅ **Simplified approach** - No complex template locking, focus on core functionality
-### **2. Template Lifecycle Management**
+### **2. Enhancer-First Workflow (First-Pass Processing)**
-#### **Development Mode (template_locked = false):**
-- Enhancement **updates templates** when developer markup changes significantly
-- API editing **preserves templates**, only updates content
-- Supports rapid iteration and template refinement
+#### **Unprocessed Element Detection:**
+- Elements without `data-content-id` attribute are unprocessed
+- Enhancer processes these elements and assigns IDs
+- Subsequent enhancer runs skip elements that already have `data-content-id`
-#### **Production Mode (template_locked = true):**
-- Enhancement **preserves existing templates** regardless of markup changes
-- API editing **never affects templates**
-- Ensures developer styles always available to clients
-
-#### **Template Management Commands:**
-```bash
-# Lock templates for production handoff
-insertr templates lock --site-id mysite
-
-# Edit specific template (opens in $EDITOR)
-insertr templates edit --site-id mysite --content-id hero-title-abc123
-
-# Show template status
-insertr templates status --site-id mysite
-```
-
-### **3. Updated Content Processing Flow**
-
-#### **Enhancement Process:**
+#### **Content Storage on First Pass:**
```go
-func (e *ContentEngine) processElement(node *html.Node, siteID, contentID string, devMode bool) {
- existingContent := getContent(siteID, contentID)
- currentMarkup := extractElementHTML(node)
-
- if existingContent == nil {
- // First time: create with template
- htmlContent := extractContentHTML(node)
- createContent(siteID, contentID, htmlContent, currentMarkup, !devMode)
- } else if devMode && !existingContent.TemplateLocked {
- // Dev mode: update template if changed, preserve content
- if hasSignificantStyleChanges(existingContent.OriginalMarkup, currentMarkup) {
- updateTemplate(siteID, contentID, currentMarkup)
- }
+func processElement(node *html.Node, siteID string) {
+ // Check if already processed
+ existingID := getAttribute(node, "data-content-id")
+ if existingID != "" {
+ return // Skip - already processed
}
- // Always inject existing html_content
- injectHTMLContent(node, existingContent.HTMLContent)
+
+ // Extract content and template
+ contentID := generateContentID(node, filePath)
+ htmlContent := extractInnerHTML(node) // For editing/injection
+ originalTemplate := extractOuterHTML(node) // For style detection
+ contentType := determineContentType(node)
+
+ // Store in database
+ createContent(siteID, contentID, htmlContent, originalTemplate, contentType)
+
+ // Mark as processed
+ setAttribute(node, "data-content-id", contentID)
+ setAttribute(node, "data-content-type", contentType)
}
```
-#### **API Content Updates:**
+#### **Development Convenience:**
+- Optional DB cleanup flag: `insertr enhance --clean-db` for fresh development iterations
+- Allows developers to start with clean slate when refining site structure
+
+### **3. Injector Redesign (HTML-Only)**
+
+#### **Remove Markdown Processing:**
+- Delete `MarkdownProcessor` and all markdown-related logic
+- Direct HTML injection using existing `injectHTMLContent()` method
+- Simplified injection flow focused on HTML fidelity
+
+#### **Updated Injection Process:**
```go
-func (h *ContentHandler) CreateContent(req CreateContentRequest) {
- // API updates only affect html_content, never original_markup
- updateContent(req.SiteID, req.ID, req.HTMLContent)
- // Template preservation handled automatically
+func (i *Injector) InjectContent(element *Element, contentID string) error {
+ // Fetch content from database
+ contentItem, err := i.client.GetContent(i.siteID, contentID)
+ if err != nil || contentItem == nil {
+ // No content found - add attributes but keep original content
+ i.AddContentAttributes(element.Node, contentID, element.Type)
+ return nil
+ }
+
+ // Direct HTML injection - no markdown processing
+ i.injectHTMLContent(element.Node, contentItem.HTMLContent)
+ i.AddContentAttributes(element.Node, contentID, element.Type)
+
+ return nil
}
```
-### **4. Frontend Integration**
+#### **Content Type Handling:**
+- All content types use HTML storage and injection
+- Remove type-specific processing (text, markdown, link)
+- StyleAware editor handles rich editing based on element context
-#### **Style Detection Using Templates:**
-```javascript
-// Always use stored template for style detection
-async initializeEditor(element) {
- const response = await this.apiClient.getContent(element.dataset.contentId);
-
- // Use original_markup for consistent style detection
- const templateHTML = response.original_markup || element.outerHTML;
- this.styleEngine.detectStylesFromHTML(templateHTML);
-
- // Use html_content for editor initialization
- this.editor.setContent(response.html_content);
-}
-```
+### **4. StyleAware Editor Integration**
-#### **Updated API Response:**
+#### **API Response Format (Matches Editor Expectations):**
```json
{
"id": "hero-title-abc123",
+ "site_id": "mysite",
"html_content": "
Welcome to Our Company
",
- "original_markup": "Welcome to Our Company
",
- "template_locked": true,
- "type": "text"
+ "original_template": "Welcome to Our Company
",
+ "type": "text",
+ "created_at": "2024-01-15T10:30:00Z",
+ "updated_at": "2024-01-15T10:30:00Z",
+ "last_edited_by": "user@example.com"
+}
+```
+
+#### **Editor Integration:**
+- **Style Detection**: Uses `original_template` for consistent formatting options
+- **Content Editing**: Uses `html_content` for rich text editing
+- **Perfect Alignment**: Response format matches StyleAware editor analysis requirements
+- **Multi-Property Support**: Complex elements (links) work seamlessly with preserved templates
+
+#### **Updated API Models:**
+```go
+type ContentItem struct {
+ ID string `json:"id"`
+ SiteID string `json:"site_id"`
+ HTMLContent string `json:"html_content"` // For editor content
+ OriginalTemplate string `json:"original_template"` // For style detection
+ Type string `json:"type"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+ LastEditedBy string `json:"last_edited_by"`
}
```
## 🔄 Development Workflows
-### **Development Phase:**
+### **Enhanced Development Workflow:**
```bash
-# Start development (templates auto-update)
+# Start fresh development iteration
+insertr enhance ./mysite --clean-db --site-id mysite
+
+# Files are processed, content stored, elements marked as processed
+# Subsequent enhancement runs skip already processed elements
+# Developer can iterate on unprocessed parts without affecting existing content
+
+# Start development server
insertr serve --dev-mode
-# Developer changes files -> enhancement updates templates automatically
-# Editor changes content -> templates preserved, only content updated
-# Style detection uses current templates (reflecting latest dev intent)
-
-# Ready for handoff:
-insertr templates lock --site-id mysite
+# Editor changes -> only html_content updated, templates preserved
+# Style detection uses stored original_template for consistency
```
-### **Production Phase:**
+### **Production Workflow:**
```bash
-# Production mode (templates locked by default)
+# Production enhancement (no DB cleanup)
+insertr enhance ./mysite --site-id mysite
+
+# Production server
insertr serve
-# Client editing -> only html_content changes, templates preserved
-# Style detection always uses locked original_markup
-# Developer styles always available regardless of content changes
-
-# For style updates:
-insertr templates edit --site-id mysite --content-id specific-element
+# All editing preserves original developer templates
+# StyleAware editor gets consistent style detection from stored templates
+# Content updates only affect html_content field
```
## 🎯 Key Benefits
### **For Developers:**
-✅ **Rapid iteration** during development with automatic template updates
-✅ **Explicit control** over template locking and updates
-✅ **HTML-first approach** aligns with frontend capabilities
-✅ **Clean schema** without legacy compatibility concerns
+✅ **Efficient Processing**: Only process unprocessed elements, skip already handled ones
+✅ **Development Convenience**: Optional DB cleanup for fresh iterations
+✅ **HTML-first approach**: Direct alignment with StyleAware editor capabilities
+✅ **Zero Configuration**: Automatic detection and processing of viable elements
-### **For Clients:**
-✅ **Style preservation** - developer styles always available
-✅ **Rich editing** with full HTML capabilities
-✅ **Version history** includes both content and template context
-✅ **Design safety** - cannot accidentally break developer styling
+### **For Content Editors:**
+✅ **Style Preservation**: Developer styles always available via original_template
+✅ **Rich Editing**: Full HTML capabilities with formatting toolbar
+✅ **Perfect Fidelity**: No lossy conversions, complete attribute preservation
+✅ **Design Safety**: Cannot accidentally break developer styling constraints
-### **For System:**
-✅ **Unified processing** - same HTML used for injection and editing
-✅ **Clear separation** between content updates and template management
-✅ **Dev/prod integration** leverages existing mode detection
-✅ **Self-contained** templates preserved in database
+### **For System Architecture:**
+✅ **Simplified Flow**: No markdown conversion complexity
+✅ **Direct Injection**: HTML content injects directly into static files
+✅ **Clean Separation**: Enhancement stores content, API serves editing
+✅ **Performance**: Skip already-processed elements for faster builds
## 📋 Implementation Tasks
-### **Phase 3a Priority Tasks:**
+### **Week 1: Database Foundation**
+1. **Schema Updates**
+ - [ ] Update SQLite schema: replace `value` with `html_content`, add `original_template`
+ - [ ] Update PostgreSQL schema: replace `value` with `html_content`, add `original_template`
+ - [ ] Update `content.sql` queries to use new fields
+ - [ ] Regenerate SQLC models
-1. **Database Schema Update**
- - [ ] Update `content` table schema
- - [ ] Update `content_versions` table schema
- - [ ] Update SQLC queries and models
+2. **API Models**
+ - [ ] Update `ContentItem` struct to use `html_content` and `original_template`
+ - [ ] Update request/response structs for new field names
+ - [ ] Update API handlers to work with new field structure
-2. **API Model Updates**
- - [ ] Update `ContentItem` and `CreateContentRequest` structs
- - [ ] Add `html_content` and `original_markup` fields
- - [ ] Remove `value` field dependencies
+### **Week 2: Enhancer Logic**
+3. **First-Pass Processing**
+ - [ ] Update enhancer to detect processed elements via `data-content-id`
+ - [ ] Update enhancer to store `html_content` and `original_template` on first pass
+ - [ ] Add development DB cleanup option (`--clean-db` flag)
-3. **Enhancement Process Updates**
- - [ ] Update content injection to use `html_content` instead of `value`
- - [ ] Add template detection and storage logic
- - [ ] Implement dev/prod mode template handling
+### **Week 3: Injector Redesign**
+4. **HTML-Only Injection**
+ - [ ] Remove `MarkdownProcessor` and all markdown-related code from injector
+ - [ ] Update injector to use `html_content` directly via `injectHTMLContent()`
+ - [ ] Remove type-specific content processing (text, markdown, link)
-4. **Template Management Commands**
- - [ ] Add `insertr templates` command group
- - [ ] Implement `lock`, `edit`, `status` subcommands
- - [ ] Add template validation and editor integration
+### **Week 4: Integration Testing**
+5. **StyleAware Editor Compatibility**
+ - [ ] Test API responses work correctly with StyleAware editor
+ - [ ] Verify `original_template` enables proper style detection
+ - [ ] Test rich HTML editing and injection end-to-end
-5. **Frontend Integration**
- - [ ] Update API client to handle new response format
- - [ ] Modify style detection to use `original_markup`
- - [ ] Test rich HTML content editing and injection
+## 🚀 Implementation Strategy
-## 🔍 Next Steps
+### **Priority Order:**
+1. **Database Changes First**: Schema, queries, models - foundation for everything else
+2. **Enhancer Updates**: First-pass processing logic and content storage
+3. **Injector Simplification**: Remove markdown, use HTML directly
+4. **Integration Testing**: Verify StyleAware editor compatibility
-Tomorrow we will:
-1. **Begin database schema implementation**
-2. **Update SQLC queries and regenerate models**
-3. **Modify API handlers for new content structure**
-4. **Test the template lifecycle management**
+### **Key Implementation Notes:**
+- **No Migration Required**: Fresh schema replacement, no backward compatibility needed
+- **Enhancer-Driven**: Content storage happens during enhancement, not via API
+- **HTML-Only**: Eliminate all markdown processing complexity
+- **StyleAware Alignment**: API response format matches editor expectations exactly
-This represents a fundamental shift to **HTML-first content management** while maintaining the zero-configuration philosophy that makes Insertr unique.
+This represents a fundamental shift to **HTML-first content management** with enhanced developer workflow efficiency while maintaining the zero-configuration philosophy that makes Insertr unique.
---
-**Status**: Planning Complete, Ready for Implementation
-**Estimated Effort**: 1-2 days for core implementation
-**Breaking Changes**: Yes (fresh schema, no migration needed)
\ No newline at end of file
+**Status**: Ready for Implementation
+**Estimated Effort**: 1 week for core implementation
+**Breaking Changes**: Yes (fresh schema, enhancer workflow changes)
\ No newline at end of file
diff --git a/cmd/serve.go b/cmd/serve.go
index d1b0137..c789421 100644
--- a/cmd/serve.go
+++ b/cmd/serve.go
@@ -221,6 +221,7 @@ func runServe(cmd *cobra.Command, args []string) {
contentRouter.Get("/{id}", contentHandler.GetContent)
contentRouter.Get("/", contentHandler.GetAllContent)
contentRouter.Post("/", contentHandler.CreateContent)
+ contentRouter.Put("/{id}", contentHandler.UpdateContent)
// Version control endpoints
contentRouter.Get("/{id}/versions", contentHandler.GetContentVersions)
diff --git a/debug-style-detection.html b/debug-style-detection.html
new file mode 100644
index 0000000..5dd1b04
--- /dev/null
+++ b/debug-style-detection.html
@@ -0,0 +1,90 @@
+
+
+
+
+
+ Debug Style Detection
+
+
+
+ 🔍 Debug Style Detection
+ Testing what styles are detected for Example 2:
+
+
+ Visit our about page for more info.
+
+
+ Click the button to see debug output...
+
+
+
+
\ No newline at end of file
diff --git a/go.mod b/go.mod
index f0b22b1..b722750 100644
--- a/go.mod
+++ b/go.mod
@@ -7,11 +7,11 @@ require (
github.com/go-chi/chi/v5 v5.2.3
github.com/go-chi/cors v1.2.2
github.com/golang-jwt/jwt/v5 v5.3.0
+ github.com/google/uuid v1.6.0
github.com/lib/pq v1.10.9
github.com/mattn/go-sqlite3 v1.14.32
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.18.2
- github.com/yuin/goldmark v1.7.8
golang.org/x/net v0.43.0
golang.org/x/oauth2 v0.31.0
)
diff --git a/go.sum b/go.sum
index 8d9dbca..fc9136c 100644
--- a/go.sum
+++ b/go.sum
@@ -19,6 +19,10 @@ github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9v
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
+github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -70,8 +74,6 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
-github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
-github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
diff --git a/internal/api/handlers.go b/internal/api/handlers.go
index dd9877c..5789ecd 100644
--- a/internal/api/handlers.go
+++ b/internal/api/handlers.go
@@ -22,6 +22,21 @@ import (
"github.com/insertr/insertr/internal/engine"
)
+// Helper functions for sql.NullString conversion
+func toNullString(s string) sql.NullString {
+ if s == "" {
+ return sql.NullString{Valid: false}
+ }
+ return sql.NullString{String: s, Valid: true}
+}
+
+func fromNullString(ns sql.NullString) string {
+ if ns.Valid {
+ return ns.String
+ }
+ return ""
+}
+
// ContentHandler handles all content-related HTTP requests
type ContentHandler struct {
database *db.Database
@@ -314,19 +329,21 @@ func (h *ContentHandler) CreateContent(w http.ResponseWriter, r *http.Request) {
switch h.database.GetDBType() {
case "sqlite3":
content, err = h.database.GetSQLiteQueries().UpsertContent(context.Background(), sqlite.UpsertContentParams{
- ID: contentID,
- SiteID: siteID,
- Value: req.Value,
- Type: contentType,
- LastEditedBy: userID,
+ ID: contentID,
+ SiteID: siteID,
+ HtmlContent: req.HTMLContent,
+ OriginalTemplate: toNullString(req.OriginalTemplate),
+ Type: contentType,
+ LastEditedBy: userID,
})
case "postgresql":
content, err = h.database.GetPostgreSQLQueries().UpsertContent(context.Background(), postgresql.UpsertContentParams{
- ID: contentID,
- SiteID: siteID,
- Value: req.Value,
- Type: contentType,
- LastEditedBy: userID,
+ ID: contentID,
+ SiteID: siteID,
+ HtmlContent: req.HTMLContent,
+ OriginalTemplate: toNullString(req.OriginalTemplate),
+ Type: contentType,
+ LastEditedBy: userID,
})
default:
http.Error(w, "Unsupported database type", http.StatusInternalServerError)
@@ -363,6 +380,111 @@ func (h *ContentHandler) CreateContent(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(item)
}
+// UpdateContent handles PUT /api/content/{id}
+func (h *ContentHandler) UpdateContent(w http.ResponseWriter, r *http.Request) {
+ contentID := chi.URLParam(r, "id")
+ siteID := r.URL.Query().Get("site_id")
+
+ if siteID == "" {
+ http.Error(w, "site_id parameter is required", http.StatusBadRequest)
+ return
+ }
+
+ var req struct {
+ HTMLContent string `json:"html_content"`
+ }
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "Invalid JSON", http.StatusBadRequest)
+ return
+ }
+
+ // Extract user from request using authentication service
+ userInfo, authErr := h.authService.ExtractUserFromRequest(r)
+ if authErr != nil {
+ http.Error(w, fmt.Sprintf("Authentication error: %v", authErr), http.StatusUnauthorized)
+ return
+ }
+ userID := userInfo.ID
+
+ // Get existing content for version history
+ var existingContent interface{}
+ var err error
+
+ switch h.database.GetDBType() {
+ case "sqlite3":
+ existingContent, err = h.database.GetSQLiteQueries().GetContent(context.Background(), sqlite.GetContentParams{
+ ID: contentID,
+ SiteID: siteID,
+ })
+ case "postgresql":
+ existingContent, err = h.database.GetPostgreSQLQueries().GetContent(context.Background(), postgresql.GetContentParams{
+ ID: contentID,
+ SiteID: siteID,
+ })
+ default:
+ http.Error(w, "Unsupported database type", http.StatusInternalServerError)
+ return
+ }
+
+ if err != nil {
+ if err == sql.ErrNoRows {
+ http.Error(w, "Content not found", http.StatusNotFound)
+ return
+ }
+ http.Error(w, fmt.Sprintf("Database error: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Archive existing version before update
+ if err := h.createContentVersion(existingContent); err != nil {
+ fmt.Printf("Warning: Failed to create content version: %v\n", err)
+ }
+
+ // Update content
+ var updatedContent interface{}
+
+ switch h.database.GetDBType() {
+ case "sqlite3":
+ updatedContent, err = h.database.GetSQLiteQueries().UpdateContent(context.Background(), sqlite.UpdateContentParams{
+ HtmlContent: req.HTMLContent,
+ Type: h.getContentType(existingContent),
+ LastEditedBy: userID,
+ ID: contentID,
+ SiteID: siteID,
+ })
+ case "postgresql":
+ updatedContent, err = h.database.GetPostgreSQLQueries().UpdateContent(context.Background(), postgresql.UpdateContentParams{
+ HtmlContent: req.HTMLContent,
+ Type: h.getContentType(existingContent),
+ LastEditedBy: userID,
+ ID: contentID,
+ SiteID: siteID,
+ })
+ default:
+ http.Error(w, "Unsupported database type", http.StatusInternalServerError)
+ return
+ }
+
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Failed to update content: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ item := h.convertToAPIContent(updatedContent)
+
+ // Trigger file enhancement if site is registered for auto-enhancement
+ if h.siteManager != nil && h.siteManager.IsAutoEnhanceEnabled(siteID) {
+ go func() {
+ if err := h.siteManager.EnhanceSite(siteID); err != nil {
+ log.Printf("⚠️ Failed to enhance site %s: %v", siteID, err)
+ }
+ }()
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(item)
+}
+
// DeleteContent handles DELETE /api/content/{id}
func (h *ContentHandler) DeleteContent(w http.ResponseWriter, r *http.Request) {
contentID := chi.URLParam(r, "id")
@@ -541,7 +663,7 @@ func (h *ContentHandler) RollbackContent(w http.ResponseWriter, r *http.Request)
case "sqlite3":
sqliteVersion := targetVersion.(sqlite.ContentVersion)
updatedContent, err = h.database.GetSQLiteQueries().UpdateContent(context.Background(), sqlite.UpdateContentParams{
- Value: sqliteVersion.Value,
+ HtmlContent: sqliteVersion.HtmlContent,
Type: sqliteVersion.Type,
LastEditedBy: userID,
ID: contentID,
@@ -550,7 +672,7 @@ func (h *ContentHandler) RollbackContent(w http.ResponseWriter, r *http.Request)
case "postgresql":
pgVersion := targetVersion.(postgresql.ContentVersion)
updatedContent, err = h.database.GetPostgreSQLQueries().UpdateContent(context.Background(), postgresql.UpdateContentParams{
- Value: pgVersion.Value,
+ HtmlContent: pgVersion.HtmlContent,
Type: pgVersion.Type,
LastEditedBy: userID,
ID: contentID,
@@ -578,24 +700,26 @@ func (h *ContentHandler) convertToAPIContent(content interface{}) ContentItem {
case "sqlite3":
c := content.(sqlite.Content)
return ContentItem{
- ID: c.ID,
- SiteID: c.SiteID,
- Value: c.Value,
- Type: c.Type,
- CreatedAt: time.Unix(c.CreatedAt, 0),
- UpdatedAt: time.Unix(c.UpdatedAt, 0),
- LastEditedBy: c.LastEditedBy,
+ ID: c.ID,
+ SiteID: c.SiteID,
+ HTMLContent: c.HtmlContent,
+ OriginalTemplate: fromNullString(c.OriginalTemplate),
+ Type: c.Type,
+ CreatedAt: time.Unix(c.CreatedAt, 0),
+ UpdatedAt: time.Unix(c.UpdatedAt, 0),
+ LastEditedBy: c.LastEditedBy,
}
case "postgresql":
c := content.(postgresql.Content)
return ContentItem{
- ID: c.ID,
- SiteID: c.SiteID,
- Value: c.Value,
- Type: c.Type,
- CreatedAt: time.Unix(c.CreatedAt, 0),
- UpdatedAt: time.Unix(c.UpdatedAt, 0),
- LastEditedBy: c.LastEditedBy,
+ ID: c.ID,
+ SiteID: c.SiteID,
+ HTMLContent: c.HtmlContent,
+ OriginalTemplate: fromNullString(c.OriginalTemplate),
+ Type: c.Type,
+ CreatedAt: time.Unix(c.CreatedAt, 0),
+ UpdatedAt: time.Unix(c.UpdatedAt, 0),
+ LastEditedBy: c.LastEditedBy,
}
}
return ContentItem{} // Should never happen
@@ -628,13 +752,14 @@ func (h *ContentHandler) convertToAPIVersionList(versionList interface{}) []Cont
versions := make([]ContentVersion, len(list))
for i, version := range list {
versions[i] = ContentVersion{
- VersionID: version.VersionID,
- ContentID: version.ContentID,
- SiteID: version.SiteID,
- Value: version.Value,
- Type: version.Type,
- CreatedAt: time.Unix(version.CreatedAt, 0),
- CreatedBy: version.CreatedBy,
+ VersionID: version.VersionID,
+ ContentID: version.ContentID,
+ SiteID: version.SiteID,
+ HTMLContent: version.HtmlContent,
+ OriginalTemplate: fromNullString(version.OriginalTemplate),
+ Type: version.Type,
+ CreatedAt: time.Unix(version.CreatedAt, 0),
+ CreatedBy: version.CreatedBy,
}
}
return versions
@@ -643,13 +768,14 @@ func (h *ContentHandler) convertToAPIVersionList(versionList interface{}) []Cont
versions := make([]ContentVersion, len(list))
for i, version := range list {
versions[i] = ContentVersion{
- VersionID: int64(version.VersionID),
- ContentID: version.ContentID,
- SiteID: version.SiteID,
- Value: version.Value,
- Type: version.Type,
- CreatedAt: time.Unix(version.CreatedAt, 0),
- CreatedBy: version.CreatedBy,
+ VersionID: int64(version.VersionID),
+ ContentID: version.ContentID,
+ SiteID: version.SiteID,
+ HTMLContent: version.HtmlContent,
+ OriginalTemplate: fromNullString(version.OriginalTemplate),
+ Type: version.Type,
+ CreatedAt: time.Unix(version.CreatedAt, 0),
+ CreatedBy: version.CreatedBy,
}
}
return versions
@@ -662,20 +788,22 @@ func (h *ContentHandler) createContentVersion(content interface{}) error {
case "sqlite3":
c := content.(sqlite.Content)
return h.database.GetSQLiteQueries().CreateContentVersion(context.Background(), sqlite.CreateContentVersionParams{
- ContentID: c.ID,
- SiteID: c.SiteID,
- Value: c.Value,
- Type: c.Type,
- CreatedBy: c.LastEditedBy,
+ ContentID: c.ID,
+ SiteID: c.SiteID,
+ HtmlContent: c.HtmlContent,
+ OriginalTemplate: c.OriginalTemplate,
+ Type: c.Type,
+ CreatedBy: c.LastEditedBy,
})
case "postgresql":
c := content.(postgresql.Content)
return h.database.GetPostgreSQLQueries().CreateContentVersion(context.Background(), postgresql.CreateContentVersionParams{
- ContentID: c.ID,
- SiteID: c.SiteID,
- Value: c.Value,
- Type: c.Type,
- CreatedBy: c.LastEditedBy,
+ ContentID: c.ID,
+ SiteID: c.SiteID,
+ HtmlContent: c.HtmlContent,
+ OriginalTemplate: c.OriginalTemplate,
+ Type: c.Type,
+ CreatedBy: c.LastEditedBy,
})
}
return fmt.Errorf("unsupported database type")
diff --git a/internal/api/models.go b/internal/api/models.go
index a0ddf10..043a356 100644
--- a/internal/api/models.go
+++ b/internal/api/models.go
@@ -4,23 +4,25 @@ import "time"
// API request/response models
type ContentItem struct {
- ID string `json:"id"`
- SiteID string `json:"site_id"`
- Value string `json:"value"`
- Type string `json:"type"`
- CreatedAt time.Time `json:"created_at"`
- UpdatedAt time.Time `json:"updated_at"`
- LastEditedBy string `json:"last_edited_by"`
+ ID string `json:"id"`
+ SiteID string `json:"site_id"`
+ HTMLContent string `json:"html_content"`
+ OriginalTemplate string `json:"original_template"`
+ Type string `json:"type"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+ LastEditedBy string `json:"last_edited_by"`
}
type ContentVersion struct {
- VersionID int64 `json:"version_id"`
- ContentID string `json:"content_id"`
- SiteID string `json:"site_id"`
- Value string `json:"value"`
- Type string `json:"type"`
- CreatedAt time.Time `json:"created_at"`
- CreatedBy string `json:"created_by"`
+ VersionID int64 `json:"version_id"`
+ ContentID string `json:"content_id"`
+ SiteID string `json:"site_id"`
+ HTMLContent string `json:"html_content"`
+ OriginalTemplate string `json:"original_template"`
+ Type string `json:"type"`
+ CreatedAt time.Time `json:"created_at"`
+ CreatedBy string `json:"created_by"`
}
type ContentResponse struct {
@@ -42,12 +44,13 @@ type ElementContext struct {
// Request models
type CreateContentRequest struct {
- HTMLMarkup string `json:"html_markup"` // HTML markup of the element
- FilePath string `json:"file_path"` // File path for consistent ID generation
- Value string `json:"value"` // Content value
- Type string `json:"type"` // Content type
- SiteID string `json:"site_id,omitempty"` // Site identifier
- CreatedBy string `json:"created_by,omitempty"` // User who created the content
+ HTMLMarkup string `json:"html_markup"` // HTML markup of the element
+ FilePath string `json:"file_path"` // File path for consistent ID generation
+ HTMLContent string `json:"html_content"` // HTML content value
+ OriginalTemplate string `json:"original_template"` // Original template markup
+ Type string `json:"type"` // Content type
+ SiteID string `json:"site_id,omitempty"` // Site identifier
+ CreatedBy string `json:"created_by,omitempty"` // User who created the content
}
type RollbackContentRequest struct {
diff --git a/internal/content/client.go b/internal/content/client.go
index a44ab59..a4b48c5 100644
--- a/internal/content/client.go
+++ b/internal/content/client.go
@@ -164,3 +164,10 @@ func (c *HTTPClient) GetAllContent(siteID string) (map[string]engine.ContentItem
return result, nil
}
+
+// CreateContent creates a new content item via HTTP API
+func (c *HTTPClient) CreateContent(siteID, contentID, htmlContent, originalTemplate, contentType, lastEditedBy string) (*engine.ContentItem, error) {
+ // For now, HTTPClient CreateContent is not implemented for enhancer use
+ // This would typically be used in API-driven enhancement scenarios
+ return nil, fmt.Errorf("CreateContent not implemented for HTTPClient - use DatabaseClient for enhancement")
+}
diff --git a/internal/content/database.go b/internal/content/database.go
index 09b8c51..5015870 100644
--- a/internal/content/database.go
+++ b/internal/content/database.go
@@ -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}
+}
diff --git a/internal/content/mock.go b/internal/content/mock.go
index 2d45360..5fb0ebe 100644
--- a/internal/content/mock.go
+++ b/internal/content/mock.go
@@ -17,82 +17,82 @@ func NewMockClient() *MockClient {
data := map[string]engine.ContentItem{
// Navigation (index.html has collision suffix)
"navbar-logo-2b10ad": {
- ID: "navbar-logo-2b10ad",
- SiteID: "demo",
- Value: "Acme Consulting Solutions",
- Type: "text",
- UpdatedAt: time.Now().Format(time.RFC3339),
+ ID: "navbar-logo-2b10ad",
+ SiteID: "demo",
+ HTMLContent: "Acme Consulting Solutions",
+ Type: "text",
+ UpdatedAt: time.Now().Format(time.RFC3339),
},
"navbar-logo-2b10ad-a44bad": {
- ID: "navbar-logo-2b10ad-a44bad",
- SiteID: "demo",
- Value: "Acme Business Advisors",
- Type: "text",
- UpdatedAt: time.Now().Format(time.RFC3339),
+ ID: "navbar-logo-2b10ad-a44bad",
+ SiteID: "demo",
+ HTMLContent: "Acme Business Advisors",
+ Type: "text",
+ UpdatedAt: time.Now().Format(time.RFC3339),
},
// Hero Section - index.html (updated with actual IDs)
"hero-title-7cfeea": {
- ID: "hero-title-7cfeea",
- SiteID: "demo",
- Value: "Transform Your Business with Strategic Expertise",
- Type: "text",
- UpdatedAt: time.Now().Format(time.RFC3339),
+ ID: "hero-title-7cfeea",
+ SiteID: "demo",
+ HTMLContent: "Transform Your Business with Strategic Expertise",
+ Type: "text",
+ UpdatedAt: time.Now().Format(time.RFC3339),
},
"hero-lead-e47475": {
- ID: "hero-lead-e47475",
- SiteID: "demo",
- Value: "We help **ambitious businesses** grow through strategic planning, process optimization, and digital transformation. Our team brings 20+ years of experience to accelerate your success.",
- Type: "markdown",
- UpdatedAt: time.Now().Format(time.RFC3339),
+ ID: "hero-lead-e47475",
+ SiteID: "demo",
+ HTMLContent: "We help ambitious businesses grow through strategic planning, process optimization, and digital transformation. Our team brings 20+ years of experience to accelerate your success.",
+ Type: "text",
+ UpdatedAt: time.Now().Format(time.RFC3339),
},
"hero-link-76c620": {
- ID: "hero-link-76c620",
- SiteID: "demo",
- Value: "Schedule Free Consultation",
- Type: "link",
- UpdatedAt: time.Now().Format(time.RFC3339),
+ ID: "hero-link-76c620",
+ SiteID: "demo",
+ HTMLContent: "Schedule Free Consultation",
+ Type: "link",
+ UpdatedAt: time.Now().Format(time.RFC3339),
},
// Hero Section - about.html
"hero-title-c70343": {
- ID: "hero-title-c70343",
- SiteID: "demo",
- Value: "About Our Consulting Expertise",
- Type: "text",
- UpdatedAt: time.Now().Format(time.RFC3339),
+ ID: "hero-title-c70343",
+ SiteID: "demo",
+ HTMLContent: "About Our Consulting Expertise",
+ Type: "text",
+ UpdatedAt: time.Now().Format(time.RFC3339),
},
"hero-lead-673026": {
- ID: "hero-lead-673026",
- SiteID: "demo",
- Value: "We're a team of **experienced consultants** dedicated to helping small businesses thrive in today's competitive marketplace through proven strategies.",
- Type: "markdown",
- UpdatedAt: time.Now().Format(time.RFC3339),
+ ID: "hero-lead-673026",
+ SiteID: "demo",
+ HTMLContent: "We're a team of experienced consultants dedicated to helping small businesses thrive in today's competitive marketplace through proven strategies.",
+ Type: "text",
+ UpdatedAt: time.Now().Format(time.RFC3339),
},
// Services Section
"services-subtitle-c8927c": {
- ID: "services-subtitle-c8927c",
- SiteID: "demo",
- Value: "Our Story",
- Type: "text",
- UpdatedAt: time.Now().Format(time.RFC3339),
+ ID: "services-subtitle-c8927c",
+ SiteID: "demo",
+ HTMLContent: "Our Story",
+ Type: "text",
+ UpdatedAt: time.Now().Format(time.RFC3339),
},
"services-text-0d96da": {
- ID: "services-text-0d96da",
- SiteID: "demo",
- Value: "**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.",
- Type: "markdown",
- UpdatedAt: time.Now().Format(time.RFC3339),
+ ID: "services-text-0d96da",
+ SiteID: "demo",
+ HTMLContent: "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.",
+ Type: "text",
+ UpdatedAt: time.Now().Format(time.RFC3339),
},
// Default fallback for any missing content
"default": {
- ID: "default",
- SiteID: "demo",
- Value: "[Enhanced Content]",
- Type: "text",
- UpdatedAt: time.Now().Format(time.RFC3339),
+ ID: "default",
+ SiteID: "demo",
+ HTMLContent: "[Enhanced Content]",
+ Type: "text",
+ UpdatedAt: time.Now().Format(time.RFC3339),
},
}
@@ -138,3 +138,22 @@ func (m *MockClient) GetAllContent(siteID string) (map[string]engine.ContentItem
return result, nil
}
+
+// CreateContent creates a new mock content item
+func (m *MockClient) CreateContent(siteID, contentID, htmlContent, originalTemplate, contentType, lastEditedBy string) (*engine.ContentItem, error) {
+ // For mock client, just create and store the item
+ item := engine.ContentItem{
+ ID: contentID,
+ SiteID: siteID,
+ HTMLContent: htmlContent,
+ OriginalTemplate: originalTemplate,
+ Type: contentType,
+ UpdatedAt: time.Now().Format(time.RFC3339),
+ LastEditedBy: lastEditedBy,
+ }
+
+ // Store in mock data
+ m.data[contentID] = item
+
+ return &item, nil
+}
diff --git a/internal/db/postgresql/content.sql.go b/internal/db/postgresql/content.sql.go
index e307674..76e6e99 100644
--- a/internal/db/postgresql/content.sql.go
+++ b/internal/db/postgresql/content.sql.go
@@ -1,34 +1,37 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
-// sqlc v1.29.0
+// sqlc v1.30.0
// source: content.sql
package postgresql
import (
"context"
+ "database/sql"
"strings"
)
const createContent = `-- name: CreateContent :one
-INSERT INTO content (id, site_id, value, type, last_edited_by)
-VALUES ($1, $2, $3, $4, $5)
-RETURNING id, site_id, value, type, created_at, updated_at, last_edited_by
+INSERT INTO content (id, site_id, html_content, original_template, type, last_edited_by)
+VALUES ($1, $2, $3, $4, $5, $6)
+RETURNING id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by
`
type CreateContentParams struct {
- ID string `json:"id"`
- SiteID string `json:"site_id"`
- Value string `json:"value"`
- Type string `json:"type"`
- LastEditedBy string `json:"last_edited_by"`
+ ID string `json:"id"`
+ SiteID string `json:"site_id"`
+ HtmlContent string `json:"html_content"`
+ OriginalTemplate sql.NullString `json:"original_template"`
+ Type string `json:"type"`
+ LastEditedBy string `json:"last_edited_by"`
}
func (q *Queries) CreateContent(ctx context.Context, arg CreateContentParams) (Content, error) {
row := q.db.QueryRowContext(ctx, createContent,
arg.ID,
arg.SiteID,
- arg.Value,
+ arg.HtmlContent,
+ arg.OriginalTemplate,
arg.Type,
arg.LastEditedBy,
)
@@ -36,7 +39,8 @@ func (q *Queries) CreateContent(ctx context.Context, arg CreateContentParams) (C
err := row.Scan(
&i.ID,
&i.SiteID,
- &i.Value,
+ &i.HtmlContent,
+ &i.OriginalTemplate,
&i.Type,
&i.CreatedAt,
&i.UpdatedAt,
@@ -45,6 +49,16 @@ func (q *Queries) CreateContent(ctx context.Context, arg CreateContentParams) (C
return i, err
}
+const deleteAllSiteContent = `-- name: DeleteAllSiteContent :exec
+DELETE FROM content
+WHERE site_id = $1
+`
+
+func (q *Queries) DeleteAllSiteContent(ctx context.Context, siteID string) error {
+ _, err := q.db.ExecContext(ctx, deleteAllSiteContent, siteID)
+ return err
+}
+
const deleteContent = `-- name: DeleteContent :exec
DELETE FROM content
WHERE id = $1 AND site_id = $2
@@ -61,7 +75,7 @@ func (q *Queries) DeleteContent(ctx context.Context, arg DeleteContentParams) er
}
const getAllContent = `-- name: GetAllContent :many
-SELECT id, site_id, value, type, created_at, updated_at, last_edited_by
+SELECT id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by
FROM content
WHERE site_id = $1
ORDER BY updated_at DESC
@@ -79,7 +93,8 @@ func (q *Queries) GetAllContent(ctx context.Context, siteID string) ([]Content,
if err := rows.Scan(
&i.ID,
&i.SiteID,
- &i.Value,
+ &i.HtmlContent,
+ &i.OriginalTemplate,
&i.Type,
&i.CreatedAt,
&i.UpdatedAt,
@@ -99,7 +114,7 @@ func (q *Queries) GetAllContent(ctx context.Context, siteID string) ([]Content,
}
const getBulkContent = `-- name: GetBulkContent :many
-SELECT id, site_id, value, type, created_at, updated_at, last_edited_by
+SELECT id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by
FROM content
WHERE site_id = $1 AND id IN ($2)
`
@@ -132,7 +147,8 @@ func (q *Queries) GetBulkContent(ctx context.Context, arg GetBulkContentParams)
if err := rows.Scan(
&i.ID,
&i.SiteID,
- &i.Value,
+ &i.HtmlContent,
+ &i.OriginalTemplate,
&i.Type,
&i.CreatedAt,
&i.UpdatedAt,
@@ -152,7 +168,7 @@ func (q *Queries) GetBulkContent(ctx context.Context, arg GetBulkContentParams)
}
const getContent = `-- name: GetContent :one
-SELECT id, site_id, value, type, created_at, updated_at, last_edited_by
+SELECT id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by
FROM content
WHERE id = $1 AND site_id = $2
`
@@ -168,7 +184,8 @@ func (q *Queries) GetContent(ctx context.Context, arg GetContentParams) (Content
err := row.Scan(
&i.ID,
&i.SiteID,
- &i.Value,
+ &i.HtmlContent,
+ &i.OriginalTemplate,
&i.Type,
&i.CreatedAt,
&i.UpdatedAt,
@@ -179,13 +196,13 @@ func (q *Queries) GetContent(ctx context.Context, arg GetContentParams) (Content
const updateContent = `-- name: UpdateContent :one
UPDATE content
-SET value = $1, type = $2, last_edited_by = $3
+SET html_content = $1, type = $2, last_edited_by = $3
WHERE id = $4 AND site_id = $5
-RETURNING id, site_id, value, type, created_at, updated_at, last_edited_by
+RETURNING id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by
`
type UpdateContentParams struct {
- Value string `json:"value"`
+ HtmlContent string `json:"html_content"`
Type string `json:"type"`
LastEditedBy string `json:"last_edited_by"`
ID string `json:"id"`
@@ -194,7 +211,7 @@ type UpdateContentParams struct {
func (q *Queries) UpdateContent(ctx context.Context, arg UpdateContentParams) (Content, error) {
row := q.db.QueryRowContext(ctx, updateContent,
- arg.Value,
+ arg.HtmlContent,
arg.Type,
arg.LastEditedBy,
arg.ID,
@@ -204,7 +221,8 @@ func (q *Queries) UpdateContent(ctx context.Context, arg UpdateContentParams) (C
err := row.Scan(
&i.ID,
&i.SiteID,
- &i.Value,
+ &i.HtmlContent,
+ &i.OriginalTemplate,
&i.Type,
&i.CreatedAt,
&i.UpdatedAt,
@@ -214,28 +232,30 @@ func (q *Queries) UpdateContent(ctx context.Context, arg UpdateContentParams) (C
}
const upsertContent = `-- name: UpsertContent :one
-INSERT INTO content (id, site_id, value, type, last_edited_by)
-VALUES ($1, $2, $3, $4, $5)
+INSERT INTO content (id, site_id, html_content, original_template, type, last_edited_by)
+VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT(id, site_id) DO UPDATE SET
- value = EXCLUDED.value,
+ html_content = EXCLUDED.html_content,
type = EXCLUDED.type,
last_edited_by = EXCLUDED.last_edited_by
-RETURNING id, site_id, value, type, created_at, updated_at, last_edited_by
+RETURNING id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by
`
type UpsertContentParams struct {
- ID string `json:"id"`
- SiteID string `json:"site_id"`
- Value string `json:"value"`
- Type string `json:"type"`
- LastEditedBy string `json:"last_edited_by"`
+ ID string `json:"id"`
+ SiteID string `json:"site_id"`
+ HtmlContent string `json:"html_content"`
+ OriginalTemplate sql.NullString `json:"original_template"`
+ Type string `json:"type"`
+ LastEditedBy string `json:"last_edited_by"`
}
func (q *Queries) UpsertContent(ctx context.Context, arg UpsertContentParams) (Content, error) {
row := q.db.QueryRowContext(ctx, upsertContent,
arg.ID,
arg.SiteID,
- arg.Value,
+ arg.HtmlContent,
+ arg.OriginalTemplate,
arg.Type,
arg.LastEditedBy,
)
@@ -243,7 +263,8 @@ func (q *Queries) UpsertContent(ctx context.Context, arg UpsertContentParams) (C
err := row.Scan(
&i.ID,
&i.SiteID,
- &i.Value,
+ &i.HtmlContent,
+ &i.OriginalTemplate,
&i.Type,
&i.CreatedAt,
&i.UpdatedAt,
diff --git a/internal/db/postgresql/db.go b/internal/db/postgresql/db.go
index 9f77c9d..b9e992d 100644
--- a/internal/db/postgresql/db.go
+++ b/internal/db/postgresql/db.go
@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
-// sqlc v1.29.0
+// sqlc v1.30.0
package postgresql
diff --git a/internal/db/postgresql/models.go b/internal/db/postgresql/models.go
index 7a53776..61be412 100644
--- a/internal/db/postgresql/models.go
+++ b/internal/db/postgresql/models.go
@@ -1,25 +1,31 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
-// sqlc v1.29.0
+// sqlc v1.30.0
package postgresql
+import (
+ "database/sql"
+)
+
type Content struct {
- ID string `json:"id"`
- SiteID string `json:"site_id"`
- Value string `json:"value"`
- Type string `json:"type"`
- CreatedAt int64 `json:"created_at"`
- UpdatedAt int64 `json:"updated_at"`
- LastEditedBy string `json:"last_edited_by"`
+ ID string `json:"id"`
+ SiteID string `json:"site_id"`
+ HtmlContent string `json:"html_content"`
+ OriginalTemplate sql.NullString `json:"original_template"`
+ Type string `json:"type"`
+ CreatedAt int64 `json:"created_at"`
+ UpdatedAt int64 `json:"updated_at"`
+ LastEditedBy string `json:"last_edited_by"`
}
type ContentVersion struct {
- VersionID int32 `json:"version_id"`
- ContentID string `json:"content_id"`
- SiteID string `json:"site_id"`
- Value string `json:"value"`
- Type string `json:"type"`
- CreatedAt int64 `json:"created_at"`
- CreatedBy string `json:"created_by"`
+ VersionID int32 `json:"version_id"`
+ ContentID string `json:"content_id"`
+ SiteID string `json:"site_id"`
+ HtmlContent string `json:"html_content"`
+ OriginalTemplate sql.NullString `json:"original_template"`
+ Type string `json:"type"`
+ CreatedAt int64 `json:"created_at"`
+ CreatedBy string `json:"created_by"`
}
diff --git a/internal/db/postgresql/querier.go b/internal/db/postgresql/querier.go
index ff09adc..4114516 100644
--- a/internal/db/postgresql/querier.go
+++ b/internal/db/postgresql/querier.go
@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
-// sqlc v1.29.0
+// sqlc v1.30.0
package postgresql
@@ -15,6 +15,7 @@ type Querier interface {
CreateContentVersion(ctx context.Context, arg CreateContentVersionParams) error
CreateUpdateFunction(ctx context.Context) error
CreateVersionsLookupIndex(ctx context.Context) error
+ DeleteAllSiteContent(ctx context.Context, siteID string) error
DeleteContent(ctx context.Context, arg DeleteContentParams) error
DeleteOldVersions(ctx context.Context, arg DeleteOldVersionsParams) error
GetAllContent(ctx context.Context, siteID string) ([]Content, error)
diff --git a/internal/db/postgresql/schema.sql b/internal/db/postgresql/schema.sql
index 503c392..0e0b4ea 100644
--- a/internal/db/postgresql/schema.sql
+++ b/internal/db/postgresql/schema.sql
@@ -3,7 +3,8 @@
CREATE TABLE content (
id TEXT NOT NULL,
site_id TEXT NOT NULL,
- value TEXT NOT NULL,
+ html_content TEXT NOT NULL,
+ original_template TEXT,
type TEXT NOT NULL,
created_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL,
updated_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL,
@@ -16,7 +17,8 @@ CREATE TABLE content_versions (
version_id SERIAL PRIMARY KEY,
content_id TEXT NOT NULL,
site_id TEXT NOT NULL,
- value TEXT NOT NULL,
+ html_content TEXT NOT NULL,
+ original_template TEXT,
type TEXT NOT NULL,
created_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL,
created_by TEXT DEFAULT 'system' NOT NULL
diff --git a/internal/db/postgresql/setup.sql b/internal/db/postgresql/setup.sql
index e9dcd2f..4d8ef51 100644
--- a/internal/db/postgresql/setup.sql
+++ b/internal/db/postgresql/setup.sql
@@ -2,8 +2,9 @@
CREATE TABLE IF NOT EXISTS content (
id TEXT NOT NULL,
site_id TEXT NOT NULL,
- value TEXT NOT NULL,
- type TEXT NOT NULL CHECK (type IN ('text', 'markdown', 'link')),
+ html_content TEXT NOT NULL,
+ original_template TEXT,
+ type TEXT NOT NULL CHECK (type IN ('text', 'link')),
created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
updated_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
last_edited_by TEXT DEFAULT 'system' NOT NULL,
@@ -15,7 +16,8 @@ CREATE TABLE IF NOT EXISTS content_versions (
version_id SERIAL PRIMARY KEY,
content_id TEXT NOT NULL,
site_id TEXT NOT NULL,
- value TEXT NOT NULL,
+ html_content TEXT NOT NULL,
+ original_template TEXT,
type TEXT NOT NULL,
created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
created_by TEXT DEFAULT 'system' NOT NULL
diff --git a/internal/db/postgresql/setup.sql.go b/internal/db/postgresql/setup.sql.go
index 030a0e0..0a100e4 100644
--- a/internal/db/postgresql/setup.sql.go
+++ b/internal/db/postgresql/setup.sql.go
@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
-// sqlc v1.29.0
+// sqlc v1.30.0
// source: setup.sql
package postgresql
@@ -55,8 +55,9 @@ const initializeSchema = `-- name: InitializeSchema :exec
CREATE TABLE IF NOT EXISTS content (
id TEXT NOT NULL,
site_id TEXT NOT NULL,
- value TEXT NOT NULL,
- type TEXT NOT NULL CHECK (type IN ('text', 'markdown', 'link')),
+ html_content TEXT NOT NULL,
+ original_template TEXT,
+ type TEXT NOT NULL CHECK (type IN ('text', 'link')),
created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
updated_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
last_edited_by TEXT DEFAULT 'system' NOT NULL,
@@ -74,7 +75,8 @@ CREATE TABLE IF NOT EXISTS content_versions (
version_id SERIAL PRIMARY KEY,
content_id TEXT NOT NULL,
site_id TEXT NOT NULL,
- value TEXT NOT NULL,
+ html_content TEXT NOT NULL,
+ original_template TEXT,
type TEXT NOT NULL,
created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
created_by TEXT DEFAULT 'system' NOT NULL
diff --git a/internal/db/postgresql/versions.sql.go b/internal/db/postgresql/versions.sql.go
index 00bd5d3..9d9939e 100644
--- a/internal/db/postgresql/versions.sql.go
+++ b/internal/db/postgresql/versions.sql.go
@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
-// sqlc v1.29.0
+// sqlc v1.30.0
// source: versions.sql
package postgresql
@@ -11,23 +11,25 @@ import (
)
const createContentVersion = `-- name: CreateContentVersion :exec
-INSERT INTO content_versions (content_id, site_id, value, type, created_by)
-VALUES ($1, $2, $3, $4, $5)
+INSERT INTO content_versions (content_id, site_id, html_content, original_template, type, created_by)
+VALUES ($1, $2, $3, $4, $5, $6)
`
type CreateContentVersionParams struct {
- ContentID string `json:"content_id"`
- SiteID string `json:"site_id"`
- Value string `json:"value"`
- Type string `json:"type"`
- CreatedBy string `json:"created_by"`
+ ContentID string `json:"content_id"`
+ SiteID string `json:"site_id"`
+ HtmlContent string `json:"html_content"`
+ OriginalTemplate sql.NullString `json:"original_template"`
+ Type string `json:"type"`
+ CreatedBy string `json:"created_by"`
}
func (q *Queries) CreateContentVersion(ctx context.Context, arg CreateContentVersionParams) error {
_, err := q.db.ExecContext(ctx, createContentVersion,
arg.ContentID,
arg.SiteID,
- arg.Value,
+ arg.HtmlContent,
+ arg.OriginalTemplate,
arg.Type,
arg.CreatedBy,
)
@@ -51,8 +53,8 @@ func (q *Queries) DeleteOldVersions(ctx context.Context, arg DeleteOldVersionsPa
const getAllVersionsForSite = `-- name: GetAllVersionsForSite :many
SELECT
- cv.version_id, cv.content_id, cv.site_id, cv.value, cv.type, cv.created_at, cv.created_by,
- c.value as current_value
+ cv.version_id, cv.content_id, cv.site_id, cv.html_content, cv.original_template, cv.type, cv.created_at, cv.created_by,
+ c.html_content as current_html_content
FROM content_versions cv
LEFT JOIN content c ON cv.content_id = c.id AND cv.site_id = c.site_id
WHERE cv.site_id = $1
@@ -66,14 +68,15 @@ type GetAllVersionsForSiteParams struct {
}
type GetAllVersionsForSiteRow struct {
- VersionID int32 `json:"version_id"`
- ContentID string `json:"content_id"`
- SiteID string `json:"site_id"`
- Value string `json:"value"`
- Type string `json:"type"`
- CreatedAt int64 `json:"created_at"`
- CreatedBy string `json:"created_by"`
- CurrentValue sql.NullString `json:"current_value"`
+ VersionID int32 `json:"version_id"`
+ ContentID string `json:"content_id"`
+ SiteID string `json:"site_id"`
+ HtmlContent string `json:"html_content"`
+ OriginalTemplate sql.NullString `json:"original_template"`
+ Type string `json:"type"`
+ CreatedAt int64 `json:"created_at"`
+ CreatedBy string `json:"created_by"`
+ CurrentHtmlContent sql.NullString `json:"current_html_content"`
}
func (q *Queries) GetAllVersionsForSite(ctx context.Context, arg GetAllVersionsForSiteParams) ([]GetAllVersionsForSiteRow, error) {
@@ -89,11 +92,12 @@ func (q *Queries) GetAllVersionsForSite(ctx context.Context, arg GetAllVersionsF
&i.VersionID,
&i.ContentID,
&i.SiteID,
- &i.Value,
+ &i.HtmlContent,
+ &i.OriginalTemplate,
&i.Type,
&i.CreatedAt,
&i.CreatedBy,
- &i.CurrentValue,
+ &i.CurrentHtmlContent,
); err != nil {
return nil, err
}
@@ -109,7 +113,7 @@ func (q *Queries) GetAllVersionsForSite(ctx context.Context, arg GetAllVersionsF
}
const getContentVersion = `-- name: GetContentVersion :one
-SELECT version_id, content_id, site_id, value, type, created_at, created_by
+SELECT version_id, content_id, site_id, html_content, original_template, type, created_at, created_by
FROM content_versions
WHERE version_id = $1
`
@@ -121,7 +125,8 @@ func (q *Queries) GetContentVersion(ctx context.Context, versionID int32) (Conte
&i.VersionID,
&i.ContentID,
&i.SiteID,
- &i.Value,
+ &i.HtmlContent,
+ &i.OriginalTemplate,
&i.Type,
&i.CreatedAt,
&i.CreatedBy,
@@ -130,7 +135,7 @@ func (q *Queries) GetContentVersion(ctx context.Context, versionID int32) (Conte
}
const getContentVersionHistory = `-- name: GetContentVersionHistory :many
-SELECT version_id, content_id, site_id, value, type, created_at, created_by
+SELECT version_id, content_id, site_id, html_content, original_template, type, created_at, created_by
FROM content_versions
WHERE content_id = $1 AND site_id = $2
ORDER BY created_at DESC
@@ -156,7 +161,8 @@ func (q *Queries) GetContentVersionHistory(ctx context.Context, arg GetContentVe
&i.VersionID,
&i.ContentID,
&i.SiteID,
- &i.Value,
+ &i.HtmlContent,
+ &i.OriginalTemplate,
&i.Type,
&i.CreatedAt,
&i.CreatedBy,
diff --git a/internal/db/queries/content.sql b/internal/db/queries/content.sql
index 98a7dbf..f25a98e 100644
--- a/internal/db/queries/content.sql
+++ b/internal/db/queries/content.sql
@@ -1,39 +1,43 @@
-- name: GetContent :one
-SELECT id, site_id, value, type, created_at, updated_at, last_edited_by
+SELECT id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by
FROM content
WHERE id = sqlc.arg(id) AND site_id = sqlc.arg(site_id);
-- name: GetAllContent :many
-SELECT id, site_id, value, type, created_at, updated_at, last_edited_by
+SELECT id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by
FROM content
WHERE site_id = sqlc.arg(site_id)
ORDER BY updated_at DESC;
-- name: GetBulkContent :many
-SELECT id, site_id, value, type, created_at, updated_at, last_edited_by
+SELECT id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by
FROM content
WHERE site_id = sqlc.arg(site_id) AND id IN (sqlc.slice('ids'));
-- name: CreateContent :one
-INSERT INTO content (id, site_id, value, type, last_edited_by)
-VALUES (sqlc.arg(id), sqlc.arg(site_id), sqlc.arg(value), sqlc.arg(type), sqlc.arg(last_edited_by))
-RETURNING id, site_id, value, type, created_at, updated_at, last_edited_by;
+INSERT INTO content (id, site_id, html_content, original_template, type, last_edited_by)
+VALUES (sqlc.arg(id), sqlc.arg(site_id), sqlc.arg(html_content), sqlc.arg(original_template), sqlc.arg(type), sqlc.arg(last_edited_by))
+RETURNING id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by;
-- name: UpdateContent :one
UPDATE content
-SET value = sqlc.arg(value), type = sqlc.arg(type), last_edited_by = sqlc.arg(last_edited_by)
+SET html_content = sqlc.arg(html_content), type = sqlc.arg(type), last_edited_by = sqlc.arg(last_edited_by)
WHERE id = sqlc.arg(id) AND site_id = sqlc.arg(site_id)
-RETURNING id, site_id, value, type, created_at, updated_at, last_edited_by;
+RETURNING id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by;
-- name: UpsertContent :one
-INSERT INTO content (id, site_id, value, type, last_edited_by)
-VALUES (sqlc.arg(id), sqlc.arg(site_id), sqlc.arg(value), sqlc.arg(type), sqlc.arg(last_edited_by))
+INSERT INTO content (id, site_id, html_content, original_template, type, last_edited_by)
+VALUES (sqlc.arg(id), sqlc.arg(site_id), sqlc.arg(html_content), sqlc.arg(original_template), sqlc.arg(type), sqlc.arg(last_edited_by))
ON CONFLICT(id, site_id) DO UPDATE SET
- value = EXCLUDED.value,
+ html_content = EXCLUDED.html_content,
type = EXCLUDED.type,
last_edited_by = EXCLUDED.last_edited_by
-RETURNING id, site_id, value, type, created_at, updated_at, last_edited_by;
+RETURNING id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by;
-- name: DeleteContent :exec
DELETE FROM content
-WHERE id = sqlc.arg(id) AND site_id = sqlc.arg(site_id);
\ No newline at end of file
+WHERE id = sqlc.arg(id) AND site_id = sqlc.arg(site_id);
+
+-- name: DeleteAllSiteContent :exec
+DELETE FROM content
+WHERE site_id = sqlc.arg(site_id);
\ No newline at end of file
diff --git a/internal/db/queries/versions.sql b/internal/db/queries/versions.sql
index 1339907..2d756ba 100644
--- a/internal/db/queries/versions.sql
+++ b/internal/db/queries/versions.sql
@@ -1,23 +1,23 @@
-- name: CreateContentVersion :exec
-INSERT INTO content_versions (content_id, site_id, value, type, created_by)
-VALUES (sqlc.arg(content_id), sqlc.arg(site_id), sqlc.arg(value), sqlc.arg(type), sqlc.arg(created_by));
+INSERT INTO content_versions (content_id, site_id, html_content, original_template, type, created_by)
+VALUES (sqlc.arg(content_id), sqlc.arg(site_id), sqlc.arg(html_content), sqlc.arg(original_template), sqlc.arg(type), sqlc.arg(created_by));
-- name: GetContentVersionHistory :many
-SELECT version_id, content_id, site_id, value, type, created_at, created_by
+SELECT version_id, content_id, site_id, html_content, original_template, type, created_at, created_by
FROM content_versions
WHERE content_id = sqlc.arg(content_id) AND site_id = sqlc.arg(site_id)
ORDER BY created_at DESC
LIMIT sqlc.arg(limit_count);
-- name: GetContentVersion :one
-SELECT version_id, content_id, site_id, value, type, created_at, created_by
+SELECT version_id, content_id, site_id, html_content, original_template, type, created_at, created_by
FROM content_versions
WHERE version_id = sqlc.arg(version_id);
-- name: GetAllVersionsForSite :many
SELECT
- cv.version_id, cv.content_id, cv.site_id, cv.value, cv.type, cv.created_at, cv.created_by,
- c.value as current_value
+ cv.version_id, cv.content_id, cv.site_id, cv.html_content, cv.original_template, cv.type, cv.created_at, cv.created_by,
+ c.html_content as current_html_content
FROM content_versions cv
LEFT JOIN content c ON cv.content_id = c.id AND cv.site_id = c.site_id
WHERE cv.site_id = sqlc.arg(site_id)
diff --git a/internal/db/sqlite/content.sql.go b/internal/db/sqlite/content.sql.go
index 20f5261..95f2c4b 100644
--- a/internal/db/sqlite/content.sql.go
+++ b/internal/db/sqlite/content.sql.go
@@ -1,34 +1,37 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
-// sqlc v1.29.0
+// sqlc v1.30.0
// source: content.sql
package sqlite
import (
"context"
+ "database/sql"
"strings"
)
const createContent = `-- name: CreateContent :one
-INSERT INTO content (id, site_id, value, type, last_edited_by)
-VALUES (?1, ?2, ?3, ?4, ?5)
-RETURNING id, site_id, value, type, created_at, updated_at, last_edited_by
+INSERT INTO content (id, site_id, html_content, original_template, type, last_edited_by)
+VALUES (?1, ?2, ?3, ?4, ?5, ?6)
+RETURNING id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by
`
type CreateContentParams struct {
- ID string `json:"id"`
- SiteID string `json:"site_id"`
- Value string `json:"value"`
- Type string `json:"type"`
- LastEditedBy string `json:"last_edited_by"`
+ ID string `json:"id"`
+ SiteID string `json:"site_id"`
+ HtmlContent string `json:"html_content"`
+ OriginalTemplate sql.NullString `json:"original_template"`
+ Type string `json:"type"`
+ LastEditedBy string `json:"last_edited_by"`
}
func (q *Queries) CreateContent(ctx context.Context, arg CreateContentParams) (Content, error) {
row := q.db.QueryRowContext(ctx, createContent,
arg.ID,
arg.SiteID,
- arg.Value,
+ arg.HtmlContent,
+ arg.OriginalTemplate,
arg.Type,
arg.LastEditedBy,
)
@@ -36,7 +39,8 @@ func (q *Queries) CreateContent(ctx context.Context, arg CreateContentParams) (C
err := row.Scan(
&i.ID,
&i.SiteID,
- &i.Value,
+ &i.HtmlContent,
+ &i.OriginalTemplate,
&i.Type,
&i.CreatedAt,
&i.UpdatedAt,
@@ -45,6 +49,16 @@ func (q *Queries) CreateContent(ctx context.Context, arg CreateContentParams) (C
return i, err
}
+const deleteAllSiteContent = `-- name: DeleteAllSiteContent :exec
+DELETE FROM content
+WHERE site_id = ?1
+`
+
+func (q *Queries) DeleteAllSiteContent(ctx context.Context, siteID string) error {
+ _, err := q.db.ExecContext(ctx, deleteAllSiteContent, siteID)
+ return err
+}
+
const deleteContent = `-- name: DeleteContent :exec
DELETE FROM content
WHERE id = ?1 AND site_id = ?2
@@ -61,7 +75,7 @@ func (q *Queries) DeleteContent(ctx context.Context, arg DeleteContentParams) er
}
const getAllContent = `-- name: GetAllContent :many
-SELECT id, site_id, value, type, created_at, updated_at, last_edited_by
+SELECT id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by
FROM content
WHERE site_id = ?1
ORDER BY updated_at DESC
@@ -79,7 +93,8 @@ func (q *Queries) GetAllContent(ctx context.Context, siteID string) ([]Content,
if err := rows.Scan(
&i.ID,
&i.SiteID,
- &i.Value,
+ &i.HtmlContent,
+ &i.OriginalTemplate,
&i.Type,
&i.CreatedAt,
&i.UpdatedAt,
@@ -99,7 +114,7 @@ func (q *Queries) GetAllContent(ctx context.Context, siteID string) ([]Content,
}
const getBulkContent = `-- name: GetBulkContent :many
-SELECT id, site_id, value, type, created_at, updated_at, last_edited_by
+SELECT id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by
FROM content
WHERE site_id = ?1 AND id IN (/*SLICE:ids*/?)
`
@@ -132,7 +147,8 @@ func (q *Queries) GetBulkContent(ctx context.Context, arg GetBulkContentParams)
if err := rows.Scan(
&i.ID,
&i.SiteID,
- &i.Value,
+ &i.HtmlContent,
+ &i.OriginalTemplate,
&i.Type,
&i.CreatedAt,
&i.UpdatedAt,
@@ -152,7 +168,7 @@ func (q *Queries) GetBulkContent(ctx context.Context, arg GetBulkContentParams)
}
const getContent = `-- name: GetContent :one
-SELECT id, site_id, value, type, created_at, updated_at, last_edited_by
+SELECT id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by
FROM content
WHERE id = ?1 AND site_id = ?2
`
@@ -168,7 +184,8 @@ func (q *Queries) GetContent(ctx context.Context, arg GetContentParams) (Content
err := row.Scan(
&i.ID,
&i.SiteID,
- &i.Value,
+ &i.HtmlContent,
+ &i.OriginalTemplate,
&i.Type,
&i.CreatedAt,
&i.UpdatedAt,
@@ -179,13 +196,13 @@ func (q *Queries) GetContent(ctx context.Context, arg GetContentParams) (Content
const updateContent = `-- name: UpdateContent :one
UPDATE content
-SET value = ?1, type = ?2, last_edited_by = ?3
+SET html_content = ?1, type = ?2, last_edited_by = ?3
WHERE id = ?4 AND site_id = ?5
-RETURNING id, site_id, value, type, created_at, updated_at, last_edited_by
+RETURNING id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by
`
type UpdateContentParams struct {
- Value string `json:"value"`
+ HtmlContent string `json:"html_content"`
Type string `json:"type"`
LastEditedBy string `json:"last_edited_by"`
ID string `json:"id"`
@@ -194,7 +211,7 @@ type UpdateContentParams struct {
func (q *Queries) UpdateContent(ctx context.Context, arg UpdateContentParams) (Content, error) {
row := q.db.QueryRowContext(ctx, updateContent,
- arg.Value,
+ arg.HtmlContent,
arg.Type,
arg.LastEditedBy,
arg.ID,
@@ -204,7 +221,8 @@ func (q *Queries) UpdateContent(ctx context.Context, arg UpdateContentParams) (C
err := row.Scan(
&i.ID,
&i.SiteID,
- &i.Value,
+ &i.HtmlContent,
+ &i.OriginalTemplate,
&i.Type,
&i.CreatedAt,
&i.UpdatedAt,
@@ -214,28 +232,30 @@ func (q *Queries) UpdateContent(ctx context.Context, arg UpdateContentParams) (C
}
const upsertContent = `-- name: UpsertContent :one
-INSERT INTO content (id, site_id, value, type, last_edited_by)
-VALUES (?1, ?2, ?3, ?4, ?5)
+INSERT INTO content (id, site_id, html_content, original_template, type, last_edited_by)
+VALUES (?1, ?2, ?3, ?4, ?5, ?6)
ON CONFLICT(id, site_id) DO UPDATE SET
- value = EXCLUDED.value,
+ html_content = EXCLUDED.html_content,
type = EXCLUDED.type,
last_edited_by = EXCLUDED.last_edited_by
-RETURNING id, site_id, value, type, created_at, updated_at, last_edited_by
+RETURNING id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by
`
type UpsertContentParams struct {
- ID string `json:"id"`
- SiteID string `json:"site_id"`
- Value string `json:"value"`
- Type string `json:"type"`
- LastEditedBy string `json:"last_edited_by"`
+ ID string `json:"id"`
+ SiteID string `json:"site_id"`
+ HtmlContent string `json:"html_content"`
+ OriginalTemplate sql.NullString `json:"original_template"`
+ Type string `json:"type"`
+ LastEditedBy string `json:"last_edited_by"`
}
func (q *Queries) UpsertContent(ctx context.Context, arg UpsertContentParams) (Content, error) {
row := q.db.QueryRowContext(ctx, upsertContent,
arg.ID,
arg.SiteID,
- arg.Value,
+ arg.HtmlContent,
+ arg.OriginalTemplate,
arg.Type,
arg.LastEditedBy,
)
@@ -243,7 +263,8 @@ func (q *Queries) UpsertContent(ctx context.Context, arg UpsertContentParams) (C
err := row.Scan(
&i.ID,
&i.SiteID,
- &i.Value,
+ &i.HtmlContent,
+ &i.OriginalTemplate,
&i.Type,
&i.CreatedAt,
&i.UpdatedAt,
diff --git a/internal/db/sqlite/db.go b/internal/db/sqlite/db.go
index 5841324..3c39218 100644
--- a/internal/db/sqlite/db.go
+++ b/internal/db/sqlite/db.go
@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
-// sqlc v1.29.0
+// sqlc v1.30.0
package sqlite
diff --git a/internal/db/sqlite/models.go b/internal/db/sqlite/models.go
index d8e7a1c..48abaf0 100644
--- a/internal/db/sqlite/models.go
+++ b/internal/db/sqlite/models.go
@@ -1,25 +1,31 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
-// sqlc v1.29.0
+// sqlc v1.30.0
package sqlite
+import (
+ "database/sql"
+)
+
type Content struct {
- ID string `json:"id"`
- SiteID string `json:"site_id"`
- Value string `json:"value"`
- Type string `json:"type"`
- CreatedAt int64 `json:"created_at"`
- UpdatedAt int64 `json:"updated_at"`
- LastEditedBy string `json:"last_edited_by"`
+ ID string `json:"id"`
+ SiteID string `json:"site_id"`
+ HtmlContent string `json:"html_content"`
+ OriginalTemplate sql.NullString `json:"original_template"`
+ Type string `json:"type"`
+ CreatedAt int64 `json:"created_at"`
+ UpdatedAt int64 `json:"updated_at"`
+ LastEditedBy string `json:"last_edited_by"`
}
type ContentVersion struct {
- VersionID int64 `json:"version_id"`
- ContentID string `json:"content_id"`
- SiteID string `json:"site_id"`
- Value string `json:"value"`
- Type string `json:"type"`
- CreatedAt int64 `json:"created_at"`
- CreatedBy string `json:"created_by"`
+ VersionID int64 `json:"version_id"`
+ ContentID string `json:"content_id"`
+ SiteID string `json:"site_id"`
+ HtmlContent string `json:"html_content"`
+ OriginalTemplate sql.NullString `json:"original_template"`
+ Type string `json:"type"`
+ CreatedAt int64 `json:"created_at"`
+ CreatedBy string `json:"created_by"`
}
diff --git a/internal/db/sqlite/querier.go b/internal/db/sqlite/querier.go
index c246168..e52f069 100644
--- a/internal/db/sqlite/querier.go
+++ b/internal/db/sqlite/querier.go
@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
-// sqlc v1.29.0
+// sqlc v1.30.0
package sqlite
@@ -11,6 +11,7 @@ import (
type Querier interface {
CreateContent(ctx context.Context, arg CreateContentParams) (Content, error)
CreateContentVersion(ctx context.Context, arg CreateContentVersionParams) error
+ DeleteAllSiteContent(ctx context.Context, siteID string) error
DeleteContent(ctx context.Context, arg DeleteContentParams) error
DeleteOldVersions(ctx context.Context, arg DeleteOldVersionsParams) error
GetAllContent(ctx context.Context, siteID string) ([]Content, error)
diff --git a/internal/db/sqlite/schema.sql b/internal/db/sqlite/schema.sql
index 722ca2f..0e545ea 100644
--- a/internal/db/sqlite/schema.sql
+++ b/internal/db/sqlite/schema.sql
@@ -3,7 +3,8 @@
CREATE TABLE content (
id TEXT NOT NULL,
site_id TEXT NOT NULL,
- value TEXT NOT NULL,
+ html_content TEXT NOT NULL,
+ original_template TEXT,
type TEXT NOT NULL,
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
updated_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
@@ -16,7 +17,8 @@ CREATE TABLE content_versions (
version_id INTEGER PRIMARY KEY AUTOINCREMENT,
content_id TEXT NOT NULL,
site_id TEXT NOT NULL,
- value TEXT NOT NULL,
+ html_content TEXT NOT NULL,
+ original_template TEXT,
type TEXT NOT NULL,
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
created_by TEXT DEFAULT 'system' NOT NULL
diff --git a/internal/db/sqlite/setup.sql b/internal/db/sqlite/setup.sql
index bfe8fcd..c31c9eb 100644
--- a/internal/db/sqlite/setup.sql
+++ b/internal/db/sqlite/setup.sql
@@ -2,8 +2,9 @@
CREATE TABLE IF NOT EXISTS content (
id TEXT NOT NULL,
site_id TEXT NOT NULL,
- value TEXT NOT NULL,
- type TEXT NOT NULL CHECK (type IN ('text', 'markdown', 'link')),
+ html_content TEXT NOT NULL,
+ original_template TEXT,
+ type TEXT NOT NULL CHECK (type IN ('text', 'link')),
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
updated_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
last_edited_by TEXT DEFAULT 'system' NOT NULL,
@@ -15,7 +16,8 @@ CREATE TABLE IF NOT EXISTS content_versions (
version_id INTEGER PRIMARY KEY AUTOINCREMENT,
content_id TEXT NOT NULL,
site_id TEXT NOT NULL,
- value TEXT NOT NULL,
+ html_content TEXT NOT NULL,
+ original_template TEXT,
type TEXT NOT NULL,
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
created_by TEXT DEFAULT 'system' NOT NULL
diff --git a/internal/db/sqlite/setup.sql.go b/internal/db/sqlite/setup.sql.go
index 800ef7e..7d9c107 100644
--- a/internal/db/sqlite/setup.sql.go
+++ b/internal/db/sqlite/setup.sql.go
@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
-// sqlc v1.29.0
+// sqlc v1.30.0
// source: setup.sql
package sqlite
@@ -13,8 +13,9 @@ const initializeSchema = `-- name: InitializeSchema :exec
CREATE TABLE IF NOT EXISTS content (
id TEXT NOT NULL,
site_id TEXT NOT NULL,
- value TEXT NOT NULL,
- type TEXT NOT NULL CHECK (type IN ('text', 'markdown', 'link')),
+ html_content TEXT NOT NULL,
+ original_template TEXT,
+ type TEXT NOT NULL CHECK (type IN ('text', 'link')),
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
updated_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
last_edited_by TEXT DEFAULT 'system' NOT NULL,
@@ -32,7 +33,8 @@ CREATE TABLE IF NOT EXISTS content_versions (
version_id INTEGER PRIMARY KEY AUTOINCREMENT,
content_id TEXT NOT NULL,
site_id TEXT NOT NULL,
- value TEXT NOT NULL,
+ html_content TEXT NOT NULL,
+ original_template TEXT,
type TEXT NOT NULL,
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
created_by TEXT DEFAULT 'system' NOT NULL
diff --git a/internal/db/sqlite/versions.sql.go b/internal/db/sqlite/versions.sql.go
index 8d46807..17c7f8d 100644
--- a/internal/db/sqlite/versions.sql.go
+++ b/internal/db/sqlite/versions.sql.go
@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
-// sqlc v1.29.0
+// sqlc v1.30.0
// source: versions.sql
package sqlite
@@ -11,23 +11,25 @@ import (
)
const createContentVersion = `-- name: CreateContentVersion :exec
-INSERT INTO content_versions (content_id, site_id, value, type, created_by)
-VALUES (?1, ?2, ?3, ?4, ?5)
+INSERT INTO content_versions (content_id, site_id, html_content, original_template, type, created_by)
+VALUES (?1, ?2, ?3, ?4, ?5, ?6)
`
type CreateContentVersionParams struct {
- ContentID string `json:"content_id"`
- SiteID string `json:"site_id"`
- Value string `json:"value"`
- Type string `json:"type"`
- CreatedBy string `json:"created_by"`
+ ContentID string `json:"content_id"`
+ SiteID string `json:"site_id"`
+ HtmlContent string `json:"html_content"`
+ OriginalTemplate sql.NullString `json:"original_template"`
+ Type string `json:"type"`
+ CreatedBy string `json:"created_by"`
}
func (q *Queries) CreateContentVersion(ctx context.Context, arg CreateContentVersionParams) error {
_, err := q.db.ExecContext(ctx, createContentVersion,
arg.ContentID,
arg.SiteID,
- arg.Value,
+ arg.HtmlContent,
+ arg.OriginalTemplate,
arg.Type,
arg.CreatedBy,
)
@@ -51,8 +53,8 @@ func (q *Queries) DeleteOldVersions(ctx context.Context, arg DeleteOldVersionsPa
const getAllVersionsForSite = `-- name: GetAllVersionsForSite :many
SELECT
- cv.version_id, cv.content_id, cv.site_id, cv.value, cv.type, cv.created_at, cv.created_by,
- c.value as current_value
+ cv.version_id, cv.content_id, cv.site_id, cv.html_content, cv.original_template, cv.type, cv.created_at, cv.created_by,
+ c.html_content as current_html_content
FROM content_versions cv
LEFT JOIN content c ON cv.content_id = c.id AND cv.site_id = c.site_id
WHERE cv.site_id = ?1
@@ -66,14 +68,15 @@ type GetAllVersionsForSiteParams struct {
}
type GetAllVersionsForSiteRow struct {
- VersionID int64 `json:"version_id"`
- ContentID string `json:"content_id"`
- SiteID string `json:"site_id"`
- Value string `json:"value"`
- Type string `json:"type"`
- CreatedAt int64 `json:"created_at"`
- CreatedBy string `json:"created_by"`
- CurrentValue sql.NullString `json:"current_value"`
+ VersionID int64 `json:"version_id"`
+ ContentID string `json:"content_id"`
+ SiteID string `json:"site_id"`
+ HtmlContent string `json:"html_content"`
+ OriginalTemplate sql.NullString `json:"original_template"`
+ Type string `json:"type"`
+ CreatedAt int64 `json:"created_at"`
+ CreatedBy string `json:"created_by"`
+ CurrentHtmlContent sql.NullString `json:"current_html_content"`
}
func (q *Queries) GetAllVersionsForSite(ctx context.Context, arg GetAllVersionsForSiteParams) ([]GetAllVersionsForSiteRow, error) {
@@ -89,11 +92,12 @@ func (q *Queries) GetAllVersionsForSite(ctx context.Context, arg GetAllVersionsF
&i.VersionID,
&i.ContentID,
&i.SiteID,
- &i.Value,
+ &i.HtmlContent,
+ &i.OriginalTemplate,
&i.Type,
&i.CreatedAt,
&i.CreatedBy,
- &i.CurrentValue,
+ &i.CurrentHtmlContent,
); err != nil {
return nil, err
}
@@ -109,7 +113,7 @@ func (q *Queries) GetAllVersionsForSite(ctx context.Context, arg GetAllVersionsF
}
const getContentVersion = `-- name: GetContentVersion :one
-SELECT version_id, content_id, site_id, value, type, created_at, created_by
+SELECT version_id, content_id, site_id, html_content, original_template, type, created_at, created_by
FROM content_versions
WHERE version_id = ?1
`
@@ -121,7 +125,8 @@ func (q *Queries) GetContentVersion(ctx context.Context, versionID int64) (Conte
&i.VersionID,
&i.ContentID,
&i.SiteID,
- &i.Value,
+ &i.HtmlContent,
+ &i.OriginalTemplate,
&i.Type,
&i.CreatedAt,
&i.CreatedBy,
@@ -130,7 +135,7 @@ func (q *Queries) GetContentVersion(ctx context.Context, versionID int64) (Conte
}
const getContentVersionHistory = `-- name: GetContentVersionHistory :many
-SELECT version_id, content_id, site_id, value, type, created_at, created_by
+SELECT version_id, content_id, site_id, html_content, original_template, type, created_at, created_by
FROM content_versions
WHERE content_id = ?1 AND site_id = ?2
ORDER BY created_at DESC
@@ -156,7 +161,8 @@ func (q *Queries) GetContentVersionHistory(ctx context.Context, arg GetContentVe
&i.VersionID,
&i.ContentID,
&i.SiteID,
- &i.Value,
+ &i.HtmlContent,
+ &i.OriginalTemplate,
&i.Type,
&i.CreatedAt,
&i.CreatedBy,
diff --git a/internal/engine/database_client.go b/internal/engine/database_client.go
index 632d8ef..e5f37f5 100644
--- a/internal/engine/database_client.go
+++ b/internal/engine/database_client.go
@@ -2,6 +2,7 @@ package engine
import (
"context"
+ "database/sql"
"fmt"
"github.com/insertr/insertr/internal/db"
@@ -9,6 +10,14 @@ import (
"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
@@ -33,11 +42,12 @@ func (c *DatabaseClient) GetContent(siteID, contentID string) (*ContentItem, err
return nil, err
}
return &ContentItem{
- ID: content.ID,
- SiteID: content.SiteID,
- Value: content.Value,
- Type: content.Type,
- LastEditedBy: content.LastEditedBy,
+ ID: content.ID,
+ SiteID: content.SiteID,
+ HTMLContent: content.HtmlContent,
+ OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
+ Type: content.Type,
+ LastEditedBy: content.LastEditedBy,
}, nil
case "postgresql":
@@ -49,11 +59,12 @@ func (c *DatabaseClient) GetContent(siteID, contentID string) (*ContentItem, err
return nil, err
}
return &ContentItem{
- ID: content.ID,
- SiteID: content.SiteID,
- Value: content.Value,
- Type: content.Type,
- LastEditedBy: content.LastEditedBy,
+ ID: content.ID,
+ SiteID: content.SiteID,
+ HTMLContent: content.HtmlContent,
+ OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
+ Type: content.Type,
+ LastEditedBy: content.LastEditedBy,
}, nil
default:
@@ -76,11 +87,12 @@ func (c *DatabaseClient) GetBulkContent(siteID string, contentIDs []string) (map
items := make(map[string]ContentItem)
for _, content := range contents {
items[content.ID] = ContentItem{
- ID: content.ID,
- SiteID: content.SiteID,
- Value: content.Value,
- Type: content.Type,
- LastEditedBy: content.LastEditedBy,
+ ID: content.ID,
+ SiteID: content.SiteID,
+ HTMLContent: content.HtmlContent,
+ OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
+ Type: content.Type,
+ LastEditedBy: content.LastEditedBy,
}
}
return items, nil
@@ -97,11 +109,12 @@ func (c *DatabaseClient) GetBulkContent(siteID string, contentIDs []string) (map
items := make(map[string]ContentItem)
for _, content := range contents {
items[content.ID] = ContentItem{
- ID: content.ID,
- SiteID: content.SiteID,
- Value: content.Value,
- Type: content.Type,
- LastEditedBy: content.LastEditedBy,
+ ID: content.ID,
+ SiteID: content.SiteID,
+ HTMLContent: content.HtmlContent,
+ OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
+ Type: content.Type,
+ LastEditedBy: content.LastEditedBy,
}
}
return items, nil
@@ -123,11 +136,12 @@ func (c *DatabaseClient) GetAllContent(siteID string) (map[string]ContentItem, e
items := make(map[string]ContentItem)
for _, content := range contents {
items[content.ID] = ContentItem{
- ID: content.ID,
- SiteID: content.SiteID,
- Value: content.Value,
- Type: content.Type,
- LastEditedBy: content.LastEditedBy,
+ ID: content.ID,
+ SiteID: content.SiteID,
+ HTMLContent: content.HtmlContent,
+ OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
+ Type: content.Type,
+ LastEditedBy: content.LastEditedBy,
}
}
return items, nil
@@ -141,11 +155,12 @@ func (c *DatabaseClient) GetAllContent(siteID string) (map[string]ContentItem, e
items := make(map[string]ContentItem)
for _, content := range contents {
items[content.ID] = ContentItem{
- ID: content.ID,
- SiteID: content.SiteID,
- Value: content.Value,
- Type: content.Type,
- LastEditedBy: content.LastEditedBy,
+ ID: content.ID,
+ SiteID: content.SiteID,
+ HTMLContent: content.HtmlContent,
+ OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
+ Type: content.Type,
+ LastEditedBy: content.LastEditedBy,
}
}
return items, nil
@@ -154,3 +169,61 @@ func (c *DatabaseClient) GetAllContent(siteID string) (map[string]ContentItem, e
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, contentType, 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),
+ Type: contentType,
+ LastEditedBy: lastEditedBy,
+ })
+ if err != nil {
+ return nil, err
+ }
+ return &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.database.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 &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.database.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}
+}
diff --git a/internal/engine/engine.go b/internal/engine/engine.go
index b88d571..383d0a4 100644
--- a/internal/engine/engine.go
+++ b/internal/engine/engine.go
@@ -17,14 +17,17 @@ type ContentEngine struct {
idGenerator *IDGenerator
client ContentClient
authProvider *AuthProvider
+ injector *Injector
}
// NewContentEngine creates a new content processing engine
func NewContentEngine(client ContentClient) *ContentEngine {
+ authProvider := &AuthProvider{Type: "mock"} // default
return &ContentEngine{
idGenerator: NewIDGenerator(),
client: client,
- authProvider: &AuthProvider{Type: "mock"}, // default
+ authProvider: authProvider,
+ injector: NewInjector(client, ""), // siteID will be set per operation
}
}
@@ -37,6 +40,7 @@ func NewContentEngineWithAuth(client ContentClient, authProvider *AuthProvider)
idGenerator: NewIDGenerator(),
client: client,
authProvider: authProvider,
+ injector: NewInjectorWithAuth(client, "", authProvider), // siteID will be set per operation
}
}
@@ -84,6 +88,20 @@ func (e *ContentEngine) ProcessContent(input ContentInput) (*ContentResult, erro
// Add/update content attributes to the node
e.addContentAttributes(elem.Node, id, elem.Type)
+
+ // Store content and template for newly discovered elements (first-pass)
+ if wasGenerated && (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(input.SiteID, id, htmlContent, originalTemplate, elem.Type, "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)
+ }
+ }
}
// 4. Inject content if required by mode
@@ -157,7 +175,7 @@ func (e *ContentEngine) determineContentType(node *html.Node) string {
case "h1", "h2", "h3", "h4", "h5", "h6":
return "text"
case "p", "div", "section", "article", "span":
- return "markdown"
+ return "text"
default:
return "text"
}
@@ -211,28 +229,35 @@ func (e *ContentEngine) injectContent(elements []ProcessedElement, siteID string
if contentItem != nil {
// Inject the content into the element
- elem.Content = contentItem.Value
- e.injectContentIntoNode(elem.Node, contentItem.Value, contentItem.Type)
+ elem.Content = contentItem.HTMLContent
+
+ // Update injector siteID for this operation
+ e.injector.siteID = siteID
+ e.injector.injectHTMLContent(elem.Node, contentItem.HTMLContent)
}
}
return nil
}
-// injectContentIntoNode injects content value into an HTML node
-func (e *ContentEngine) injectContentIntoNode(node *html.Node, content, contentType string) {
- // Clear existing text content
- for child := node.FirstChild; child != nil; {
- next := child.NextSibling
- if child.Type == html.TextNode {
- node.RemoveChild(child)
+// extractHTMLContent extracts the inner HTML content from a node
+func (e *ContentEngine) extractHTMLContent(node *html.Node) string {
+ var content strings.Builder
+
+ // Render all child nodes in order to preserve HTML structure
+ for child := node.FirstChild; child != nil; child = child.NextSibling {
+ if err := html.Render(&content, child); err == nil {
+ // All nodes (text and element) rendered in correct order
}
- child = next
}
- // Add new text content
- textNode := &html.Node{
- Type: html.TextNode,
- Data: content,
- }
- node.AppendChild(textNode)
+ return strings.TrimSpace(content.String())
+}
+
+// extractOriginalTemplate extracts the outer HTML of the element (including the element itself)
+func (e *ContentEngine) extractOriginalTemplate(node *html.Node) string {
+ var buf strings.Builder
+ if err := html.Render(&buf, node); err != nil {
+ return ""
+ }
+ return buf.String()
}
diff --git a/internal/engine/id_generator.go b/internal/engine/id_generator.go
index 34d8400..a656ade 100644
--- a/internal/engine/id_generator.go
+++ b/internal/engine/id_generator.go
@@ -1,12 +1,11 @@
package engine
import (
- "crypto/sha256"
- "encoding/hex"
"fmt"
"path/filepath"
"strings"
+ "github.com/google/uuid"
"golang.org/x/net/html"
)
@@ -36,12 +35,10 @@ func (g *IDGenerator) Generate(node *html.Node, filePath string) string {
// 3. Build readable prefix (deterministic, no runtime counting)
prefix := g.buildDeterministicPrefix(fileName, tag, primaryClass)
- // 5. Add collision-resistant suffix
- signature := g.createSignature(node, filePath)
- hash := sha256.Sum256([]byte(signature))
- suffix := hex.EncodeToString(hash[:3])
+ // 5. Add UUID-based suffix for guaranteed uniqueness
+ uuidSuffix := uuid.New().String()[:6] // Use first 6 chars of UUID
- finalID := fmt.Sprintf("%s-%s", prefix, suffix)
+ finalID := fmt.Sprintf("%s-%s", prefix, uuidSuffix)
// Ensure uniqueness (should be guaranteed by hash, but safety check)
g.usedIDs[finalID] = true
@@ -114,14 +111,10 @@ func (g *IDGenerator) buildPrefix(fileName, tag, primaryClass string, index int)
return strings.Join(parts, "-")
}
-// createSignature creates a unique signature for collision resistance
+// createSignature creates a unique signature for collision resistance (DEPRECATED - using UUID now)
func (g *IDGenerator) createSignature(node *html.Node, filePath string) string {
- // Minimal signature for uniqueness
- tag := node.Data
- classes := strings.Join(GetClasses(node), " ")
- domPath := g.getSimpleDOMPath(node)
-
- return fmt.Sprintf("%s|%s|%s|%s", filePath, domPath, tag, classes)
+ // This method is kept for compatibility but not used in UUID-based generation
+ return ""
}
// getSimpleDOMPath creates a simple DOM path for uniqueness
@@ -142,3 +135,68 @@ func (g *IDGenerator) getSimpleDOMPath(node *html.Node) string {
return strings.Join(pathParts, ">")
}
+
+// getContentPreview extracts first 50 characters of text content for uniqueness
+func (g *IDGenerator) getContentPreview(node *html.Node) string {
+ var text strings.Builder
+ g.extractTextContent(node, &text)
+ content := strings.TrimSpace(text.String())
+ if len(content) > 50 {
+ content = content[:50]
+ }
+ // Remove newlines and normalize whitespace
+ content = strings.ReplaceAll(content, "\n", " ")
+ content = strings.ReplaceAll(content, "\t", " ")
+ for strings.Contains(content, " ") {
+ content = strings.ReplaceAll(content, " ", " ")
+ }
+ return content
+}
+
+// extractTextContent recursively extracts text content from a node
+func (g *IDGenerator) extractTextContent(node *html.Node, text *strings.Builder) {
+ if node.Type == html.TextNode {
+ text.WriteString(node.Data)
+ }
+ for child := node.FirstChild; child != nil; child = child.NextSibling {
+ g.extractTextContent(child, text)
+ }
+}
+
+// getSiblingIndex returns the position of this element among its siblings of the same type
+func (g *IDGenerator) getSiblingIndex(node *html.Node) int {
+ if node.Parent == nil {
+ return 0
+ }
+
+ index := 0
+ tag := node.Data
+ classes := GetClasses(node)
+
+ for sibling := node.Parent.FirstChild; sibling != nil; sibling = sibling.NextSibling {
+ if sibling.Type == html.ElementNode && sibling.Data == tag {
+ siblingClasses := GetClasses(sibling)
+ // Check if classes match (for more precise positioning)
+ if g.classesMatch(classes, siblingClasses) {
+ if sibling == node {
+ return index
+ }
+ index++
+ }
+ }
+ }
+ return index
+}
+
+// classesMatch checks if two class lists are equivalent
+func (g *IDGenerator) classesMatch(classes1, classes2 []string) bool {
+ if len(classes1) != len(classes2) {
+ return false
+ }
+ for i, class := range classes1 {
+ if i >= len(classes2) || class != classes2[i] {
+ return false
+ }
+ }
+ return true
+}
diff --git a/internal/engine/injector.go b/internal/engine/injector.go
index 6c9b9e0..ad30cea 100644
--- a/internal/engine/injector.go
+++ b/internal/engine/injector.go
@@ -12,7 +12,6 @@ import (
type Injector struct {
client ContentClient
siteID string
- mdProcessor *MarkdownProcessor
authProvider *AuthProvider
}
@@ -21,7 +20,6 @@ func NewInjector(client ContentClient, siteID string) *Injector {
return &Injector{
client: client,
siteID: siteID,
- mdProcessor: NewMarkdownProcessor(),
authProvider: &AuthProvider{Type: "mock"}, // default
}
}
@@ -34,7 +32,6 @@ func NewInjectorWithAuth(client ContentClient, siteID string, authProvider *Auth
return &Injector{
client: client,
siteID: siteID,
- mdProcessor: NewMarkdownProcessor(),
authProvider: authProvider,
}
}
@@ -53,17 +50,8 @@ func (i *Injector) InjectContent(element *Element, contentID string) error {
return nil
}
- // Replace element content based on type
- switch element.Type {
- case "text":
- i.injectTextContent(element.Node, contentItem.Value)
- case "markdown":
- i.injectMarkdownContent(element.Node, contentItem.Value)
- case "link":
- i.injectLinkContent(element.Node, contentItem.Value)
- default:
- i.injectTextContent(element.Node, contentItem.Value)
- }
+ // Direct HTML injection for all content types
+ i.injectHTMLContent(element.Node, contentItem.HTMLContent)
// Add data attributes for editor functionality
i.AddContentAttributes(element.Node, contentID, element.Type)
@@ -97,65 +85,13 @@ func (i *Injector) InjectBulkContent(elements []ElementWithID) error {
continue
}
- // Replace content based on type
- switch elem.Element.Type {
- case "text":
- i.injectTextContent(elem.Element.Node, contentItem.Value)
- case "markdown":
- i.injectMarkdownContent(elem.Element.Node, contentItem.Value)
- case "link":
- i.injectLinkContent(elem.Element.Node, contentItem.Value)
- default:
- i.injectTextContent(elem.Element.Node, contentItem.Value)
- }
+ // Direct HTML injection for all content types
+ i.injectHTMLContent(elem.Element.Node, contentItem.HTMLContent)
}
return nil
}
-// injectTextContent replaces text content in an element
-func (i *Injector) injectTextContent(node *html.Node, content string) {
- // Remove all child nodes
- for child := node.FirstChild; child != nil; {
- next := child.NextSibling
- node.RemoveChild(child)
- child = next
- }
-
- // Add new text content
- textNode := &html.Node{
- Type: html.TextNode,
- Data: content,
- }
- node.AppendChild(textNode)
-}
-
-// injectMarkdownContent handles markdown content - converts markdown to HTML
-func (i *Injector) injectMarkdownContent(node *html.Node, content string) {
- if content == "" {
- i.injectTextContent(node, "")
- return
- }
-
- // Convert markdown to HTML using server processor
- htmlContent, err := i.mdProcessor.ToHTML(content)
- if err != nil {
- log.Printf("⚠️ Markdown conversion failed for content '%s': %v, falling back to text", content, err)
- i.injectTextContent(node, content)
- return
- }
-
- // Inject the HTML content
- i.injectHTMLContent(node, htmlContent)
-}
-
-// injectLinkContent handles link/button content with URL extraction
-func (i *Injector) injectLinkContent(node *html.Node, content string) {
- // For now, just inject the text content
- // TODO: Parse content for URL and text components
- i.injectTextContent(node, content)
-}
-
// injectHTMLContent safely injects HTML content into a DOM node
// Preserves the original element and only replaces its content
func (i *Injector) injectHTMLContent(node *html.Node, htmlContent string) {
@@ -172,8 +108,14 @@ func (i *Injector) injectHTMLContent(node *html.Node, htmlContent string) {
// Parse HTML string
doc, err := html.Parse(strings.NewReader(wrappedHTML))
if err != nil {
- log.Printf("Failed to parse HTML content '%s': %v, falling back to text", htmlContent, err)
- i.injectTextContent(node, htmlContent)
+ log.Printf("Failed to parse HTML content '%s': %v, falling back to text node", htmlContent, err)
+ // Fallback: inject as text node
+ i.clearNode(node)
+ textNode := &html.Node{
+ Type: html.TextNode,
+ Data: htmlContent,
+ }
+ node.AppendChild(textNode)
return
}
diff --git a/internal/engine/markdown.go b/internal/engine/markdown.go
deleted file mode 100644
index 273b16a..0000000
--- a/internal/engine/markdown.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package engine
-
-import (
- "bytes"
- "log"
- "strings"
-
- "github.com/yuin/goldmark"
- "github.com/yuin/goldmark/parser"
- "github.com/yuin/goldmark/renderer/html"
- "github.com/yuin/goldmark/util"
-)
-
-// MarkdownProcessor handles minimal markdown processing
-// Supports only: **bold**, *italic*, and [link](url)
-type MarkdownProcessor struct {
- parser goldmark.Markdown
-}
-
-// NewMarkdownProcessor creates a new markdown processor with minimal configuration
-func NewMarkdownProcessor() *MarkdownProcessor {
- // Configure goldmark to only support basic inline formatting
- md := goldmark.New(
- goldmark.WithParserOptions(
- parser.WithInlineParsers(
- // Bold (**text**) and italic (*text*) - same parser handles both
- util.Prioritized(parser.NewEmphasisParser(), 500),
-
- // Links [text](url)
- util.Prioritized(parser.NewLinkParser(), 600),
- ),
- // Disable all block parsers except paragraph (no headings, lists, etc.)
- parser.WithBlockParsers(
- util.Prioritized(parser.NewParagraphParser(), 200),
- ),
- ),
- goldmark.WithRendererOptions(
- html.WithXHTML(), //
instead of
- html.WithHardWraps(), // Line breaks become
- html.WithUnsafe(), // Allow existing HTML to pass through
- ),
- )
-
- return &MarkdownProcessor{parser: md}
-}
-
-// ToHTML converts markdown string to HTML
-func (mp *MarkdownProcessor) ToHTML(markdown string) (string, error) {
- if markdown == "" {
- return "", nil
- }
-
- var buf bytes.Buffer
- if err := mp.parser.Convert([]byte(markdown), &buf); err != nil {
- log.Printf("Markdown conversion failed: %v", err)
- return "", err
- }
-
- html := buf.String()
-
- // Clean up goldmark's paragraph wrapping for inline content
- // If content is wrapped in a single tag, extract just the inner content
- html = strings.TrimSpace(html)
-
- if strings.HasPrefix(html, "
") && strings.HasSuffix(html, "
") {
- // Check if this is a single paragraph (no other tags inside)
- inner := html[3 : len(html)-4] // Remove
and
- if !strings.Contains(inner, "") {
- // Single paragraph - return just the inner content for inline injection
- return inner, nil
- }
- }
-
- // Multiple paragraphs or other block content - return as-is
- return html, nil
-}
diff --git a/internal/engine/types.go b/internal/engine/types.go
index 6530c4d..e1ed683 100644
--- a/internal/engine/types.go
+++ b/internal/engine/types.go
@@ -35,7 +35,7 @@ type ContentResult struct {
type ProcessedElement struct {
Node *html.Node // HTML node
ID string // Generated content ID
- Type string // Content type (text, markdown, link)
+ Type string // Content type (text, link)
Content string // Injected content (if any)
Generated bool // Whether ID was generated (vs existing)
Tag string // Element tag name
@@ -48,16 +48,18 @@ 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, contentType, lastEditedBy string) (*ContentItem, error)
}
// ContentItem represents a piece of content from the database
type ContentItem struct {
- ID string `json:"id"`
- SiteID string `json:"site_id"`
- Value string `json:"value"`
- Type string `json:"type"`
- UpdatedAt string `json:"updated_at"`
- LastEditedBy string `json:"last_edited_by,omitempty"`
+ ID string `json:"id"`
+ SiteID string `json:"site_id"`
+ HTMLContent string `json:"html_content"`
+ OriginalTemplate string `json:"original_template"`
+ Type string `json:"type"`
+ UpdatedAt string `json:"updated_at"`
+ LastEditedBy string `json:"last_edited_by,omitempty"`
}
// ContentResponse represents the API response structure
diff --git a/lib/package-lock.json b/lib/package-lock.json
index 01eb4fe..f9cb9bc 100644
--- a/lib/package-lock.json
+++ b/lib/package-lock.json
@@ -8,10 +8,6 @@
"name": "@insertr/lib",
"version": "1.0.0",
"license": "MIT",
- "dependencies": {
- "marked": "^16.2.1",
- "turndown": "^7.2.1"
- },
"devDependencies": {
"@rollup/plugin-node-resolve": "^15.0.0",
"@rollup/plugin-terser": "^0.4.0",
@@ -68,12 +64,6 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
- "node_modules/@mixmark-io/domino": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz",
- "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==",
- "license": "BSD-2-Clause"
- },
"node_modules/@rollup/plugin-node-resolve": {
"version": "15.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz",
@@ -264,18 +254,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/marked": {
- "version": "16.2.1",
- "resolved": "https://registry.npmjs.org/marked/-/marked-16.2.1.tgz",
- "integrity": "sha512-r3UrXED9lMlHF97jJByry90cwrZBBvZmjG1L68oYfuPMW+uDTnuMbyJDymCWwbTE+f+3LhpNDKfpR3a3saFyjA==",
- "license": "MIT",
- "bin": {
- "marked": "bin/marked.js"
- },
- "engines": {
- "node": ">= 20"
- }
- },
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -434,15 +412,6 @@
"engines": {
"node": ">=10"
}
- },
- "node_modules/turndown": {
- "version": "7.2.1",
- "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.1.tgz",
- "integrity": "sha512-7YiPJw6rLClQL3oUKN3KgMaXeJJ2lAyZItclgKDurqnH61so4k4IH/qwmMva0zpuJc/FhRExBBnk7EbeFANlgQ==",
- "license": "MIT",
- "dependencies": {
- "@mixmark-io/domino": "^2.2.0"
- }
}
}
}
diff --git a/lib/src/core/api-client.js b/lib/src/core/api-client.js
index b41a530..9f2b625 100644
--- a/lib/src/core/api-client.js
+++ b/lib/src/core/api-client.js
@@ -33,7 +33,7 @@ export class ApiClient {
try {
const payload = {
html_markup: htmlMarkup, // Always send HTML markup - server extracts ID or generates new one
- value: content,
+ html_content: content,
type: type,
file_path: this.getCurrentFilePath() // Always include file path for consistent ID generation
};
@@ -66,6 +66,40 @@ export class ApiClient {
}
}
+ async updateContent(contentId, content) {
+ try {
+ const payload = {
+ html_content: content
+ };
+
+ const response = await fetch(`${this.baseUrl}/${contentId}?site_id=${this.siteId}`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${this.getAuthToken()}`
+ },
+ body: JSON.stringify(payload)
+ });
+
+ if (response.ok) {
+ const result = await response.json();
+ console.log(`✅ Content updated: ${contentId}`);
+ return result;
+ } else {
+ console.warn(`⚠️ Update failed (${response.status}): ${contentId}`);
+ return false;
+ }
+ } catch (error) {
+ if (error.name === 'TypeError' && error.message.includes('fetch')) {
+ console.warn(`🔌 API Server not reachable at ${this.baseUrl}`);
+ console.warn('💡 Start full-stack development: just dev');
+ } else {
+ console.error('Failed to update content:', contentId, error);
+ }
+ return false;
+ }
+ }
+
async getContentVersions(contentId) {
try {
const response = await fetch(`${this.baseUrl}/${contentId}/versions?site_id=${this.siteId}`);
diff --git a/lib/src/core/insertr.js b/lib/src/core/insertr.js
index 067a69c..d1bcf82 100644
--- a/lib/src/core/insertr.js
+++ b/lib/src/core/insertr.js
@@ -134,24 +134,13 @@ export class InsertrCore {
detectContentType(element) {
const tag = element.tagName.toLowerCase();
- if (element.classList.contains('insertr-group')) {
- return 'group';
+ // Only return database-valid types: 'text' or 'link'
+ if (tag === 'a' || tag === 'button') {
+ return 'link';
}
- switch (tag) {
- case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6':
- return 'text';
- case 'p':
- return 'textarea';
- case 'a': case 'button':
- return 'link';
- case 'div': case 'section':
- return 'text';
- case 'span':
- return 'text';
- default:
- return 'text';
- }
+ // All other elements are text content
+ return 'text';
}
// Get all elements with their metadata, including group elements