Implement complete collection persistence with database-backed survival across server restarts

• Add full multi-table schema for collections with normalized design (collections, collection_templates, collection_items, collection_item_versions)
• Implement collection detection and processing in enhancement pipeline for .insertr-add elements
• Add template extraction and storage from existing HTML children with multi-variant support
• Enable collection reconstruction from database on server restart with proper DOM rebuilding
• Extend ContentClient interface with collection operations and full database integration
• Update enhance command to use engine.DatabaseClient for collection persistence support
This commit is contained in:
2025-09-22 18:29:58 +02:00
parent b25663f76b
commit 2315ba4750
36 changed files with 4356 additions and 46 deletions

View File

@@ -108,11 +108,36 @@ func (db *Database) initializeSQLiteSchema() error {
return fmt.Errorf("failed to create content_versions table: %w", err)
}
// Create collection tables
if err := db.sqliteQueries.InitializeCollectionsTable(ctx); err != nil {
return fmt.Errorf("failed to create collections table: %w", err)
}
if err := db.sqliteQueries.InitializeCollectionTemplatesTable(ctx); err != nil {
return fmt.Errorf("failed to create collection_templates table: %w", err)
}
if err := db.sqliteQueries.InitializeCollectionItemsTable(ctx); err != nil {
return fmt.Errorf("failed to create collection_items table: %w", err)
}
if err := db.sqliteQueries.InitializeCollectionItemVersionsTable(ctx); err != nil {
return fmt.Errorf("failed to create collection_item_versions table: %w", err)
}
// Create indexes manually (sqlc doesn't generate CREATE INDEX functions for SQLite)
indexQueries := []string{
"CREATE INDEX IF NOT EXISTS idx_content_site_id ON content(site_id);",
"CREATE INDEX IF NOT EXISTS idx_content_updated_at ON content(updated_at);",
"CREATE INDEX IF NOT EXISTS idx_content_versions_lookup ON content_versions(content_id, site_id, created_at DESC);",
"CREATE INDEX IF NOT EXISTS idx_collections_site_id ON collections(site_id);",
"CREATE INDEX IF NOT EXISTS idx_collections_updated_at ON collections(updated_at);",
"CREATE INDEX IF NOT EXISTS idx_collection_templates_lookup ON collection_templates(collection_id, site_id);",
"CREATE INDEX IF NOT EXISTS idx_collection_templates_default ON collection_templates(collection_id, site_id, is_default DESC);",
"CREATE INDEX IF NOT EXISTS idx_collection_items_lookup ON collection_items(collection_id, site_id, position);",
"CREATE INDEX IF NOT EXISTS idx_collection_items_template ON collection_items(template_id);",
"CREATE INDEX IF NOT EXISTS idx_collection_item_versions_lookup ON collection_item_versions(item_id, collection_id, site_id, created_at DESC);",
"CREATE UNIQUE INDEX IF NOT EXISTS idx_collection_templates_one_default ON collection_templates(collection_id, site_id) WHERE is_default = 1;",
}
for _, query := range indexQueries {
@@ -121,17 +146,32 @@ func (db *Database) initializeSQLiteSchema() error {
}
}
// Create update trigger manually (sqlc doesn't generate trigger creation functions)
triggerQuery := `
CREATE TRIGGER IF NOT EXISTS update_content_updated_at
AFTER UPDATE ON content
FOR EACH ROW
BEGIN
UPDATE content SET updated_at = strftime('%s', 'now') WHERE id = NEW.id AND site_id = NEW.site_id;
END;`
// Create update triggers manually (sqlc doesn't generate trigger creation functions)
triggerQueries := []string{
`CREATE TRIGGER IF NOT EXISTS update_content_updated_at
AFTER UPDATE ON content
FOR EACH ROW
BEGIN
UPDATE content SET updated_at = strftime('%s', 'now') WHERE id = NEW.id AND site_id = NEW.site_id;
END;`,
`CREATE TRIGGER IF NOT EXISTS update_collections_updated_at
AFTER UPDATE ON collections
FOR EACH ROW
BEGIN
UPDATE collections SET updated_at = strftime('%s', 'now') WHERE id = NEW.id AND site_id = NEW.site_id;
END;`,
`CREATE TRIGGER IF NOT EXISTS update_collection_items_updated_at
AFTER UPDATE ON collection_items
FOR EACH ROW
BEGIN
UPDATE collection_items SET updated_at = strftime('%s', 'now') WHERE item_id = NEW.item_id AND collection_id = NEW.collection_id AND site_id = NEW.site_id;
END;`,
}
if _, err := db.conn.Exec(triggerQuery); err != nil {
return fmt.Errorf("failed to create update trigger: %w", err)
for _, query := range triggerQueries {
if _, err := db.conn.Exec(query); err != nil {
return fmt.Errorf("failed to create trigger: %w", err)
}
}
return nil
@@ -150,6 +190,23 @@ func (db *Database) initializePostgreSQLSchema() error {
return fmt.Errorf("failed to create content_versions table: %w", err)
}
// Create collection tables
if err := db.postgresqlQueries.InitializeCollectionsTable(ctx); err != nil {
return fmt.Errorf("failed to create collections table: %w", err)
}
if err := db.postgresqlQueries.InitializeCollectionTemplatesTable(ctx); err != nil {
return fmt.Errorf("failed to create collection_templates table: %w", err)
}
if err := db.postgresqlQueries.InitializeCollectionItemsTable(ctx); err != nil {
return fmt.Errorf("failed to create collection_items table: %w", err)
}
if err := db.postgresqlQueries.InitializeCollectionItemVersionsTable(ctx); err != nil {
return fmt.Errorf("failed to create collection_item_versions table: %w", err)
}
// Create indexes using sqlc-generated functions (PostgreSQL supports this)
if err := db.postgresqlQueries.CreateContentSiteIndex(ctx); err != nil {
return fmt.Errorf("failed to create content site index: %w", err)
@@ -163,21 +220,67 @@ func (db *Database) initializePostgreSQLSchema() error {
return fmt.Errorf("failed to create versions lookup index: %w", err)
}
// Create collection indexes using sqlc-generated functions
if err := db.postgresqlQueries.CreateCollectionsSiteIndex(ctx); err != nil {
return fmt.Errorf("failed to create collections site index: %w", err)
}
if err := db.postgresqlQueries.CreateCollectionsUpdatedAtIndex(ctx); err != nil {
return fmt.Errorf("failed to create collections updated_at index: %w", err)
}
if err := db.postgresqlQueries.CreateCollectionTemplatesLookupIndex(ctx); err != nil {
return fmt.Errorf("failed to create collection templates lookup index: %w", err)
}
if err := db.postgresqlQueries.CreateCollectionTemplatesDefaultIndex(ctx); err != nil {
return fmt.Errorf("failed to create collection templates default index: %w", err)
}
if err := db.postgresqlQueries.CreateCollectionItemsLookupIndex(ctx); err != nil {
return fmt.Errorf("failed to create collection items lookup index: %w", err)
}
if err := db.postgresqlQueries.CreateCollectionItemsTemplateIndex(ctx); err != nil {
return fmt.Errorf("failed to create collection items template index: %w", err)
}
if err := db.postgresqlQueries.CreateCollectionItemVersionsLookupIndex(ctx); err != nil {
return fmt.Errorf("failed to create collection item versions lookup index: %w", err)
}
if err := db.postgresqlQueries.CreateCollectionTemplatesOneDefaultIndex(ctx); err != nil {
return fmt.Errorf("failed to create collection templates one default constraint: %w", err)
}
// Create update function using sqlc-generated function
if err := db.postgresqlQueries.CreateUpdateFunction(ctx); err != nil {
return fmt.Errorf("failed to create update function: %w", err)
}
// Create trigger manually (sqlc doesn't generate trigger creation functions)
triggerQuery := `
DROP TRIGGER IF EXISTS update_content_updated_at ON content;
// Create triggers manually (sqlc doesn't generate trigger creation functions)
triggerQueries := []string{
`DROP TRIGGER IF EXISTS update_content_updated_at ON content;
CREATE TRIGGER update_content_updated_at
BEFORE UPDATE ON content
FOR EACH ROW
EXECUTE FUNCTION update_content_timestamp();`
EXECUTE FUNCTION update_content_timestamp();`,
`DROP TRIGGER IF EXISTS update_collections_updated_at ON collections;
CREATE TRIGGER update_collections_updated_at
BEFORE UPDATE ON collections
FOR EACH ROW
EXECUTE FUNCTION update_content_timestamp();`,
`DROP TRIGGER IF EXISTS update_collection_items_updated_at ON collection_items;
CREATE TRIGGER update_collection_items_updated_at
BEFORE UPDATE ON collection_items
FOR EACH ROW
EXECUTE FUNCTION update_content_timestamp();`,
}
if _, err := db.conn.Exec(triggerQuery); err != nil {
return fmt.Errorf("failed to create update trigger: %w", err)
for _, query := range triggerQueries {
if _, err := db.conn.Exec(query); err != nil {
return fmt.Errorf("failed to create trigger: %w", err)
}
}
return nil