feat: complete HTML-first architecture implementation (Phase 1 & 2)

Major architectural simplification removing content type complexity:

Database Schema:
- Remove 'type' field from content and content_versions tables
- Simplify to pure HTML storage with html_content + original_template
- Regenerate all sqlc models for SQLite and PostgreSQL

API Simplification:
- Remove content type routing and validation
- Eliminate type-specific handlers (text/markdown/structured)
- Unified HTML-first approach for all content operations
- Simplify CreateContent and UpdateContent to HTML-only

Backend Enhancements:
- Update enhancer to only generate data-content-id (no data-content-type)
- Improve container expansion utilities with comprehensive block/inline rules
- Add Phase 3 preparation with boundary-respecting traversal logic
- Strengthen element classification for viable children detection

Documentation:
- Update TODO.md to reflect Phase 1-3 completion status
- Add WORKING_ON.md documenting the architectural transformation
- Mark container expansion and HTML-first architecture as complete

This completes the transition to a unified HTML-first content management system
with automatic style detection and element-based behavior, eliminating the
complex multi-type system in favor of semantic HTML-driven editing.
This commit is contained in:
2025-09-21 19:23:54 +02:00
parent b5e601d09f
commit b75eda2a87
25 changed files with 470 additions and 214 deletions

40
TODO.md
View File

@@ -10,12 +10,15 @@
- **Authentication System**: Mock (development) + Authentik OIDC (production) - **Authentication System**: Mock (development) + Authentik OIDC (production)
- **Build-Time Enhancement**: Content injection from database to static HTML - **Build-Time Enhancement**: Content injection from database to static HTML
- **Development Workflow**: Hot reload, auto-enhanced demo sites, seamless testing - **Development Workflow**: Hot reload, auto-enhanced demo sites, seamless testing
- **Container Transformation**: CLASSES.md syntactic sugar - containers auto-expand to viable children
### **🏗️ Architecture Achievements** ### **🏗️ Architecture Achievements**
- **Zero Configuration**: Just add `class="insertr"` to any element - **Zero Configuration**: Just add `class="insertr"` to any element
- **Framework Agnostic**: Works with any static site generator - **Framework Agnostic**: Works with any static site generator
- **Performance First**: Regular visitors get pure static HTML with zero CMS overhead - **Performance First**: Regular visitors get pure static HTML with zero CMS overhead
- **HTML-First**: No lossy markdown conversion - perfect attribute preservation - **HTML-First**: No lossy markdown conversion - perfect attribute preservation
- **Unified System**: Single HTML preservation path for all content types
- **Element-Based Behavior**: Automatic editing interface based on HTML tag semantics
--- ---
@@ -24,7 +27,7 @@
### **🔴 Phase 1: Editor Integration Polish** (High Priority) ### **🔴 Phase 1: Editor Integration Polish** (High Priority)
#### **Frontend-Backend Integration** #### **Frontend-Backend Integration**
- [ ] **Editor-API Connection**: Wire up StyleAware editor saves to actual HTTP API - [x] **Editor-API Connection**: StyleAware editor saves successfully to HTTP API
- [ ] **Error Handling**: Proper error states, loading indicators, offline handling - [ ] **Error Handling**: Proper error states, loading indicators, offline handling
- [ ] **Content Validation**: Client-side validation before API calls - [ ] **Content Validation**: Client-side validation before API calls
- [ ] **Save Feedback**: Professional save/error feedback in editor interface - [ ] **Save Feedback**: Professional save/error feedback in editor interface
@@ -49,7 +52,40 @@
- [ ] **Content Approval**: Editorial workflows and publishing controls - [ ] **Content Approval**: Editorial workflows and publishing controls
- [ ] **Performance Monitoring**: Analytics and optimization tools - [ ] **Performance Monitoring**: Analytics and optimization tools
### **🟢 Phase 3: Advanced CMS Features** (Low Priority) ### ** Phase 3: Container Expansion Intelligence** (Complete)
#### **Element Classification and Boundaries**
- [x] **HTML Semantics Approach**: Use HTML tag semantics for block vs inline detection
- [x] **Framework Agnostic Processing**: No special framework container detection
- [x] **Boundary Rules**: Only `.insertr` elements are boundaries, traverse all other containers
- [x] **Block/Inline Classification**: Clear rules for when elements get `.insertr` vs formatting
#### **Implementation Status**
- [x] **Backend Container Transformation**: Implemented syntactic sugar transformation in `internal/engine/engine.go`
- [x] **Frontend Container Logic Removal**: Cleaned up `lib/src/core/insertr.js` - frontend finds enhanced elements only
- [x] **Backend Viable Children**: Updated `internal/engine/utils.go` with comprehensive block/inline logic
- [x] **Recursive Traversal**: Deep nesting support with proper boundary respect implemented
- [x] **CLASSES.md Compliance**: Container expansion now follows specification exactly
#### **Complex Element Handling** (Deferred)
- [ ] **Table Editing**: Complex hierarchy needs separate planning for `<table>`, `<tr>`, `<td>` elements
- Tables have nested semantic structure that doesn't fit simple block/inline model
- Need to determine: Should individual cells be editable? Entire table? Row-level?
- Consider: Table headers, captions, complex layouts, accessibility concerns
- [ ] **Form Element Editing**: Interactive form controls need specialized editors
- `<input>` fields: Different types need different editing interfaces (text, email, etc.)
- `<textarea>`: Should get rich text editing or preserve plain text?
- `<select>` options: Need dynamic option management interface
- `<form>` containers: Validation rules, action URLs, method selection
- Consider: Form submission handling, validation, accessibility
- [ ] **Self-Closing Element Management**: Media and input elements
- `<img>`: Alt text, src, responsive image sets, lazy loading
- `<video>/<audio>`: Multiple sources, controls, accessibility features
- `<input>`: Type-specific validation, placeholder text, required fields
### **🟢 Phase 4: Advanced CMS Features** (Low Priority)
#### **Content Management Enhancements** #### **Content Management Enhancements**
- [ ] **Media Management**: Image upload, asset management, optimization - [ ] **Media Management**: Image upload, asset management, optimization

187
WORKING_ON.md Normal file
View File

@@ -0,0 +1,187 @@
# Insertr Structural Refactoring - Work in Progress
## Current Status: Major Simplification Opportunity Identified
We've identified that the current Insertr codebase has remnants from multiple refactoring phases and can be significantly simplified by fully embracing the HTML-first philosophy outlined in CLASSES.md.
## Key Insights Discovered
### 1. **Obsolete Content Type System**
- **Problem**: Currently has 3 content types (`'text'`, `'html'`, `'structured'`) with complex routing logic
- **HTML-First Reality**: Only need HTML preservation with automatic style detection
- **Opportunity**: Remove `data-content-type` attributes entirely and detect behavior from HTML element tags
### 2. **Multiple Editor Systems (Legacy)**
- **Current**: Complex strategy detection choosing between simple vs rich editors
- **HTML-First**: Always use StyleAwareEditor with HTML preservation and remove legacy code
- **Benefit**: Massive code reduction, consistent behavior everywhere
### 3. **Element-Based Behavior Detection**
- **Instead of**: `data-content-type="link"` attributes
- **Use**: Element tag analysis (`<a>` = link behavior, `<p>` = text behavior)
- **Source**: Behavior rules already defined in CLASSES.md
### 4. **UX Optimization Opportunity**
- **Direct Multi-Property Editing**: `<a class="insertr">` should open URL+text editor immediately
- **Rich Text Editing**: `<p class="insertr">` with nested elements should use StyleAwareEditor
- **Context-Appropriate UX**: Right editor for the right element type
## Planned Refactoring (Major Changes)
Keep in mind that we need no legacy code preserved or backwards compatibility. Database changes can be done to the schema, not with ALTER statements. We have no data to preserve and no users of any current insertr versions.
### Phase 1: Frontend Simplification
- [ ] **Remove content type routing** from StyleAwareEditor
- [ ] **Eliminate 'text' and 'structured' handlers** - keep only HTML preservation
- [ ] **Remove simple/textarea editor** that strips HTML formatting
- [ ] **Implement element-based behavior detection** using tag names
- [ ] **Create direct multi-property editors** for `<a>`, `<button>`, `<img>`
### Phase 2: Backend Simplification
- [ ] **Remove `data-content-type` generation** from enhancer
- [ ] **Simplify API** to be purely HTML-based
- [ ] **Keep only `data-content-id`** for content tracking
- [ ] **Update database schema** remove type field
### Phase 3: Container Expansion Intelligence
- [ ] **Improve viable children detection** with element-type-aware rules
- [ ] **Smart stopping rules** for multi-property elements (`<a>`, `<button>`, `<img>`)
- [ ] **Semantic container expansion** respecting HTML element purposes
- [ ] **Framework/technical container filtering** (React, Gatsby artifacts)
## Technical Architecture Changes
### Before (Complex Multi-System):
```javascript
// Multiple content types with routing
switch (content.type) {
case 'text': // Strips HTML - obsolete
case 'html': // HTML preservation
case 'structured': // Legacy template system
}
// Multiple editor strategies
switch (analysis.strategy) {
case 'simple': // Textarea - strips HTML
case 'rich': // Rich editor with styles
}
```
### After (Unified HTML-First):
```javascript
// Single path for ALL .insertr elements
class StyleAwareEditor {
async initialize() {
// 1. Always extract HTML with preservation
this.originalContent = this.htmlEngine.extractForEditing(this.element);
// 2. Always detect styles from nested elements
this.detectedStyles = this.styleEngine.detectStyles(this.element);
// 3. Element-based UX routing
if (this.isMultiPropertyElement()) {
this.createDirectEditor(); // URL+text for links
} else {
this.createRichEditor(); // Rich text with styles
}
}
}
```
## Container Expansion Rules (New Logic)
Container expansions are syntactic sugar for applying the insertr class to viable children. Our autodetection might need to be revised. Some anchor tags for instance can be a formatting style inside of content, or it could be a standalone navigation link. So for instance a p>a element might be formatting, while div>a could be a link. This might resolve itself when we respect boundaries, but would need further investigation.
### Stopping Rules:
- **Stop**: Element has `.insertr` (respect boundaries)
- **Stop**: Multi-property element found (`<a>`, `<button>`, `<img>` get direct editing)
- **Stop**: Framework containers (React, Gatsby artifacts)
- **Continue**: Semantic containers with viable children
## Benefits of This Refactoring
### 🚀 **Massive Code Reduction**
- Remove 3 content type handlers → 1 HTML handler
- Remove 2 editor types → 1 unified system
- Remove strategy detection → linear flow
- Remove attribute generation → tag-based detection
### 🎯 **Consistent Behavior**
- `<h1>Our <em>Services</em></h1>` always preserves formatting
- Style detection always runs
- No more edge cases between strategies
### ⚡ **Better Performance**
- No strategy detection overhead
- No conditional branching complexity
- Simpler initialization paths
### 🛠️ **Easier Maintenance**
- Single code path to test and debug
- Unified behavior aligns with CLASSES.md
- HTML-first philosophy consistently applied
## Current Working Files
### Fixed in This Session:
-**Style preview system** - Now uses CSS isolation for perfect previews
-**Simple demo cleanup** - Removed unsupported examples 6 & 8
-**Default demo preparation** - Stripped data attributes for clean enhancement
### Next Session Priorities:
1. **Remove content type routing** from StyleAwareEditor
2. **Implement element-based behavior detection**
3. **Create direct multi-property editors** for better UX
4. **Enhance container expansion logic** with smart stopping rules
## Status
**Phase 1, 2 & 3 COMPLETED** - Complete HTML-first architecture with container transformation!
### ✅ **Completed Across Multiple Sessions**:
#### **Phase 1 & 2: HTML-First Architecture**
- **Removed content type routing** from StyleAwareEditor - now uses unified HTML-first approach
- **Eliminated simple/textarea editor** that stripped HTML formatting
- **Implemented element-based behavior detection** using tag names (no more `data-content-type` attributes)
- **Created direct multi-property editors** for `<a>`, `<button>`, `<img>` elements
- **Removed `data-content-type` generation** from backend enhancer
- **Updated database schema** - completely removed type field from content and content_versions tables
- **Regenerated all sqlc code** with new schema
- **Simplified API layer** to be purely HTML-based
#### **Phase 3: Container Transformation**
- **Implemented backend container transformation** following CLASSES.md syntactic sugar specification
- **Added addClass/removeClass methods** to ContentEngine for proper class manipulation
- **Removed frontend container expansion logic** - frontend now only finds enhanced elements
- **Fixed StyleAwareEditor runtime error** (hasMultiPropertyElements method)
- **Verified container transformation** works correctly: `<section class="insertr">` → children get `.insertr`
### 🎯 **Results Achieved**:
- **Massive code reduction**: 3 content handlers → 1 unified system
- **Consistent behavior**: All content now uses HTML preservation with style detection
- **Element-based UX**: `<a>` tags get direct URL+text editor, `<p>` tags get rich text with formatting
- **Container transformation**: Syntactic sugar working per CLASSES.md specification
- **Simplified frontend**: No runtime container expansion - backend handles transformation
- **Zero breaking changes**: User-facing functionality maintained while simplifying backend
### 🚀 **System Now Works As**:
```javascript
// Single path for ALL .insertr elements
class StyleAwareEditor {
async initialize() {
// 1. Always extract HTML with preservation
this.originalContent = this.htmlEngine.extractForEditing(this.element);
// 2. Always detect styles from nested elements
this.detectedStyles = this.styleEngine.detectStyles(this.element);
// 3. Element-based UX routing
if (this.isMultiPropertyElement()) {
this.createDirectEditor(); // URL+text for links
} else {
this.createRichEditor(); // Rich text with styles
}
}
}
```
**Confidence Level**: High - All changes successfully implemented and tested. Server starts correctly, builds complete successfully.

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"path/filepath"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -15,11 +16,14 @@ import (
) )
var enhanceCmd = &cobra.Command{ var enhanceCmd = &cobra.Command{
Use: "enhance [input-dir]", Use: "enhance [input-path]",
Short: "Enhance HTML files by injecting content from database", Short: "Enhance HTML files by injecting content from database",
Long: `Enhance processes HTML files and injects latest content from the database Long: `Enhance processes HTML files and injects latest content from the database
while adding editing capabilities. This is the core build-time enhancement while adding editing capabilities. This is the core build-time enhancement
process that transforms static HTML into an editable CMS.`, process that transforms static HTML into an editable CMS.
The input can be either a directory (to enhance all HTML files) or a single
HTML file. Non-HTML files in directories are copied as-is.`,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
Run: runEnhance, Run: runEnhance,
} }
@@ -36,11 +40,23 @@ func init() {
} }
func runEnhance(cmd *cobra.Command, args []string) { func runEnhance(cmd *cobra.Command, args []string) {
inputDir := args[0] inputPath := args[0]
// Validate input directory // Validate input path and determine if it's a file or directory
if _, err := os.Stat(inputDir); os.IsNotExist(err) { inputStat, err := os.Stat(inputPath)
log.Fatalf("Input directory does not exist: %s", inputDir) if os.IsNotExist(err) {
log.Fatalf("Input path does not exist: %s", inputPath)
}
if err != nil {
log.Fatalf("Error accessing input path: %v", err)
}
isFile := !inputStat.IsDir()
if isFile {
// Validate that single files are HTML files
if !strings.HasSuffix(strings.ToLower(inputPath), ".html") {
log.Fatalf("Single file input must be an HTML file (.html extension): %s", inputPath)
}
} }
// Get configuration values // Get configuration values
@@ -51,9 +67,9 @@ func runEnhance(cmd *cobra.Command, args []string) {
outputDir := viper.GetString("cli.output") outputDir := viper.GetString("cli.output")
// Auto-derive site_id for demo paths or validate for production // Auto-derive site_id for demo paths or validate for production
if strings.Contains(inputDir, "/demos/") || strings.Contains(inputDir, "./demos/") { if strings.Contains(inputPath, "/demos/") || strings.Contains(inputPath, "./demos/") {
// Auto-derive site_id from demo path // Auto-derive site_id from demo path
siteID = content.DeriveOrValidateSiteID(inputDir, siteID) siteID = content.DeriveOrValidateSiteID(inputPath, siteID)
} else { } else {
// Validate site_id for non-demo paths // Validate site_id for non-demo paths
if siteID == "" || siteID == "demo" { if siteID == "" || siteID == "demo" {
@@ -137,14 +153,28 @@ func runEnhance(cmd *cobra.Command, args []string) {
enhancer := content.NewEnhancer(client, siteID, enhancementConfig) enhancer := content.NewEnhancer(client, siteID, enhancementConfig)
fmt.Printf("🚀 Starting enhancement process...\n") fmt.Printf("🚀 Starting enhancement process...\n")
fmt.Printf("📁 Input: %s\n", inputDir) fmt.Printf("📁 Input: %s\n", inputPath)
fmt.Printf("📁 Output: %s\n", outputDir) fmt.Printf("📁 Output: %s\n", outputDir)
fmt.Printf("🏷️ Site ID: %s\n\n", siteID) fmt.Printf("🏷️ Site ID: %s\n\n", siteID)
// Enhance directory // Enhance based on input type
if err := enhancer.EnhanceDirectory(inputDir, outputDir); err != nil { if isFile {
// For single files, determine the output file path
outputFilePath := outputDir
if stat, err := os.Stat(outputDir); err == nil && stat.IsDir() {
// Output is a directory, use input filename in output directory
outputFilePath = filepath.Join(outputDir, filepath.Base(inputPath))
}
// If output doesn't exist or is a file, use it as-is as the output file path
if err := enhancer.EnhanceFile(inputPath, outputFilePath); err != nil {
log.Fatalf("Enhancement failed: %v", err) log.Fatalf("Enhancement failed: %v", err)
} }
} else {
if err := enhancer.EnhanceDirectory(inputPath, outputDir); err != nil {
log.Fatalf("Enhancement failed: %v", err)
}
}
fmt.Printf("\n✅ Enhancement complete! Enhanced files available in: %s\n", outputDir) fmt.Printf("\n✅ Enhancement complete! Enhanced files available in: %s\n", outputDir)
} }

View File

@@ -314,14 +314,7 @@ func (h *ContentHandler) CreateContent(w http.ResponseWriter, r *http.Request) {
} }
} }
// Determine content type: use provided type, fallback to existing type, default to "text" // HTML-first approach: no content type needed
contentType := req.Type
if contentType == "" && contentExists {
contentType = h.getContentType(existingContent)
}
if contentType == "" {
contentType = "text" // default type for new content
}
var content interface{} var content interface{}
var err error var err error
@@ -333,7 +326,6 @@ func (h *ContentHandler) CreateContent(w http.ResponseWriter, r *http.Request) {
SiteID: siteID, SiteID: siteID,
HtmlContent: req.HTMLContent, HtmlContent: req.HTMLContent,
OriginalTemplate: toNullString(req.OriginalTemplate), OriginalTemplate: toNullString(req.OriginalTemplate),
Type: contentType,
LastEditedBy: userID, LastEditedBy: userID,
}) })
case "postgresql": case "postgresql":
@@ -342,7 +334,6 @@ func (h *ContentHandler) CreateContent(w http.ResponseWriter, r *http.Request) {
SiteID: siteID, SiteID: siteID,
HtmlContent: req.HTMLContent, HtmlContent: req.HTMLContent,
OriginalTemplate: toNullString(req.OriginalTemplate), OriginalTemplate: toNullString(req.OriginalTemplate),
Type: contentType,
LastEditedBy: userID, LastEditedBy: userID,
}) })
default: default:
@@ -447,7 +438,6 @@ func (h *ContentHandler) UpdateContent(w http.ResponseWriter, r *http.Request) {
case "sqlite3": case "sqlite3":
updatedContent, err = h.database.GetSQLiteQueries().UpdateContent(context.Background(), sqlite.UpdateContentParams{ updatedContent, err = h.database.GetSQLiteQueries().UpdateContent(context.Background(), sqlite.UpdateContentParams{
HtmlContent: req.HTMLContent, HtmlContent: req.HTMLContent,
Type: h.getContentType(existingContent),
LastEditedBy: userID, LastEditedBy: userID,
ID: contentID, ID: contentID,
SiteID: siteID, SiteID: siteID,
@@ -455,7 +445,6 @@ func (h *ContentHandler) UpdateContent(w http.ResponseWriter, r *http.Request) {
case "postgresql": case "postgresql":
updatedContent, err = h.database.GetPostgreSQLQueries().UpdateContent(context.Background(), postgresql.UpdateContentParams{ updatedContent, err = h.database.GetPostgreSQLQueries().UpdateContent(context.Background(), postgresql.UpdateContentParams{
HtmlContent: req.HTMLContent, HtmlContent: req.HTMLContent,
Type: h.getContentType(existingContent),
LastEditedBy: userID, LastEditedBy: userID,
ID: contentID, ID: contentID,
SiteID: siteID, SiteID: siteID,
@@ -664,7 +653,6 @@ func (h *ContentHandler) RollbackContent(w http.ResponseWriter, r *http.Request)
sqliteVersion := targetVersion.(sqlite.ContentVersion) sqliteVersion := targetVersion.(sqlite.ContentVersion)
updatedContent, err = h.database.GetSQLiteQueries().UpdateContent(context.Background(), sqlite.UpdateContentParams{ updatedContent, err = h.database.GetSQLiteQueries().UpdateContent(context.Background(), sqlite.UpdateContentParams{
HtmlContent: sqliteVersion.HtmlContent, HtmlContent: sqliteVersion.HtmlContent,
Type: sqliteVersion.Type,
LastEditedBy: userID, LastEditedBy: userID,
ID: contentID, ID: contentID,
SiteID: siteID, SiteID: siteID,
@@ -673,7 +661,6 @@ func (h *ContentHandler) RollbackContent(w http.ResponseWriter, r *http.Request)
pgVersion := targetVersion.(postgresql.ContentVersion) pgVersion := targetVersion.(postgresql.ContentVersion)
updatedContent, err = h.database.GetPostgreSQLQueries().UpdateContent(context.Background(), postgresql.UpdateContentParams{ updatedContent, err = h.database.GetPostgreSQLQueries().UpdateContent(context.Background(), postgresql.UpdateContentParams{
HtmlContent: pgVersion.HtmlContent, HtmlContent: pgVersion.HtmlContent,
Type: pgVersion.Type,
LastEditedBy: userID, LastEditedBy: userID,
ID: contentID, ID: contentID,
SiteID: siteID, SiteID: siteID,
@@ -704,7 +691,6 @@ func (h *ContentHandler) convertToAPIContent(content interface{}) ContentItem {
SiteID: c.SiteID, SiteID: c.SiteID,
HTMLContent: c.HtmlContent, HTMLContent: c.HtmlContent,
OriginalTemplate: fromNullString(c.OriginalTemplate), OriginalTemplate: fromNullString(c.OriginalTemplate),
Type: c.Type,
CreatedAt: time.Unix(c.CreatedAt, 0), CreatedAt: time.Unix(c.CreatedAt, 0),
UpdatedAt: time.Unix(c.UpdatedAt, 0), UpdatedAt: time.Unix(c.UpdatedAt, 0),
LastEditedBy: c.LastEditedBy, LastEditedBy: c.LastEditedBy,
@@ -716,7 +702,6 @@ func (h *ContentHandler) convertToAPIContent(content interface{}) ContentItem {
SiteID: c.SiteID, SiteID: c.SiteID,
HTMLContent: c.HtmlContent, HTMLContent: c.HtmlContent,
OriginalTemplate: fromNullString(c.OriginalTemplate), OriginalTemplate: fromNullString(c.OriginalTemplate),
Type: c.Type,
CreatedAt: time.Unix(c.CreatedAt, 0), CreatedAt: time.Unix(c.CreatedAt, 0),
UpdatedAt: time.Unix(c.UpdatedAt, 0), UpdatedAt: time.Unix(c.UpdatedAt, 0),
LastEditedBy: c.LastEditedBy, LastEditedBy: c.LastEditedBy,
@@ -757,7 +742,6 @@ func (h *ContentHandler) convertToAPIVersionList(versionList interface{}) []Cont
SiteID: version.SiteID, SiteID: version.SiteID,
HTMLContent: version.HtmlContent, HTMLContent: version.HtmlContent,
OriginalTemplate: fromNullString(version.OriginalTemplate), OriginalTemplate: fromNullString(version.OriginalTemplate),
Type: version.Type,
CreatedAt: time.Unix(version.CreatedAt, 0), CreatedAt: time.Unix(version.CreatedAt, 0),
CreatedBy: version.CreatedBy, CreatedBy: version.CreatedBy,
} }
@@ -773,7 +757,6 @@ func (h *ContentHandler) convertToAPIVersionList(versionList interface{}) []Cont
SiteID: version.SiteID, SiteID: version.SiteID,
HTMLContent: version.HtmlContent, HTMLContent: version.HtmlContent,
OriginalTemplate: fromNullString(version.OriginalTemplate), OriginalTemplate: fromNullString(version.OriginalTemplate),
Type: version.Type,
CreatedAt: time.Unix(version.CreatedAt, 0), CreatedAt: time.Unix(version.CreatedAt, 0),
CreatedBy: version.CreatedBy, CreatedBy: version.CreatedBy,
} }
@@ -792,7 +775,6 @@ func (h *ContentHandler) createContentVersion(content interface{}) error {
SiteID: c.SiteID, SiteID: c.SiteID,
HtmlContent: c.HtmlContent, HtmlContent: c.HtmlContent,
OriginalTemplate: c.OriginalTemplate, OriginalTemplate: c.OriginalTemplate,
Type: c.Type,
CreatedBy: c.LastEditedBy, CreatedBy: c.LastEditedBy,
}) })
case "postgresql": case "postgresql":
@@ -802,23 +784,12 @@ func (h *ContentHandler) createContentVersion(content interface{}) error {
SiteID: c.SiteID, SiteID: c.SiteID,
HtmlContent: c.HtmlContent, HtmlContent: c.HtmlContent,
OriginalTemplate: c.OriginalTemplate, OriginalTemplate: c.OriginalTemplate,
Type: c.Type,
CreatedBy: c.LastEditedBy, CreatedBy: c.LastEditedBy,
}) })
} }
return fmt.Errorf("unsupported database type") return fmt.Errorf("unsupported database type")
} }
func (h *ContentHandler) getContentType(content interface{}) string {
switch h.database.GetDBType() {
case "sqlite3":
return content.(sqlite.Content).Type
case "postgresql":
return content.(postgresql.Content).Type
}
return ""
}
func (h *ContentHandler) versionMatches(version interface{}, contentID, siteID string) bool { func (h *ContentHandler) versionMatches(version interface{}, contentID, siteID string) bool {
switch h.database.GetDBType() { switch h.database.GetDBType() {
case "sqlite3": case "sqlite3":

View File

@@ -8,7 +8,6 @@ type ContentItem struct {
SiteID string `json:"site_id"` SiteID string `json:"site_id"`
HTMLContent string `json:"html_content"` HTMLContent string `json:"html_content"`
OriginalTemplate string `json:"original_template"` OriginalTemplate string `json:"original_template"`
Type string `json:"type"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
LastEditedBy string `json:"last_edited_by"` LastEditedBy string `json:"last_edited_by"`
@@ -20,7 +19,6 @@ type ContentVersion struct {
SiteID string `json:"site_id"` SiteID string `json:"site_id"`
HTMLContent string `json:"html_content"` HTMLContent string `json:"html_content"`
OriginalTemplate string `json:"original_template"` OriginalTemplate string `json:"original_template"`
Type string `json:"type"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
CreatedBy string `json:"created_by"` CreatedBy string `json:"created_by"`
} }
@@ -48,7 +46,6 @@ type CreateContentRequest struct {
FilePath string `json:"file_path"` // File path for consistent ID generation FilePath string `json:"file_path"` // File path for consistent ID generation
HTMLContent string `json:"html_content"` // HTML content value HTMLContent string `json:"html_content"` // HTML content value
OriginalTemplate string `json:"original_template"` // Original template markup OriginalTemplate string `json:"original_template"` // Original template markup
Type string `json:"type"` // Content type
SiteID string `json:"site_id,omitempty"` // Site identifier SiteID string `json:"site_id,omitempty"` // Site identifier
CreatedBy string `json:"created_by,omitempty"` // User who created the content CreatedBy string `json:"created_by,omitempty"` // User who created the content
} }

View File

@@ -166,7 +166,7 @@ func (c *HTTPClient) GetAllContent(siteID string) (map[string]engine.ContentItem
} }
// CreateContent creates a new content item via HTTP API // CreateContent creates a new content item via HTTP API
func (c *HTTPClient) CreateContent(siteID, contentID, htmlContent, originalTemplate, contentType, lastEditedBy string) (*engine.ContentItem, error) { func (c *HTTPClient) CreateContent(siteID, contentID, htmlContent, originalTemplate, lastEditedBy string) (*engine.ContentItem, error) {
// For now, HTTPClient CreateContent is not implemented for enhancer use // For now, HTTPClient CreateContent is not implemented for enhancer use
// This would typically be used in API-driven enhancement scenarios // This would typically be used in API-driven enhancement scenarios
return nil, fmt.Errorf("CreateContent not implemented for HTTPClient - use DatabaseClient for enhancement") return nil, fmt.Errorf("CreateContent not implemented for HTTPClient - use DatabaseClient for enhancement")

View File

@@ -144,7 +144,6 @@ func (d *DatabaseClient) convertToContentItem(content interface{}) engine.Conten
SiteID: c.SiteID, SiteID: c.SiteID,
HTMLContent: c.HtmlContent, HTMLContent: c.HtmlContent,
OriginalTemplate: getStringFromNullString(c.OriginalTemplate), OriginalTemplate: getStringFromNullString(c.OriginalTemplate),
Type: c.Type,
UpdatedAt: time.Unix(c.UpdatedAt, 0).Format(time.RFC3339), UpdatedAt: time.Unix(c.UpdatedAt, 0).Format(time.RFC3339),
} }
case "postgresql": case "postgresql":
@@ -154,7 +153,6 @@ func (d *DatabaseClient) convertToContentItem(content interface{}) engine.Conten
SiteID: c.SiteID, SiteID: c.SiteID,
HTMLContent: c.HtmlContent, HTMLContent: c.HtmlContent,
OriginalTemplate: getStringFromNullString(c.OriginalTemplate), OriginalTemplate: getStringFromNullString(c.OriginalTemplate),
Type: c.Type,
UpdatedAt: time.Unix(c.UpdatedAt, 0).Format(time.RFC3339), UpdatedAt: time.Unix(c.UpdatedAt, 0).Format(time.RFC3339),
} }
} }
@@ -183,7 +181,7 @@ func (d *DatabaseClient) convertToContentItemList(contentList interface{}) []eng
} }
// CreateContent creates a new content item // CreateContent creates a new content item
func (c *DatabaseClient) CreateContent(siteID, contentID, htmlContent, originalTemplate, contentType, lastEditedBy string) (*engine.ContentItem, error) { func (c *DatabaseClient) CreateContent(siteID, contentID, htmlContent, originalTemplate, lastEditedBy string) (*engine.ContentItem, error) {
switch c.db.GetDBType() { switch c.db.GetDBType() {
case "sqlite3": case "sqlite3":
content, err := c.db.GetSQLiteQueries().CreateContent(context.Background(), sqlite.CreateContentParams{ content, err := c.db.GetSQLiteQueries().CreateContent(context.Background(), sqlite.CreateContentParams{
@@ -191,7 +189,6 @@ func (c *DatabaseClient) CreateContent(siteID, contentID, htmlContent, originalT
SiteID: siteID, SiteID: siteID,
HtmlContent: htmlContent, HtmlContent: htmlContent,
OriginalTemplate: toNullString(originalTemplate), OriginalTemplate: toNullString(originalTemplate),
Type: contentType,
LastEditedBy: lastEditedBy, LastEditedBy: lastEditedBy,
}) })
if err != nil { if err != nil {
@@ -202,7 +199,6 @@ func (c *DatabaseClient) CreateContent(siteID, contentID, htmlContent, originalT
SiteID: content.SiteID, SiteID: content.SiteID,
HTMLContent: content.HtmlContent, HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate), OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
Type: content.Type,
LastEditedBy: content.LastEditedBy, LastEditedBy: content.LastEditedBy,
}, nil }, nil
@@ -212,7 +208,6 @@ func (c *DatabaseClient) CreateContent(siteID, contentID, htmlContent, originalT
SiteID: siteID, SiteID: siteID,
HtmlContent: htmlContent, HtmlContent: htmlContent,
OriginalTemplate: toNullString(originalTemplate), OriginalTemplate: toNullString(originalTemplate),
Type: contentType,
LastEditedBy: lastEditedBy, LastEditedBy: lastEditedBy,
}) })
if err != nil { if err != nil {
@@ -223,7 +218,6 @@ func (c *DatabaseClient) CreateContent(siteID, contentID, htmlContent, originalT
SiteID: content.SiteID, SiteID: content.SiteID,
HTMLContent: content.HtmlContent, HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate), OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
Type: content.Type,
LastEditedBy: content.LastEditedBy, LastEditedBy: content.LastEditedBy,
}, nil }, nil

View File

@@ -20,14 +20,14 @@ func NewMockClient() *MockClient {
ID: "navbar-logo-2b10ad", ID: "navbar-logo-2b10ad",
SiteID: "demo", SiteID: "demo",
HTMLContent: "Acme Consulting Solutions", HTMLContent: "Acme Consulting Solutions",
Type: "text",
UpdatedAt: time.Now().Format(time.RFC3339), UpdatedAt: time.Now().Format(time.RFC3339),
}, },
"navbar-logo-2b10ad-a44bad": { "navbar-logo-2b10ad-a44bad": {
ID: "navbar-logo-2b10ad-a44bad", ID: "navbar-logo-2b10ad-a44bad",
SiteID: "demo", SiteID: "demo",
HTMLContent: "Acme Business Advisors", HTMLContent: "Acme Business Advisors",
Type: "text",
UpdatedAt: time.Now().Format(time.RFC3339), UpdatedAt: time.Now().Format(time.RFC3339),
}, },
@@ -36,21 +36,21 @@ func NewMockClient() *MockClient {
ID: "hero-title-7cfeea", ID: "hero-title-7cfeea",
SiteID: "demo", SiteID: "demo",
HTMLContent: "Transform Your Business with Strategic Expertise", HTMLContent: "Transform Your Business with Strategic Expertise",
Type: "text",
UpdatedAt: time.Now().Format(time.RFC3339), UpdatedAt: time.Now().Format(time.RFC3339),
}, },
"hero-lead-e47475": { "hero-lead-e47475": {
ID: "hero-lead-e47475", ID: "hero-lead-e47475",
SiteID: "demo", SiteID: "demo",
HTMLContent: "We help <strong>ambitious businesses</strong> grow through strategic planning, process optimization, and digital transformation. Our team brings 20+ years of experience to accelerate your success.", HTMLContent: "We help <strong>ambitious businesses</strong> 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), UpdatedAt: time.Now().Format(time.RFC3339),
}, },
"hero-link-76c620": { "hero-link-76c620": {
ID: "hero-link-76c620", ID: "hero-link-76c620",
SiteID: "demo", SiteID: "demo",
HTMLContent: "Schedule Free Consultation", HTMLContent: "Schedule Free Consultation",
Type: "link",
UpdatedAt: time.Now().Format(time.RFC3339), UpdatedAt: time.Now().Format(time.RFC3339),
}, },
@@ -59,14 +59,14 @@ func NewMockClient() *MockClient {
ID: "hero-title-c70343", ID: "hero-title-c70343",
SiteID: "demo", SiteID: "demo",
HTMLContent: "About Our Consulting Expertise", HTMLContent: "About Our Consulting Expertise",
Type: "text",
UpdatedAt: time.Now().Format(time.RFC3339), UpdatedAt: time.Now().Format(time.RFC3339),
}, },
"hero-lead-673026": { "hero-lead-673026": {
ID: "hero-lead-673026", ID: "hero-lead-673026",
SiteID: "demo", SiteID: "demo",
HTMLContent: "We're a team of <strong>experienced consultants</strong> dedicated to helping small businesses thrive in today's competitive marketplace through proven strategies.", HTMLContent: "We're a team of <strong>experienced consultants</strong> dedicated to helping small businesses thrive in today's competitive marketplace through proven strategies.",
Type: "text",
UpdatedAt: time.Now().Format(time.RFC3339), UpdatedAt: time.Now().Format(time.RFC3339),
}, },
@@ -75,14 +75,14 @@ func NewMockClient() *MockClient {
ID: "services-subtitle-c8927c", ID: "services-subtitle-c8927c",
SiteID: "demo", SiteID: "demo",
HTMLContent: "Our Story", HTMLContent: "Our Story",
Type: "text",
UpdatedAt: time.Now().Format(time.RFC3339), UpdatedAt: time.Now().Format(time.RFC3339),
}, },
"services-text-0d96da": { "services-text-0d96da": {
ID: "services-text-0d96da", ID: "services-text-0d96da",
SiteID: "demo", SiteID: "demo",
HTMLContent: "<strong>Founded in 2020</strong>, 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.", HTMLContent: "<strong>Founded in 2020</strong>, 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), UpdatedAt: time.Now().Format(time.RFC3339),
}, },
@@ -91,7 +91,7 @@ func NewMockClient() *MockClient {
ID: "default", ID: "default",
SiteID: "demo", SiteID: "demo",
HTMLContent: "[Enhanced Content]", HTMLContent: "[Enhanced Content]",
Type: "text",
UpdatedAt: time.Now().Format(time.RFC3339), UpdatedAt: time.Now().Format(time.RFC3339),
}, },
} }
@@ -140,14 +140,13 @@ func (m *MockClient) GetAllContent(siteID string) (map[string]engine.ContentItem
} }
// CreateContent creates a new mock content item // CreateContent creates a new mock content item
func (m *MockClient) CreateContent(siteID, contentID, htmlContent, originalTemplate, contentType, lastEditedBy string) (*engine.ContentItem, error) { func (m *MockClient) CreateContent(siteID, contentID, htmlContent, originalTemplate, lastEditedBy string) (*engine.ContentItem, error) {
// For mock client, just create and store the item // For mock client, just create and store the item
item := engine.ContentItem{ item := engine.ContentItem{
ID: contentID, ID: contentID,
SiteID: siteID, SiteID: siteID,
HTMLContent: htmlContent, HTMLContent: htmlContent,
OriginalTemplate: originalTemplate, OriginalTemplate: originalTemplate,
Type: contentType,
UpdatedAt: time.Now().Format(time.RFC3339), UpdatedAt: time.Now().Format(time.RFC3339),
LastEditedBy: lastEditedBy, LastEditedBy: lastEditedBy,
} }

View File

@@ -12,9 +12,9 @@ import (
) )
const createContent = `-- name: CreateContent :one const createContent = `-- name: CreateContent :one
INSERT INTO content (id, site_id, html_content, original_template, type, last_edited_by) INSERT INTO content (id, site_id, html_content, original_template, last_edited_by)
VALUES ($1, $2, $3, $4, $5, $6) VALUES ($1, $2, $3, $4, $5)
RETURNING id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by RETURNING id, site_id, html_content, original_template, created_at, updated_at, last_edited_by
` `
type CreateContentParams struct { type CreateContentParams struct {
@@ -22,7 +22,6 @@ type CreateContentParams struct {
SiteID string `json:"site_id"` SiteID string `json:"site_id"`
HtmlContent string `json:"html_content"` HtmlContent string `json:"html_content"`
OriginalTemplate sql.NullString `json:"original_template"` OriginalTemplate sql.NullString `json:"original_template"`
Type string `json:"type"`
LastEditedBy string `json:"last_edited_by"` LastEditedBy string `json:"last_edited_by"`
} }
@@ -32,7 +31,6 @@ func (q *Queries) CreateContent(ctx context.Context, arg CreateContentParams) (C
arg.SiteID, arg.SiteID,
arg.HtmlContent, arg.HtmlContent,
arg.OriginalTemplate, arg.OriginalTemplate,
arg.Type,
arg.LastEditedBy, arg.LastEditedBy,
) )
var i Content var i Content
@@ -41,7 +39,6 @@ func (q *Queries) CreateContent(ctx context.Context, arg CreateContentParams) (C
&i.SiteID, &i.SiteID,
&i.HtmlContent, &i.HtmlContent,
&i.OriginalTemplate, &i.OriginalTemplate,
&i.Type,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.LastEditedBy, &i.LastEditedBy,
@@ -75,7 +72,7 @@ func (q *Queries) DeleteContent(ctx context.Context, arg DeleteContentParams) er
} }
const getAllContent = `-- name: GetAllContent :many const getAllContent = `-- name: GetAllContent :many
SELECT id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by SELECT id, site_id, html_content, original_template, created_at, updated_at, last_edited_by
FROM content FROM content
WHERE site_id = $1 WHERE site_id = $1
ORDER BY updated_at DESC ORDER BY updated_at DESC
@@ -95,7 +92,6 @@ func (q *Queries) GetAllContent(ctx context.Context, siteID string) ([]Content,
&i.SiteID, &i.SiteID,
&i.HtmlContent, &i.HtmlContent,
&i.OriginalTemplate, &i.OriginalTemplate,
&i.Type,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.LastEditedBy, &i.LastEditedBy,
@@ -114,7 +110,7 @@ func (q *Queries) GetAllContent(ctx context.Context, siteID string) ([]Content,
} }
const getBulkContent = `-- name: GetBulkContent :many const getBulkContent = `-- name: GetBulkContent :many
SELECT id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by SELECT id, site_id, html_content, original_template, created_at, updated_at, last_edited_by
FROM content FROM content
WHERE site_id = $1 AND id IN ($2) WHERE site_id = $1 AND id IN ($2)
` `
@@ -149,7 +145,6 @@ func (q *Queries) GetBulkContent(ctx context.Context, arg GetBulkContentParams)
&i.SiteID, &i.SiteID,
&i.HtmlContent, &i.HtmlContent,
&i.OriginalTemplate, &i.OriginalTemplate,
&i.Type,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.LastEditedBy, &i.LastEditedBy,
@@ -168,7 +163,7 @@ func (q *Queries) GetBulkContent(ctx context.Context, arg GetBulkContentParams)
} }
const getContent = `-- name: GetContent :one const getContent = `-- name: GetContent :one
SELECT id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by SELECT id, site_id, html_content, original_template, created_at, updated_at, last_edited_by
FROM content FROM content
WHERE id = $1 AND site_id = $2 WHERE id = $1 AND site_id = $2
` `
@@ -186,7 +181,6 @@ func (q *Queries) GetContent(ctx context.Context, arg GetContentParams) (Content
&i.SiteID, &i.SiteID,
&i.HtmlContent, &i.HtmlContent,
&i.OriginalTemplate, &i.OriginalTemplate,
&i.Type,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.LastEditedBy, &i.LastEditedBy,
@@ -196,14 +190,13 @@ func (q *Queries) GetContent(ctx context.Context, arg GetContentParams) (Content
const updateContent = `-- name: UpdateContent :one const updateContent = `-- name: UpdateContent :one
UPDATE content UPDATE content
SET html_content = $1, type = $2, last_edited_by = $3 SET html_content = $1, last_edited_by = $2
WHERE id = $4 AND site_id = $5 WHERE id = $3 AND site_id = $4
RETURNING id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by RETURNING id, site_id, html_content, original_template, created_at, updated_at, last_edited_by
` `
type UpdateContentParams struct { type UpdateContentParams struct {
HtmlContent string `json:"html_content"` HtmlContent string `json:"html_content"`
Type string `json:"type"`
LastEditedBy string `json:"last_edited_by"` LastEditedBy string `json:"last_edited_by"`
ID string `json:"id"` ID string `json:"id"`
SiteID string `json:"site_id"` SiteID string `json:"site_id"`
@@ -212,7 +205,6 @@ type UpdateContentParams struct {
func (q *Queries) UpdateContent(ctx context.Context, arg UpdateContentParams) (Content, error) { func (q *Queries) UpdateContent(ctx context.Context, arg UpdateContentParams) (Content, error) {
row := q.db.QueryRowContext(ctx, updateContent, row := q.db.QueryRowContext(ctx, updateContent,
arg.HtmlContent, arg.HtmlContent,
arg.Type,
arg.LastEditedBy, arg.LastEditedBy,
arg.ID, arg.ID,
arg.SiteID, arg.SiteID,
@@ -223,7 +215,6 @@ func (q *Queries) UpdateContent(ctx context.Context, arg UpdateContentParams) (C
&i.SiteID, &i.SiteID,
&i.HtmlContent, &i.HtmlContent,
&i.OriginalTemplate, &i.OriginalTemplate,
&i.Type,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.LastEditedBy, &i.LastEditedBy,
@@ -232,13 +223,12 @@ func (q *Queries) UpdateContent(ctx context.Context, arg UpdateContentParams) (C
} }
const upsertContent = `-- name: UpsertContent :one const upsertContent = `-- name: UpsertContent :one
INSERT INTO content (id, site_id, html_content, original_template, type, last_edited_by) INSERT INTO content (id, site_id, html_content, original_template, last_edited_by)
VALUES ($1, $2, $3, $4, $5, $6) VALUES ($1, $2, $3, $4, $5)
ON CONFLICT(id, site_id) DO UPDATE SET ON CONFLICT(id, site_id) DO UPDATE SET
html_content = EXCLUDED.html_content, html_content = EXCLUDED.html_content,
type = EXCLUDED.type,
last_edited_by = EXCLUDED.last_edited_by last_edited_by = EXCLUDED.last_edited_by
RETURNING id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by RETURNING id, site_id, html_content, original_template, created_at, updated_at, last_edited_by
` `
type UpsertContentParams struct { type UpsertContentParams struct {
@@ -246,7 +236,6 @@ type UpsertContentParams struct {
SiteID string `json:"site_id"` SiteID string `json:"site_id"`
HtmlContent string `json:"html_content"` HtmlContent string `json:"html_content"`
OriginalTemplate sql.NullString `json:"original_template"` OriginalTemplate sql.NullString `json:"original_template"`
Type string `json:"type"`
LastEditedBy string `json:"last_edited_by"` LastEditedBy string `json:"last_edited_by"`
} }
@@ -256,7 +245,6 @@ func (q *Queries) UpsertContent(ctx context.Context, arg UpsertContentParams) (C
arg.SiteID, arg.SiteID,
arg.HtmlContent, arg.HtmlContent,
arg.OriginalTemplate, arg.OriginalTemplate,
arg.Type,
arg.LastEditedBy, arg.LastEditedBy,
) )
var i Content var i Content
@@ -265,7 +253,6 @@ func (q *Queries) UpsertContent(ctx context.Context, arg UpsertContentParams) (C
&i.SiteID, &i.SiteID,
&i.HtmlContent, &i.HtmlContent,
&i.OriginalTemplate, &i.OriginalTemplate,
&i.Type,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.LastEditedBy, &i.LastEditedBy,

View File

@@ -13,7 +13,6 @@ type Content struct {
SiteID string `json:"site_id"` SiteID string `json:"site_id"`
HtmlContent string `json:"html_content"` HtmlContent string `json:"html_content"`
OriginalTemplate sql.NullString `json:"original_template"` OriginalTemplate sql.NullString `json:"original_template"`
Type string `json:"type"`
CreatedAt int64 `json:"created_at"` CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"` UpdatedAt int64 `json:"updated_at"`
LastEditedBy string `json:"last_edited_by"` LastEditedBy string `json:"last_edited_by"`
@@ -25,7 +24,6 @@ type ContentVersion struct {
SiteID string `json:"site_id"` SiteID string `json:"site_id"`
HtmlContent string `json:"html_content"` HtmlContent string `json:"html_content"`
OriginalTemplate sql.NullString `json:"original_template"` OriginalTemplate sql.NullString `json:"original_template"`
Type string `json:"type"`
CreatedAt int64 `json:"created_at"` CreatedAt int64 `json:"created_at"`
CreatedBy string `json:"created_by"` CreatedBy string `json:"created_by"`
} }

View File

@@ -5,7 +5,6 @@ CREATE TABLE content (
site_id TEXT NOT NULL, site_id TEXT NOT NULL,
html_content TEXT NOT NULL, html_content TEXT NOT NULL,
original_template TEXT, original_template TEXT,
type TEXT NOT NULL,
created_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL, created_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL,
updated_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, last_edited_by TEXT DEFAULT 'system' NOT NULL,
@@ -19,7 +18,6 @@ CREATE TABLE content_versions (
site_id TEXT NOT NULL, site_id TEXT NOT NULL,
html_content TEXT NOT NULL, html_content TEXT NOT NULL,
original_template TEXT, original_template TEXT,
type TEXT NOT NULL,
created_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL, created_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL,
created_by TEXT DEFAULT 'system' NOT NULL created_by TEXT DEFAULT 'system' NOT NULL
); );

View File

@@ -4,7 +4,6 @@ CREATE TABLE IF NOT EXISTS content (
site_id TEXT NOT NULL, site_id TEXT NOT NULL,
html_content TEXT NOT NULL, html_content TEXT NOT NULL,
original_template TEXT, original_template TEXT,
type TEXT NOT NULL CHECK (type IN ('text', 'link')),
created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL, created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
updated_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, last_edited_by TEXT DEFAULT 'system' NOT NULL,
@@ -18,7 +17,6 @@ CREATE TABLE IF NOT EXISTS content_versions (
site_id TEXT NOT NULL, site_id TEXT NOT NULL,
html_content TEXT NOT NULL, html_content TEXT NOT NULL,
original_template TEXT, original_template TEXT,
type TEXT NOT NULL,
created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL, created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
created_by TEXT DEFAULT 'system' NOT NULL created_by TEXT DEFAULT 'system' NOT NULL
); );

View File

@@ -57,7 +57,6 @@ CREATE TABLE IF NOT EXISTS content (
site_id TEXT NOT NULL, site_id TEXT NOT NULL,
html_content TEXT NOT NULL, html_content TEXT NOT NULL,
original_template TEXT, original_template TEXT,
type TEXT NOT NULL CHECK (type IN ('text', 'link')),
created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL, created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
updated_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, last_edited_by TEXT DEFAULT 'system' NOT NULL,
@@ -77,7 +76,6 @@ CREATE TABLE IF NOT EXISTS content_versions (
site_id TEXT NOT NULL, site_id TEXT NOT NULL,
html_content TEXT NOT NULL, html_content TEXT NOT NULL,
original_template TEXT, original_template TEXT,
type TEXT NOT NULL,
created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL, created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
created_by TEXT DEFAULT 'system' NOT NULL created_by TEXT DEFAULT 'system' NOT NULL
) )

View File

@@ -11,8 +11,8 @@ import (
) )
const createContentVersion = `-- name: CreateContentVersion :exec const createContentVersion = `-- name: CreateContentVersion :exec
INSERT INTO content_versions (content_id, site_id, html_content, original_template, type, created_by) INSERT INTO content_versions (content_id, site_id, html_content, original_template, created_by)
VALUES ($1, $2, $3, $4, $5, $6) VALUES ($1, $2, $3, $4, $5)
` `
type CreateContentVersionParams struct { type CreateContentVersionParams struct {
@@ -20,7 +20,6 @@ type CreateContentVersionParams struct {
SiteID string `json:"site_id"` SiteID string `json:"site_id"`
HtmlContent string `json:"html_content"` HtmlContent string `json:"html_content"`
OriginalTemplate sql.NullString `json:"original_template"` OriginalTemplate sql.NullString `json:"original_template"`
Type string `json:"type"`
CreatedBy string `json:"created_by"` CreatedBy string `json:"created_by"`
} }
@@ -30,7 +29,6 @@ func (q *Queries) CreateContentVersion(ctx context.Context, arg CreateContentVer
arg.SiteID, arg.SiteID,
arg.HtmlContent, arg.HtmlContent,
arg.OriginalTemplate, arg.OriginalTemplate,
arg.Type,
arg.CreatedBy, arg.CreatedBy,
) )
return err return err
@@ -53,7 +51,7 @@ func (q *Queries) DeleteOldVersions(ctx context.Context, arg DeleteOldVersionsPa
const getAllVersionsForSite = `-- name: GetAllVersionsForSite :many const getAllVersionsForSite = `-- name: GetAllVersionsForSite :many
SELECT SELECT
cv.version_id, cv.content_id, cv.site_id, cv.html_content, cv.original_template, cv.type, cv.created_at, cv.created_by, cv.version_id, cv.content_id, cv.site_id, cv.html_content, cv.original_template, cv.created_at, cv.created_by,
c.html_content as current_html_content c.html_content as current_html_content
FROM content_versions cv FROM content_versions cv
LEFT JOIN content c ON cv.content_id = c.id AND cv.site_id = c.site_id LEFT JOIN content c ON cv.content_id = c.id AND cv.site_id = c.site_id
@@ -73,7 +71,6 @@ type GetAllVersionsForSiteRow struct {
SiteID string `json:"site_id"` SiteID string `json:"site_id"`
HtmlContent string `json:"html_content"` HtmlContent string `json:"html_content"`
OriginalTemplate sql.NullString `json:"original_template"` OriginalTemplate sql.NullString `json:"original_template"`
Type string `json:"type"`
CreatedAt int64 `json:"created_at"` CreatedAt int64 `json:"created_at"`
CreatedBy string `json:"created_by"` CreatedBy string `json:"created_by"`
CurrentHtmlContent sql.NullString `json:"current_html_content"` CurrentHtmlContent sql.NullString `json:"current_html_content"`
@@ -94,7 +91,6 @@ func (q *Queries) GetAllVersionsForSite(ctx context.Context, arg GetAllVersionsF
&i.SiteID, &i.SiteID,
&i.HtmlContent, &i.HtmlContent,
&i.OriginalTemplate, &i.OriginalTemplate,
&i.Type,
&i.CreatedAt, &i.CreatedAt,
&i.CreatedBy, &i.CreatedBy,
&i.CurrentHtmlContent, &i.CurrentHtmlContent,
@@ -113,7 +109,7 @@ func (q *Queries) GetAllVersionsForSite(ctx context.Context, arg GetAllVersionsF
} }
const getContentVersion = `-- name: GetContentVersion :one const getContentVersion = `-- name: GetContentVersion :one
SELECT version_id, content_id, site_id, html_content, original_template, type, created_at, created_by SELECT version_id, content_id, site_id, html_content, original_template, created_at, created_by
FROM content_versions FROM content_versions
WHERE version_id = $1 WHERE version_id = $1
` `
@@ -127,7 +123,6 @@ func (q *Queries) GetContentVersion(ctx context.Context, versionID int32) (Conte
&i.SiteID, &i.SiteID,
&i.HtmlContent, &i.HtmlContent,
&i.OriginalTemplate, &i.OriginalTemplate,
&i.Type,
&i.CreatedAt, &i.CreatedAt,
&i.CreatedBy, &i.CreatedBy,
) )
@@ -135,7 +130,7 @@ func (q *Queries) GetContentVersion(ctx context.Context, versionID int32) (Conte
} }
const getContentVersionHistory = `-- name: GetContentVersionHistory :many const getContentVersionHistory = `-- name: GetContentVersionHistory :many
SELECT version_id, content_id, site_id, html_content, original_template, type, created_at, created_by SELECT version_id, content_id, site_id, html_content, original_template, created_at, created_by
FROM content_versions FROM content_versions
WHERE content_id = $1 AND site_id = $2 WHERE content_id = $1 AND site_id = $2
ORDER BY created_at DESC ORDER BY created_at DESC
@@ -163,7 +158,6 @@ func (q *Queries) GetContentVersionHistory(ctx context.Context, arg GetContentVe
&i.SiteID, &i.SiteID,
&i.HtmlContent, &i.HtmlContent,
&i.OriginalTemplate, &i.OriginalTemplate,
&i.Type,
&i.CreatedAt, &i.CreatedAt,
&i.CreatedBy, &i.CreatedBy,
); err != nil { ); err != nil {

View File

@@ -1,38 +1,37 @@
-- name: GetContent :one -- name: GetContent :one
SELECT id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by SELECT id, site_id, html_content, original_template, created_at, updated_at, last_edited_by
FROM content FROM content
WHERE id = sqlc.arg(id) AND site_id = sqlc.arg(site_id); WHERE id = sqlc.arg(id) AND site_id = sqlc.arg(site_id);
-- name: GetAllContent :many -- name: GetAllContent :many
SELECT id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by SELECT id, site_id, html_content, original_template, created_at, updated_at, last_edited_by
FROM content FROM content
WHERE site_id = sqlc.arg(site_id) WHERE site_id = sqlc.arg(site_id)
ORDER BY updated_at DESC; ORDER BY updated_at DESC;
-- name: GetBulkContent :many -- name: GetBulkContent :many
SELECT id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by SELECT id, site_id, html_content, original_template, created_at, updated_at, last_edited_by
FROM content FROM content
WHERE site_id = sqlc.arg(site_id) AND id IN (sqlc.slice('ids')); WHERE site_id = sqlc.arg(site_id) AND id IN (sqlc.slice('ids'));
-- name: CreateContent :one -- name: CreateContent :one
INSERT INTO content (id, site_id, html_content, original_template, type, last_edited_by) INSERT INTO content (id, site_id, html_content, original_template, 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)) VALUES (sqlc.arg(id), sqlc.arg(site_id), sqlc.arg(html_content), sqlc.arg(original_template), sqlc.arg(last_edited_by))
RETURNING id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by; RETURNING id, site_id, html_content, original_template, created_at, updated_at, last_edited_by;
-- name: UpdateContent :one -- name: UpdateContent :one
UPDATE content UPDATE content
SET html_content = sqlc.arg(html_content), type = sqlc.arg(type), last_edited_by = sqlc.arg(last_edited_by) SET html_content = sqlc.arg(html_content), last_edited_by = sqlc.arg(last_edited_by)
WHERE id = sqlc.arg(id) AND site_id = sqlc.arg(site_id) WHERE id = sqlc.arg(id) AND site_id = sqlc.arg(site_id)
RETURNING id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by; RETURNING id, site_id, html_content, original_template, created_at, updated_at, last_edited_by;
-- name: UpsertContent :one -- name: UpsertContent :one
INSERT INTO content (id, site_id, html_content, original_template, type, last_edited_by) INSERT INTO content (id, site_id, html_content, original_template, 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)) VALUES (sqlc.arg(id), sqlc.arg(site_id), sqlc.arg(html_content), sqlc.arg(original_template), sqlc.arg(last_edited_by))
ON CONFLICT(id, site_id) DO UPDATE SET ON CONFLICT(id, site_id) DO UPDATE SET
html_content = EXCLUDED.html_content, html_content = EXCLUDED.html_content,
type = EXCLUDED.type,
last_edited_by = EXCLUDED.last_edited_by last_edited_by = EXCLUDED.last_edited_by
RETURNING id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by; RETURNING id, site_id, html_content, original_template, created_at, updated_at, last_edited_by;
-- name: DeleteContent :exec -- name: DeleteContent :exec
DELETE FROM content DELETE FROM content

View File

@@ -1,22 +1,22 @@
-- name: CreateContentVersion :exec -- name: CreateContentVersion :exec
INSERT INTO content_versions (content_id, site_id, html_content, original_template, type, created_by) INSERT INTO content_versions (content_id, site_id, html_content, original_template, 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)); VALUES (sqlc.arg(content_id), sqlc.arg(site_id), sqlc.arg(html_content), sqlc.arg(original_template), sqlc.arg(created_by));
-- name: GetContentVersionHistory :many -- name: GetContentVersionHistory :many
SELECT version_id, content_id, site_id, html_content, original_template, type, created_at, created_by SELECT version_id, content_id, site_id, html_content, original_template, created_at, created_by
FROM content_versions FROM content_versions
WHERE content_id = sqlc.arg(content_id) AND site_id = sqlc.arg(site_id) WHERE content_id = sqlc.arg(content_id) AND site_id = sqlc.arg(site_id)
ORDER BY created_at DESC ORDER BY created_at DESC
LIMIT sqlc.arg(limit_count); LIMIT sqlc.arg(limit_count);
-- name: GetContentVersion :one -- name: GetContentVersion :one
SELECT version_id, content_id, site_id, html_content, original_template, type, created_at, created_by SELECT version_id, content_id, site_id, html_content, original_template, created_at, created_by
FROM content_versions FROM content_versions
WHERE version_id = sqlc.arg(version_id); WHERE version_id = sqlc.arg(version_id);
-- name: GetAllVersionsForSite :many -- name: GetAllVersionsForSite :many
SELECT SELECT
cv.version_id, cv.content_id, cv.site_id, cv.html_content, cv.original_template, cv.type, cv.created_at, cv.created_by, cv.version_id, cv.content_id, cv.site_id, cv.html_content, cv.original_template, cv.created_at, cv.created_by,
c.html_content as current_html_content c.html_content as current_html_content
FROM content_versions cv FROM content_versions cv
LEFT JOIN content c ON cv.content_id = c.id AND cv.site_id = c.site_id LEFT JOIN content c ON cv.content_id = c.id AND cv.site_id = c.site_id

View File

@@ -12,9 +12,9 @@ import (
) )
const createContent = `-- name: CreateContent :one const createContent = `-- name: CreateContent :one
INSERT INTO content (id, site_id, html_content, original_template, type, last_edited_by) INSERT INTO content (id, site_id, html_content, original_template, last_edited_by)
VALUES (?1, ?2, ?3, ?4, ?5, ?6) VALUES (?1, ?2, ?3, ?4, ?5)
RETURNING id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by RETURNING id, site_id, html_content, original_template, created_at, updated_at, last_edited_by
` `
type CreateContentParams struct { type CreateContentParams struct {
@@ -22,7 +22,6 @@ type CreateContentParams struct {
SiteID string `json:"site_id"` SiteID string `json:"site_id"`
HtmlContent string `json:"html_content"` HtmlContent string `json:"html_content"`
OriginalTemplate sql.NullString `json:"original_template"` OriginalTemplate sql.NullString `json:"original_template"`
Type string `json:"type"`
LastEditedBy string `json:"last_edited_by"` LastEditedBy string `json:"last_edited_by"`
} }
@@ -32,7 +31,6 @@ func (q *Queries) CreateContent(ctx context.Context, arg CreateContentParams) (C
arg.SiteID, arg.SiteID,
arg.HtmlContent, arg.HtmlContent,
arg.OriginalTemplate, arg.OriginalTemplate,
arg.Type,
arg.LastEditedBy, arg.LastEditedBy,
) )
var i Content var i Content
@@ -41,7 +39,6 @@ func (q *Queries) CreateContent(ctx context.Context, arg CreateContentParams) (C
&i.SiteID, &i.SiteID,
&i.HtmlContent, &i.HtmlContent,
&i.OriginalTemplate, &i.OriginalTemplate,
&i.Type,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.LastEditedBy, &i.LastEditedBy,
@@ -75,7 +72,7 @@ func (q *Queries) DeleteContent(ctx context.Context, arg DeleteContentParams) er
} }
const getAllContent = `-- name: GetAllContent :many const getAllContent = `-- name: GetAllContent :many
SELECT id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by SELECT id, site_id, html_content, original_template, created_at, updated_at, last_edited_by
FROM content FROM content
WHERE site_id = ?1 WHERE site_id = ?1
ORDER BY updated_at DESC ORDER BY updated_at DESC
@@ -95,7 +92,6 @@ func (q *Queries) GetAllContent(ctx context.Context, siteID string) ([]Content,
&i.SiteID, &i.SiteID,
&i.HtmlContent, &i.HtmlContent,
&i.OriginalTemplate, &i.OriginalTemplate,
&i.Type,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.LastEditedBy, &i.LastEditedBy,
@@ -114,7 +110,7 @@ func (q *Queries) GetAllContent(ctx context.Context, siteID string) ([]Content,
} }
const getBulkContent = `-- name: GetBulkContent :many const getBulkContent = `-- name: GetBulkContent :many
SELECT id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by SELECT id, site_id, html_content, original_template, created_at, updated_at, last_edited_by
FROM content FROM content
WHERE site_id = ?1 AND id IN (/*SLICE:ids*/?) WHERE site_id = ?1 AND id IN (/*SLICE:ids*/?)
` `
@@ -149,7 +145,6 @@ func (q *Queries) GetBulkContent(ctx context.Context, arg GetBulkContentParams)
&i.SiteID, &i.SiteID,
&i.HtmlContent, &i.HtmlContent,
&i.OriginalTemplate, &i.OriginalTemplate,
&i.Type,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.LastEditedBy, &i.LastEditedBy,
@@ -168,7 +163,7 @@ func (q *Queries) GetBulkContent(ctx context.Context, arg GetBulkContentParams)
} }
const getContent = `-- name: GetContent :one const getContent = `-- name: GetContent :one
SELECT id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by SELECT id, site_id, html_content, original_template, created_at, updated_at, last_edited_by
FROM content FROM content
WHERE id = ?1 AND site_id = ?2 WHERE id = ?1 AND site_id = ?2
` `
@@ -186,7 +181,6 @@ func (q *Queries) GetContent(ctx context.Context, arg GetContentParams) (Content
&i.SiteID, &i.SiteID,
&i.HtmlContent, &i.HtmlContent,
&i.OriginalTemplate, &i.OriginalTemplate,
&i.Type,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.LastEditedBy, &i.LastEditedBy,
@@ -196,14 +190,13 @@ func (q *Queries) GetContent(ctx context.Context, arg GetContentParams) (Content
const updateContent = `-- name: UpdateContent :one const updateContent = `-- name: UpdateContent :one
UPDATE content UPDATE content
SET html_content = ?1, type = ?2, last_edited_by = ?3 SET html_content = ?1, last_edited_by = ?2
WHERE id = ?4 AND site_id = ?5 WHERE id = ?3 AND site_id = ?4
RETURNING id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by RETURNING id, site_id, html_content, original_template, created_at, updated_at, last_edited_by
` `
type UpdateContentParams struct { type UpdateContentParams struct {
HtmlContent string `json:"html_content"` HtmlContent string `json:"html_content"`
Type string `json:"type"`
LastEditedBy string `json:"last_edited_by"` LastEditedBy string `json:"last_edited_by"`
ID string `json:"id"` ID string `json:"id"`
SiteID string `json:"site_id"` SiteID string `json:"site_id"`
@@ -212,7 +205,6 @@ type UpdateContentParams struct {
func (q *Queries) UpdateContent(ctx context.Context, arg UpdateContentParams) (Content, error) { func (q *Queries) UpdateContent(ctx context.Context, arg UpdateContentParams) (Content, error) {
row := q.db.QueryRowContext(ctx, updateContent, row := q.db.QueryRowContext(ctx, updateContent,
arg.HtmlContent, arg.HtmlContent,
arg.Type,
arg.LastEditedBy, arg.LastEditedBy,
arg.ID, arg.ID,
arg.SiteID, arg.SiteID,
@@ -223,7 +215,6 @@ func (q *Queries) UpdateContent(ctx context.Context, arg UpdateContentParams) (C
&i.SiteID, &i.SiteID,
&i.HtmlContent, &i.HtmlContent,
&i.OriginalTemplate, &i.OriginalTemplate,
&i.Type,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.LastEditedBy, &i.LastEditedBy,
@@ -232,13 +223,12 @@ func (q *Queries) UpdateContent(ctx context.Context, arg UpdateContentParams) (C
} }
const upsertContent = `-- name: UpsertContent :one const upsertContent = `-- name: UpsertContent :one
INSERT INTO content (id, site_id, html_content, original_template, type, last_edited_by) INSERT INTO content (id, site_id, html_content, original_template, last_edited_by)
VALUES (?1, ?2, ?3, ?4, ?5, ?6) VALUES (?1, ?2, ?3, ?4, ?5)
ON CONFLICT(id, site_id) DO UPDATE SET ON CONFLICT(id, site_id) DO UPDATE SET
html_content = EXCLUDED.html_content, html_content = EXCLUDED.html_content,
type = EXCLUDED.type,
last_edited_by = EXCLUDED.last_edited_by last_edited_by = EXCLUDED.last_edited_by
RETURNING id, site_id, html_content, original_template, type, created_at, updated_at, last_edited_by RETURNING id, site_id, html_content, original_template, created_at, updated_at, last_edited_by
` `
type UpsertContentParams struct { type UpsertContentParams struct {
@@ -246,7 +236,6 @@ type UpsertContentParams struct {
SiteID string `json:"site_id"` SiteID string `json:"site_id"`
HtmlContent string `json:"html_content"` HtmlContent string `json:"html_content"`
OriginalTemplate sql.NullString `json:"original_template"` OriginalTemplate sql.NullString `json:"original_template"`
Type string `json:"type"`
LastEditedBy string `json:"last_edited_by"` LastEditedBy string `json:"last_edited_by"`
} }
@@ -256,7 +245,6 @@ func (q *Queries) UpsertContent(ctx context.Context, arg UpsertContentParams) (C
arg.SiteID, arg.SiteID,
arg.HtmlContent, arg.HtmlContent,
arg.OriginalTemplate, arg.OriginalTemplate,
arg.Type,
arg.LastEditedBy, arg.LastEditedBy,
) )
var i Content var i Content
@@ -265,7 +253,6 @@ func (q *Queries) UpsertContent(ctx context.Context, arg UpsertContentParams) (C
&i.SiteID, &i.SiteID,
&i.HtmlContent, &i.HtmlContent,
&i.OriginalTemplate, &i.OriginalTemplate,
&i.Type,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.LastEditedBy, &i.LastEditedBy,

View File

@@ -13,7 +13,6 @@ type Content struct {
SiteID string `json:"site_id"` SiteID string `json:"site_id"`
HtmlContent string `json:"html_content"` HtmlContent string `json:"html_content"`
OriginalTemplate sql.NullString `json:"original_template"` OriginalTemplate sql.NullString `json:"original_template"`
Type string `json:"type"`
CreatedAt int64 `json:"created_at"` CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"` UpdatedAt int64 `json:"updated_at"`
LastEditedBy string `json:"last_edited_by"` LastEditedBy string `json:"last_edited_by"`
@@ -25,7 +24,6 @@ type ContentVersion struct {
SiteID string `json:"site_id"` SiteID string `json:"site_id"`
HtmlContent string `json:"html_content"` HtmlContent string `json:"html_content"`
OriginalTemplate sql.NullString `json:"original_template"` OriginalTemplate sql.NullString `json:"original_template"`
Type string `json:"type"`
CreatedAt int64 `json:"created_at"` CreatedAt int64 `json:"created_at"`
CreatedBy string `json:"created_by"` CreatedBy string `json:"created_by"`
} }

View File

@@ -5,7 +5,6 @@ CREATE TABLE content (
site_id TEXT NOT NULL, site_id TEXT NOT NULL,
html_content TEXT NOT NULL, html_content TEXT NOT NULL,
original_template TEXT, original_template TEXT,
type TEXT NOT NULL,
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL, created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
updated_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, last_edited_by TEXT DEFAULT 'system' NOT NULL,
@@ -19,7 +18,6 @@ CREATE TABLE content_versions (
site_id TEXT NOT NULL, site_id TEXT NOT NULL,
html_content TEXT NOT NULL, html_content TEXT NOT NULL,
original_template TEXT, original_template TEXT,
type TEXT NOT NULL,
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL, created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
created_by TEXT DEFAULT 'system' NOT NULL created_by TEXT DEFAULT 'system' NOT NULL
); );

View File

@@ -4,7 +4,6 @@ CREATE TABLE IF NOT EXISTS content (
site_id TEXT NOT NULL, site_id TEXT NOT NULL,
html_content TEXT NOT NULL, html_content TEXT NOT NULL,
original_template TEXT, original_template TEXT,
type TEXT NOT NULL CHECK (type IN ('text', 'link')),
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL, created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
updated_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, last_edited_by TEXT DEFAULT 'system' NOT NULL,
@@ -18,7 +17,6 @@ CREATE TABLE IF NOT EXISTS content_versions (
site_id TEXT NOT NULL, site_id TEXT NOT NULL,
html_content TEXT NOT NULL, html_content TEXT NOT NULL,
original_template TEXT, original_template TEXT,
type TEXT NOT NULL,
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL, created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
created_by TEXT DEFAULT 'system' NOT NULL created_by TEXT DEFAULT 'system' NOT NULL
); );

View File

@@ -15,7 +15,6 @@ CREATE TABLE IF NOT EXISTS content (
site_id TEXT NOT NULL, site_id TEXT NOT NULL,
html_content TEXT NOT NULL, html_content TEXT NOT NULL,
original_template TEXT, original_template TEXT,
type TEXT NOT NULL CHECK (type IN ('text', 'link')),
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL, created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
updated_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, last_edited_by TEXT DEFAULT 'system' NOT NULL,
@@ -35,7 +34,6 @@ CREATE TABLE IF NOT EXISTS content_versions (
site_id TEXT NOT NULL, site_id TEXT NOT NULL,
html_content TEXT NOT NULL, html_content TEXT NOT NULL,
original_template TEXT, original_template TEXT,
type TEXT NOT NULL,
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL, created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
created_by TEXT DEFAULT 'system' NOT NULL created_by TEXT DEFAULT 'system' NOT NULL
) )

View File

@@ -11,8 +11,8 @@ import (
) )
const createContentVersion = `-- name: CreateContentVersion :exec const createContentVersion = `-- name: CreateContentVersion :exec
INSERT INTO content_versions (content_id, site_id, html_content, original_template, type, created_by) INSERT INTO content_versions (content_id, site_id, html_content, original_template, created_by)
VALUES (?1, ?2, ?3, ?4, ?5, ?6) VALUES (?1, ?2, ?3, ?4, ?5)
` `
type CreateContentVersionParams struct { type CreateContentVersionParams struct {
@@ -20,7 +20,6 @@ type CreateContentVersionParams struct {
SiteID string `json:"site_id"` SiteID string `json:"site_id"`
HtmlContent string `json:"html_content"` HtmlContent string `json:"html_content"`
OriginalTemplate sql.NullString `json:"original_template"` OriginalTemplate sql.NullString `json:"original_template"`
Type string `json:"type"`
CreatedBy string `json:"created_by"` CreatedBy string `json:"created_by"`
} }
@@ -30,7 +29,6 @@ func (q *Queries) CreateContentVersion(ctx context.Context, arg CreateContentVer
arg.SiteID, arg.SiteID,
arg.HtmlContent, arg.HtmlContent,
arg.OriginalTemplate, arg.OriginalTemplate,
arg.Type,
arg.CreatedBy, arg.CreatedBy,
) )
return err return err
@@ -53,7 +51,7 @@ func (q *Queries) DeleteOldVersions(ctx context.Context, arg DeleteOldVersionsPa
const getAllVersionsForSite = `-- name: GetAllVersionsForSite :many const getAllVersionsForSite = `-- name: GetAllVersionsForSite :many
SELECT SELECT
cv.version_id, cv.content_id, cv.site_id, cv.html_content, cv.original_template, cv.type, cv.created_at, cv.created_by, cv.version_id, cv.content_id, cv.site_id, cv.html_content, cv.original_template, cv.created_at, cv.created_by,
c.html_content as current_html_content c.html_content as current_html_content
FROM content_versions cv FROM content_versions cv
LEFT JOIN content c ON cv.content_id = c.id AND cv.site_id = c.site_id LEFT JOIN content c ON cv.content_id = c.id AND cv.site_id = c.site_id
@@ -73,7 +71,6 @@ type GetAllVersionsForSiteRow struct {
SiteID string `json:"site_id"` SiteID string `json:"site_id"`
HtmlContent string `json:"html_content"` HtmlContent string `json:"html_content"`
OriginalTemplate sql.NullString `json:"original_template"` OriginalTemplate sql.NullString `json:"original_template"`
Type string `json:"type"`
CreatedAt int64 `json:"created_at"` CreatedAt int64 `json:"created_at"`
CreatedBy string `json:"created_by"` CreatedBy string `json:"created_by"`
CurrentHtmlContent sql.NullString `json:"current_html_content"` CurrentHtmlContent sql.NullString `json:"current_html_content"`
@@ -94,7 +91,6 @@ func (q *Queries) GetAllVersionsForSite(ctx context.Context, arg GetAllVersionsF
&i.SiteID, &i.SiteID,
&i.HtmlContent, &i.HtmlContent,
&i.OriginalTemplate, &i.OriginalTemplate,
&i.Type,
&i.CreatedAt, &i.CreatedAt,
&i.CreatedBy, &i.CreatedBy,
&i.CurrentHtmlContent, &i.CurrentHtmlContent,
@@ -113,7 +109,7 @@ func (q *Queries) GetAllVersionsForSite(ctx context.Context, arg GetAllVersionsF
} }
const getContentVersion = `-- name: GetContentVersion :one const getContentVersion = `-- name: GetContentVersion :one
SELECT version_id, content_id, site_id, html_content, original_template, type, created_at, created_by SELECT version_id, content_id, site_id, html_content, original_template, created_at, created_by
FROM content_versions FROM content_versions
WHERE version_id = ?1 WHERE version_id = ?1
` `
@@ -127,7 +123,6 @@ func (q *Queries) GetContentVersion(ctx context.Context, versionID int64) (Conte
&i.SiteID, &i.SiteID,
&i.HtmlContent, &i.HtmlContent,
&i.OriginalTemplate, &i.OriginalTemplate,
&i.Type,
&i.CreatedAt, &i.CreatedAt,
&i.CreatedBy, &i.CreatedBy,
) )
@@ -135,7 +130,7 @@ func (q *Queries) GetContentVersion(ctx context.Context, versionID int64) (Conte
} }
const getContentVersionHistory = `-- name: GetContentVersionHistory :many const getContentVersionHistory = `-- name: GetContentVersionHistory :many
SELECT version_id, content_id, site_id, html_content, original_template, type, created_at, created_by SELECT version_id, content_id, site_id, html_content, original_template, created_at, created_by
FROM content_versions FROM content_versions
WHERE content_id = ?1 AND site_id = ?2 WHERE content_id = ?1 AND site_id = ?2
ORDER BY created_at DESC ORDER BY created_at DESC
@@ -163,7 +158,6 @@ func (q *Queries) GetContentVersionHistory(ctx context.Context, arg GetContentVe
&i.SiteID, &i.SiteID,
&i.HtmlContent, &i.HtmlContent,
&i.OriginalTemplate, &i.OriginalTemplate,
&i.Type,
&i.CreatedAt, &i.CreatedAt,
&i.CreatedBy, &i.CreatedBy,
); err != nil { ); err != nil {

View File

@@ -46,7 +46,6 @@ func (c *DatabaseClient) GetContent(siteID, contentID string) (*ContentItem, err
SiteID: content.SiteID, SiteID: content.SiteID,
HTMLContent: content.HtmlContent, HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate), OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
Type: content.Type,
LastEditedBy: content.LastEditedBy, LastEditedBy: content.LastEditedBy,
}, nil }, nil
@@ -63,7 +62,6 @@ func (c *DatabaseClient) GetContent(siteID, contentID string) (*ContentItem, err
SiteID: content.SiteID, SiteID: content.SiteID,
HTMLContent: content.HtmlContent, HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate), OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
Type: content.Type,
LastEditedBy: content.LastEditedBy, LastEditedBy: content.LastEditedBy,
}, nil }, nil
@@ -91,7 +89,6 @@ func (c *DatabaseClient) GetBulkContent(siteID string, contentIDs []string) (map
SiteID: content.SiteID, SiteID: content.SiteID,
HTMLContent: content.HtmlContent, HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate), OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
Type: content.Type,
LastEditedBy: content.LastEditedBy, LastEditedBy: content.LastEditedBy,
} }
} }
@@ -113,7 +110,6 @@ func (c *DatabaseClient) GetBulkContent(siteID string, contentIDs []string) (map
SiteID: content.SiteID, SiteID: content.SiteID,
HTMLContent: content.HtmlContent, HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate), OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
Type: content.Type,
LastEditedBy: content.LastEditedBy, LastEditedBy: content.LastEditedBy,
} }
} }
@@ -140,7 +136,6 @@ func (c *DatabaseClient) GetAllContent(siteID string) (map[string]ContentItem, e
SiteID: content.SiteID, SiteID: content.SiteID,
HTMLContent: content.HtmlContent, HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate), OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
Type: content.Type,
LastEditedBy: content.LastEditedBy, LastEditedBy: content.LastEditedBy,
} }
} }
@@ -159,7 +154,6 @@ func (c *DatabaseClient) GetAllContent(siteID string) (map[string]ContentItem, e
SiteID: content.SiteID, SiteID: content.SiteID,
HTMLContent: content.HtmlContent, HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate), OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
Type: content.Type,
LastEditedBy: content.LastEditedBy, LastEditedBy: content.LastEditedBy,
} }
} }
@@ -171,7 +165,7 @@ func (c *DatabaseClient) GetAllContent(siteID string) (map[string]ContentItem, e
} }
// CreateContent creates a new content item // CreateContent creates a new content item
func (c *DatabaseClient) CreateContent(siteID, contentID, htmlContent, originalTemplate, contentType, lastEditedBy string) (*ContentItem, error) { func (c *DatabaseClient) CreateContent(siteID, contentID, htmlContent, originalTemplate, lastEditedBy string) (*ContentItem, error) {
switch c.database.GetDBType() { switch c.database.GetDBType() {
case "sqlite3": case "sqlite3":
content, err := c.database.GetSQLiteQueries().CreateContent(context.Background(), sqlite.CreateContentParams{ content, err := c.database.GetSQLiteQueries().CreateContent(context.Background(), sqlite.CreateContentParams{
@@ -179,7 +173,6 @@ func (c *DatabaseClient) CreateContent(siteID, contentID, htmlContent, originalT
SiteID: siteID, SiteID: siteID,
HtmlContent: htmlContent, HtmlContent: htmlContent,
OriginalTemplate: toNullString(originalTemplate), OriginalTemplate: toNullString(originalTemplate),
Type: contentType,
LastEditedBy: lastEditedBy, LastEditedBy: lastEditedBy,
}) })
if err != nil { if err != nil {
@@ -190,7 +183,6 @@ func (c *DatabaseClient) CreateContent(siteID, contentID, htmlContent, originalT
SiteID: content.SiteID, SiteID: content.SiteID,
HTMLContent: content.HtmlContent, HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate), OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
Type: content.Type,
LastEditedBy: content.LastEditedBy, LastEditedBy: content.LastEditedBy,
}, nil }, nil
@@ -200,7 +192,6 @@ func (c *DatabaseClient) CreateContent(siteID, contentID, htmlContent, originalT
SiteID: siteID, SiteID: siteID,
HtmlContent: htmlContent, HtmlContent: htmlContent,
OriginalTemplate: toNullString(originalTemplate), OriginalTemplate: toNullString(originalTemplate),
Type: contentType,
LastEditedBy: lastEditedBy, LastEditedBy: lastEditedBy,
}) })
if err != nil { if err != nil {
@@ -211,7 +202,6 @@ func (c *DatabaseClient) CreateContent(siteID, contentID, htmlContent, originalT
SiteID: content.SiteID, SiteID: content.SiteID,
HTMLContent: content.HtmlContent, HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate), OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
Type: content.Type,
LastEditedBy: content.LastEditedBy, LastEditedBy: content.LastEditedBy,
}, nil }, nil

View File

@@ -35,7 +35,6 @@ type ContentResult struct {
type ProcessedElement struct { type ProcessedElement struct {
Node *html.Node // HTML node Node *html.Node // HTML node
ID string // Generated content ID ID string // Generated content ID
Type string // Content type (text, link)
Content string // Injected content (if any) Content string // Injected content (if any)
Generated bool // Whether ID was generated (vs existing) Generated bool // Whether ID was generated (vs existing)
Tag string // Element tag name Tag string // Element tag name
@@ -48,7 +47,7 @@ type ContentClient interface {
GetContent(siteID, contentID string) (*ContentItem, error) GetContent(siteID, contentID string) (*ContentItem, error)
GetBulkContent(siteID string, contentIDs []string) (map[string]ContentItem, error) GetBulkContent(siteID string, contentIDs []string) (map[string]ContentItem, error)
GetAllContent(siteID string) (map[string]ContentItem, error) GetAllContent(siteID string) (map[string]ContentItem, error)
CreateContent(siteID, contentID, htmlContent, originalTemplate, contentType, lastEditedBy string) (*ContentItem, error) CreateContent(siteID, contentID, htmlContent, originalTemplate, lastEditedBy string) (*ContentItem, error)
} }
// ContentItem represents a piece of content from the database // ContentItem represents a piece of content from the database
@@ -57,7 +56,6 @@ type ContentItem struct {
SiteID string `json:"site_id"` SiteID string `json:"site_id"`
HTMLContent string `json:"html_content"` HTMLContent string `json:"html_content"`
OriginalTemplate string `json:"original_template"` OriginalTemplate string `json:"original_template"`
Type string `json:"type"`
UpdatedAt string `json:"updated_at"` UpdatedAt string `json:"updated_at"`
LastEditedBy string `json:"last_edited_by,omitempty"` LastEditedBy string `json:"last_edited_by,omitempty"`
} }

View File

@@ -179,40 +179,149 @@ func isContainer(node *html.Node) bool {
"main": true, "main": true,
"aside": true, "aside": true,
"nav": true, "nav": true,
"ul": true, // Phase 3: Lists are containers
"ol": true,
} }
return containerTags[node.Data] return containerTags[node.Data]
} }
// findViableChildren finds all child elements that are viable for editing // findViableChildren finds all descendant elements that should get .insertr class
// Phase 3: Recursive traversal with block/inline classification and boundary respect
func findViableChildren(node *html.Node) []*html.Node { func findViableChildren(node *html.Node) []*html.Node {
var viable []*html.Node var viable []*html.Node
traverseForViableElements(node, &viable)
return viable
}
// traverseForViableElements recursively traverses all descendants, stopping at .insertr boundaries
func traverseForViableElements(node *html.Node, viable *[]*html.Node) {
for child := node.FirstChild; child != nil; child = child.NextSibling { for child := node.FirstChild; child != nil; child = child.NextSibling {
// Skip whitespace-only text nodes
if child.Type == html.TextNode {
if strings.TrimSpace(child.Data) == "" {
continue
}
}
// Only consider element nodes // Only consider element nodes
if child.Type != html.ElementNode { if child.Type != html.ElementNode {
continue continue
} }
// Skip self-closing elements for now // BOUNDARY: Stop if element already has .insertr class
if isSelfClosing(child) { if hasInsertrClass(child) {
continue continue
} }
// Check if element has editable content (improved logic) // Skip deferred complex elements (tables, forms)
if hasEditableContent(child) { if isDeferredElement(child) {
viable = append(viable, child) continue
}
// Determine if this element should get .insertr
if shouldGetInsertrClass(child) {
*viable = append(*viable, child)
// Don't traverse children - they're handled by this element's expansion
continue
}
// Continue traversing if this is just a container
traverseForViableElements(child, viable)
} }
} }
return viable // Phase 3: Block vs Inline element classification
func isBlockElement(node *html.Node) bool {
blockTags := map[string]bool{
// Content blocks
"h1": true, "h2": true, "h3": true, "h4": true, "h5": true, "h6": true,
"p": true, "div": true, "article": true, "section": true, "nav": true,
"header": true, "footer": true, "main": true, "aside": true,
// Lists
"ul": true, "ol": true, "li": true,
// Interactive (when at block level)
"button": true, "a": true, "img": true, "video": true, "audio": true,
}
return blockTags[node.Data]
}
// isInlineElement checks if element is inline formatting (never gets .insertr)
func isInlineElement(node *html.Node) bool {
inlineTags := map[string]bool{
"strong": true, "b": true, "em": true, "i": true, "span": true,
"code": true, "small": true, "sub": true, "sup": true, "br": true,
"mark": true, "kbd": true,
}
return inlineTags[node.Data]
}
// isContextSensitive checks if element can be block or inline (a, button)
func isContextSensitive(node *html.Node) bool {
contextTags := map[string]bool{
"a": true,
"button": true,
}
return contextTags[node.Data]
}
// isInBlockContext determines if context-sensitive element should be treated as block
func isInBlockContext(node *html.Node) bool {
parent := node.Parent
if parent == nil || parent.Type != html.ElementNode {
return true
}
// If parent is a content element, this is inline formatting
contentElements := map[string]bool{
"p": true, "h1": true, "h2": true, "h3": true, "h4": true, "h5": true, "h6": true,
"li": true, "td": true, "th": true,
}
return !contentElements[parent.Data]
}
// shouldGetInsertrClass determines if element should receive .insertr class
func shouldGetInsertrClass(node *html.Node) bool {
// Always block elements get .insertr
if isBlockElement(node) && !isContextSensitive(node) {
return true
}
// Context-sensitive elements depend on parent context
if isContextSensitive(node) {
return isInBlockContext(node)
}
// Inline elements never get .insertr
if isInlineElement(node) {
return false
}
// Self-closing elements - only img gets .insertr when block-level
if isSelfClosing(node) {
return node.Data == "img" && isInBlockContext(node)
}
return false
}
// isDeferredElement checks for complex elements that need separate planning
func isDeferredElement(node *html.Node) bool {
deferredTags := map[string]bool{
"table": true, "tr": true, "td": true, "th": true,
"thead": true, "tbody": true, "tfoot": true,
"form": true, "input": true, "textarea": true, "select": true, "option": true,
}
return deferredTags[node.Data]
}
// hasInsertrClass checks if node has class="insertr"
func hasInsertrClass(node *html.Node) bool {
classes := GetClasses(node)
for _, class := range classes {
if class == "insertr" {
return true
}
}
return false
} }
// findViableChildrenLegacy uses the old text-only logic for backwards compatibility // findViableChildrenLegacy uses the old text-only logic for backwards compatibility