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:
@@ -102,7 +102,7 @@ func runEnhance(cmd *cobra.Command, args []string) {
|
|||||||
log.Fatalf("Failed to initialize database: %v", err)
|
log.Fatalf("Failed to initialize database: %v", err)
|
||||||
}
|
}
|
||||||
defer database.Close()
|
defer database.Close()
|
||||||
client = content.NewDatabaseClient(database)
|
client = engine.NewDatabaseClient(database)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("🧪 No database or API configured, using mock content\n")
|
fmt.Printf("🧪 No database or API configured, using mock content\n")
|
||||||
client = content.NewMockClient()
|
client = content.NewMockClient()
|
||||||
|
|||||||
@@ -95,6 +95,53 @@
|
|||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
margin: 0 0 0.5rem 0;
|
margin: 0 0 0.5rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Testimonials styling for .insertr-add demo */
|
||||||
|
.testimonials {
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testimonial-item {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testimonial-item blockquote {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #374151;
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
quotes: """ """;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testimonial-item blockquote:before {
|
||||||
|
content: open-quote;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testimonial-item blockquote:after {
|
||||||
|
content: close-quote;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testimonial-item cite {
|
||||||
|
display: block;
|
||||||
|
text-align: right;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #6b7280;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testimonial-item cite:before {
|
||||||
|
content: "— ";
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -166,5 +213,33 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="insertr">Need help? Contact our <a id="support-link" class="fancy" href="mailto:support@example.com" title="Email our support team">support team</a> anytime.</p>
|
<p class="insertr">Need help? Contact our <a id="support-link" class="fancy" href="mailto:support@example.com" title="Email our support team">support team</a> anytime.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Example 8: Collection Management (.insertr-add) -->
|
||||||
|
<div class="example">
|
||||||
|
<h3>Example 8: Dynamic Collection (.insertr-add)</h3>
|
||||||
|
<div class="example-description">
|
||||||
|
Tests dynamic add/remove/reorder functionality. In edit mode, you should see "+ Add Item" button and item controls.
|
||||||
|
</div>
|
||||||
|
<div class="testimonials insertr-add">
|
||||||
|
<div class="testimonial-item">
|
||||||
|
<blockquote class="insertr">Not all that is gold does glitter</blockquote>
|
||||||
|
<cite class="insertr">Tolkien</cite>
|
||||||
|
</div>
|
||||||
|
<div class="testimonial-item">
|
||||||
|
<blockquote class="insertr">The journey of a thousand miles begins with one step</blockquote>
|
||||||
|
<cite class="insertr">Lao Tzu</cite>
|
||||||
|
</div>
|
||||||
|
<div class="testimonial-item">
|
||||||
|
<blockquote class="insertr">Innovation distinguishes between a leader and a follower</blockquote>
|
||||||
|
<cite class="insertr">Steve Jobs</cite>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add login gate for testing -->
|
||||||
|
<footer style="margin-top: 3rem; padding: 2rem; border-top: 1px solid #e5e7eb; text-align: center;">
|
||||||
|
<p>© 2024 Insertr Demo Site</p>
|
||||||
|
<a href="#" class="insertr-gate" style="color: #6b7280; text-decoration: none; font-size: 0.9rem;">Admin Login</a>
|
||||||
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -828,3 +828,129 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
|
|||||||
order: -1;
|
order: -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
COLLECTION MANAGEMENT STYLES (.insertr-add)
|
||||||
|
================================================================= */
|
||||||
|
|
||||||
|
/* Collection container when active */
|
||||||
|
.insertr-collection-active {
|
||||||
|
outline: 2px dashed var(--insertr-primary);
|
||||||
|
outline-offset: 4px;
|
||||||
|
border-radius: var(--insertr-border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add button positioned in top right of container */
|
||||||
|
.insertr-add-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: -12px;
|
||||||
|
right: -12px;
|
||||||
|
background: var(--insertr-primary);
|
||||||
|
color: var(--insertr-text-inverse);
|
||||||
|
border: none;
|
||||||
|
padding: var(--insertr-spacing-xs) var(--insertr-spacing-sm);
|
||||||
|
border-radius: var(--insertr-border-radius);
|
||||||
|
font-size: var(--insertr-font-size-sm);
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: var(--insertr-z-floating);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-add-btn:hover {
|
||||||
|
background: var(--insertr-primary-hover);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-add-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Item controls positioned in top right corner of each item */
|
||||||
|
.insertr-item-controls {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
z-index: var(--insertr-z-floating);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Individual control buttons */
|
||||||
|
.insertr-control-btn {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: var(--insertr-bg-primary);
|
||||||
|
border: 1px solid var(--insertr-border-color);
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--insertr-text-primary);
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-control-btn:hover {
|
||||||
|
background: var(--insertr-bg-secondary);
|
||||||
|
border-color: var(--insertr-primary);
|
||||||
|
color: var(--insertr-primary);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove button specific styling */
|
||||||
|
.insertr-control-btn:last-child {
|
||||||
|
color: var(--insertr-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-control-btn:last-child:hover {
|
||||||
|
background: var(--insertr-danger);
|
||||||
|
color: var(--insertr-text-inverse);
|
||||||
|
border-color: var(--insertr-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Collection items hover state */
|
||||||
|
.insertr-collection-active > *:hover {
|
||||||
|
background: rgba(0, 123, 255, 0.03);
|
||||||
|
outline: 1px solid rgba(var(--insertr-primary), 0.2);
|
||||||
|
outline-offset: 2px;
|
||||||
|
border-radius: var(--insertr-border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show item controls on hover */
|
||||||
|
.insertr-collection-active > *:hover .insertr-item-controls {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments for collection management */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.insertr-add-btn {
|
||||||
|
position: static;
|
||||||
|
display: block;
|
||||||
|
margin: var(--insertr-spacing-sm) auto 0;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-item-controls {
|
||||||
|
position: relative;
|
||||||
|
opacity: 1;
|
||||||
|
top: auto;
|
||||||
|
right: auto;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: var(--insertr-spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-control-btn {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -171,3 +171,20 @@ func (c *HTTPClient) CreateContent(siteID, contentID, htmlContent, originalTempl
|
|||||||
// 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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collection method stubs - TODO: Implement these for HTTP API
|
||||||
|
func (c *HTTPClient) GetCollection(siteID, collectionID string) (*engine.CollectionItem, error) {
|
||||||
|
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HTTPClient) CreateCollection(siteID, collectionID, containerHTML, lastEditedBy string) (*engine.CollectionItem, error) {
|
||||||
|
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HTTPClient) GetCollectionItems(siteID, collectionID string) ([]engine.CollectionItemWithTemplate, error) {
|
||||||
|
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HTTPClient) CreateCollectionTemplate(siteID, collectionID, name, htmlTemplate string, isDefault bool) (*engine.CollectionTemplateItem, error) {
|
||||||
|
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
|
||||||
|
}
|
||||||
|
|||||||
@@ -233,3 +233,20 @@ func toNullString(s string) sql.NullString {
|
|||||||
}
|
}
|
||||||
return sql.NullString{String: s, Valid: true}
|
return sql.NullString{String: s, Valid: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collection method stubs - TODO: Implement these
|
||||||
|
func (d *DatabaseClient) GetCollection(siteID, collectionID string) (*engine.CollectionItem, error) {
|
||||||
|
return nil, fmt.Errorf("collection operations not implemented in content.DatabaseClient")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DatabaseClient) CreateCollection(siteID, collectionID, containerHTML, lastEditedBy string) (*engine.CollectionItem, error) {
|
||||||
|
return nil, fmt.Errorf("collection operations not implemented in content.DatabaseClient")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DatabaseClient) GetCollectionItems(siteID, collectionID string) ([]engine.CollectionItemWithTemplate, error) {
|
||||||
|
return nil, fmt.Errorf("collection operations not implemented in content.DatabaseClient")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DatabaseClient) CreateCollectionTemplate(siteID, collectionID, name, htmlTemplate string, isDefault bool) (*engine.CollectionTemplateItem, error) {
|
||||||
|
return nil, fmt.Errorf("collection operations not implemented in content.DatabaseClient")
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package content
|
package content
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/insertr/insertr/internal/engine"
|
"github.com/insertr/insertr/internal/engine"
|
||||||
@@ -156,3 +157,20 @@ func (m *MockClient) CreateContent(siteID, contentID, htmlContent, originalTempl
|
|||||||
|
|
||||||
return &item, nil
|
return &item, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collection method stubs - TODO: Implement these for mock testing
|
||||||
|
func (m *MockClient) GetCollection(siteID, collectionID string) (*engine.CollectionItem, error) {
|
||||||
|
return nil, fmt.Errorf("collection operations not implemented in MockClient")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockClient) CreateCollection(siteID, collectionID, containerHTML, lastEditedBy string) (*engine.CollectionItem, error) {
|
||||||
|
return nil, fmt.Errorf("collection operations not implemented in MockClient")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockClient) GetCollectionItems(siteID, collectionID string) ([]engine.CollectionItemWithTemplate, error) {
|
||||||
|
return nil, fmt.Errorf("collection operations not implemented in MockClient")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockClient) CreateCollectionTemplate(siteID, collectionID, name, htmlTemplate string, isDefault bool) (*engine.CollectionTemplateItem, error) {
|
||||||
|
return nil, fmt.Errorf("collection operations not implemented in MockClient")
|
||||||
|
}
|
||||||
|
|||||||
@@ -108,11 +108,36 @@ func (db *Database) initializeSQLiteSchema() error {
|
|||||||
return fmt.Errorf("failed to create content_versions table: %w", err)
|
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)
|
// Create indexes manually (sqlc doesn't generate CREATE INDEX functions for SQLite)
|
||||||
indexQueries := []string{
|
indexQueries := []string{
|
||||||
"CREATE INDEX IF NOT EXISTS idx_content_site_id ON content(site_id);",
|
"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_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_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 {
|
for _, query := range indexQueries {
|
||||||
@@ -121,17 +146,32 @@ func (db *Database) initializeSQLiteSchema() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create update trigger manually (sqlc doesn't generate trigger creation functions)
|
// Create update triggers manually (sqlc doesn't generate trigger creation functions)
|
||||||
triggerQuery := `
|
triggerQueries := []string{
|
||||||
CREATE TRIGGER IF NOT EXISTS update_content_updated_at
|
`CREATE TRIGGER IF NOT EXISTS update_content_updated_at
|
||||||
AFTER UPDATE ON content
|
AFTER UPDATE ON content
|
||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
BEGIN
|
BEGIN
|
||||||
UPDATE content SET updated_at = strftime('%s', 'now') WHERE id = NEW.id AND site_id = NEW.site_id;
|
UPDATE content SET updated_at = strftime('%s', 'now') WHERE id = NEW.id AND site_id = NEW.site_id;
|
||||||
END;`
|
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 {
|
for _, query := range triggerQueries {
|
||||||
return fmt.Errorf("failed to create update trigger: %w", err)
|
if _, err := db.conn.Exec(query); err != nil {
|
||||||
|
return fmt.Errorf("failed to create trigger: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -150,6 +190,23 @@ func (db *Database) initializePostgreSQLSchema() error {
|
|||||||
return fmt.Errorf("failed to create content_versions table: %w", err)
|
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)
|
// Create indexes using sqlc-generated functions (PostgreSQL supports this)
|
||||||
if err := db.postgresqlQueries.CreateContentSiteIndex(ctx); err != nil {
|
if err := db.postgresqlQueries.CreateContentSiteIndex(ctx); err != nil {
|
||||||
return fmt.Errorf("failed to create content site index: %w", err)
|
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)
|
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
|
// Create update function using sqlc-generated function
|
||||||
if err := db.postgresqlQueries.CreateUpdateFunction(ctx); err != nil {
|
if err := db.postgresqlQueries.CreateUpdateFunction(ctx); err != nil {
|
||||||
return fmt.Errorf("failed to create update function: %w", err)
|
return fmt.Errorf("failed to create update function: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create trigger manually (sqlc doesn't generate trigger creation functions)
|
// Create triggers manually (sqlc doesn't generate trigger creation functions)
|
||||||
triggerQuery := `
|
triggerQueries := []string{
|
||||||
DROP TRIGGER IF EXISTS update_content_updated_at ON content;
|
`DROP TRIGGER IF EXISTS update_content_updated_at ON content;
|
||||||
CREATE TRIGGER update_content_updated_at
|
CREATE TRIGGER update_content_updated_at
|
||||||
BEFORE UPDATE ON content
|
BEFORE UPDATE ON content
|
||||||
FOR EACH ROW
|
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 {
|
for _, query := range triggerQueries {
|
||||||
return fmt.Errorf("failed to create update trigger: %w", err)
|
if _, err := db.conn.Exec(query); err != nil {
|
||||||
|
return fmt.Errorf("failed to create trigger: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
197
internal/db/postgresql/collection_item_versions.sql.go
Normal file
197
internal/db/postgresql/collection_item_versions.sql.go
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: collection_item_versions.sql
|
||||||
|
|
||||||
|
package postgresql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createCollectionItemVersion = `-- name: CreateCollectionItemVersion :exec
|
||||||
|
|
||||||
|
INSERT INTO collection_item_versions (item_id, collection_id, site_id, html_content, template_id, position, created_by)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateCollectionItemVersionParams struct {
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
HtmlContent string `json:"html_content"`
|
||||||
|
TemplateID int32 `json:"template_id"`
|
||||||
|
Position int32 `json:"position"`
|
||||||
|
CreatedBy string `json:"created_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collection item versions table queries
|
||||||
|
func (q *Queries) CreateCollectionItemVersion(ctx context.Context, arg CreateCollectionItemVersionParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, createCollectionItemVersion,
|
||||||
|
arg.ItemID,
|
||||||
|
arg.CollectionID,
|
||||||
|
arg.SiteID,
|
||||||
|
arg.HtmlContent,
|
||||||
|
arg.TemplateID,
|
||||||
|
arg.Position,
|
||||||
|
arg.CreatedBy,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteOldCollectionItemVersions = `-- name: DeleteOldCollectionItemVersions :exec
|
||||||
|
DELETE FROM collection_item_versions
|
||||||
|
WHERE created_at < $1 AND site_id = $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type DeleteOldCollectionItemVersionsParams struct {
|
||||||
|
CreatedBefore int64 `json:"created_before"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) DeleteOldCollectionItemVersions(ctx context.Context, arg DeleteOldCollectionItemVersionsParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, deleteOldCollectionItemVersions, arg.CreatedBefore, arg.SiteID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAllCollectionItemVersionsForSite = `-- name: GetAllCollectionItemVersionsForSite :many
|
||||||
|
SELECT
|
||||||
|
civ.version_id, civ.item_id, civ.collection_id, civ.site_id, civ.html_content, civ.template_id, civ.position, civ.created_at, civ.created_by,
|
||||||
|
ci.html_content as current_html_content, ci.position as current_position
|
||||||
|
FROM collection_item_versions civ
|
||||||
|
LEFT JOIN collection_items ci ON civ.item_id = ci.item_id AND civ.collection_id = ci.collection_id AND civ.site_id = ci.site_id
|
||||||
|
WHERE civ.site_id = $1
|
||||||
|
ORDER BY civ.created_at DESC
|
||||||
|
LIMIT $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetAllCollectionItemVersionsForSiteParams struct {
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
LimitCount int32 `json:"limit_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetAllCollectionItemVersionsForSiteRow struct {
|
||||||
|
VersionID int32 `json:"version_id"`
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
HtmlContent string `json:"html_content"`
|
||||||
|
TemplateID int32 `json:"template_id"`
|
||||||
|
Position int32 `json:"position"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
CreatedBy string `json:"created_by"`
|
||||||
|
CurrentHtmlContent sql.NullString `json:"current_html_content"`
|
||||||
|
CurrentPosition sql.NullInt32 `json:"current_position"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetAllCollectionItemVersionsForSite(ctx context.Context, arg GetAllCollectionItemVersionsForSiteParams) ([]GetAllCollectionItemVersionsForSiteRow, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, getAllCollectionItemVersionsForSite, arg.SiteID, arg.LimitCount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetAllCollectionItemVersionsForSiteRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetAllCollectionItemVersionsForSiteRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.VersionID,
|
||||||
|
&i.ItemID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.HtmlContent,
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.Position,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.CreatedBy,
|
||||||
|
&i.CurrentHtmlContent,
|
||||||
|
&i.CurrentPosition,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCollectionItemVersion = `-- name: GetCollectionItemVersion :one
|
||||||
|
SELECT version_id, item_id, collection_id, site_id, html_content, template_id, position, created_at, created_by
|
||||||
|
FROM collection_item_versions
|
||||||
|
WHERE version_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetCollectionItemVersion(ctx context.Context, versionID int32) (CollectionItemVersion, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getCollectionItemVersion, versionID)
|
||||||
|
var i CollectionItemVersion
|
||||||
|
err := row.Scan(
|
||||||
|
&i.VersionID,
|
||||||
|
&i.ItemID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.HtmlContent,
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.Position,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.CreatedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCollectionItemVersionHistory = `-- name: GetCollectionItemVersionHistory :many
|
||||||
|
SELECT version_id, item_id, collection_id, site_id, html_content, template_id, position, created_at, created_by
|
||||||
|
FROM collection_item_versions
|
||||||
|
WHERE item_id = $1 AND collection_id = $2 AND site_id = $3
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT $4
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetCollectionItemVersionHistoryParams struct {
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
LimitCount int32 `json:"limit_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetCollectionItemVersionHistory(ctx context.Context, arg GetCollectionItemVersionHistoryParams) ([]CollectionItemVersion, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, getCollectionItemVersionHistory,
|
||||||
|
arg.ItemID,
|
||||||
|
arg.CollectionID,
|
||||||
|
arg.SiteID,
|
||||||
|
arg.LimitCount,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []CollectionItemVersion
|
||||||
|
for rows.Next() {
|
||||||
|
var i CollectionItemVersion
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.VersionID,
|
||||||
|
&i.ItemID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.HtmlContent,
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.Position,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.CreatedBy,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
327
internal/db/postgresql/collection_items.sql.go
Normal file
327
internal/db/postgresql/collection_items.sql.go
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: collection_items.sql
|
||||||
|
|
||||||
|
package postgresql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createCollectionItem = `-- name: CreateCollectionItem :one
|
||||||
|
INSERT INTO collection_items (item_id, collection_id, site_id, template_id, html_content, position, last_edited_by)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||||
|
RETURNING item_id, collection_id, site_id, template_id, html_content, position, created_at, updated_at, last_edited_by
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateCollectionItemParams struct {
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
TemplateID int32 `json:"template_id"`
|
||||||
|
HtmlContent string `json:"html_content"`
|
||||||
|
Position int32 `json:"position"`
|
||||||
|
LastEditedBy string `json:"last_edited_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateCollectionItem(ctx context.Context, arg CreateCollectionItemParams) (CollectionItem, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, createCollectionItem,
|
||||||
|
arg.ItemID,
|
||||||
|
arg.CollectionID,
|
||||||
|
arg.SiteID,
|
||||||
|
arg.TemplateID,
|
||||||
|
arg.HtmlContent,
|
||||||
|
arg.Position,
|
||||||
|
arg.LastEditedBy,
|
||||||
|
)
|
||||||
|
var i CollectionItem
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ItemID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.HtmlContent,
|
||||||
|
&i.Position,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.LastEditedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteCollectionItem = `-- name: DeleteCollectionItem :exec
|
||||||
|
DELETE FROM collection_items
|
||||||
|
WHERE item_id = $1 AND collection_id = $2 AND site_id = $3
|
||||||
|
`
|
||||||
|
|
||||||
|
type DeleteCollectionItemParams struct {
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) DeleteCollectionItem(ctx context.Context, arg DeleteCollectionItemParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, deleteCollectionItem, arg.ItemID, arg.CollectionID, arg.SiteID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteCollectionItems = `-- name: DeleteCollectionItems :exec
|
||||||
|
DELETE FROM collection_items
|
||||||
|
WHERE collection_id = $1 AND site_id = $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type DeleteCollectionItemsParams struct {
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) DeleteCollectionItems(ctx context.Context, arg DeleteCollectionItemsParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, deleteCollectionItems, arg.CollectionID, arg.SiteID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCollectionItem = `-- name: GetCollectionItem :one
|
||||||
|
|
||||||
|
SELECT item_id, collection_id, site_id, template_id, html_content, position, created_at, updated_at, last_edited_by
|
||||||
|
FROM collection_items
|
||||||
|
WHERE item_id = $1 AND collection_id = $2 AND site_id = $3
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetCollectionItemParams struct {
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collection items table queries
|
||||||
|
func (q *Queries) GetCollectionItem(ctx context.Context, arg GetCollectionItemParams) (CollectionItem, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getCollectionItem, arg.ItemID, arg.CollectionID, arg.SiteID)
|
||||||
|
var i CollectionItem
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ItemID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.HtmlContent,
|
||||||
|
&i.Position,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.LastEditedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCollectionItems = `-- name: GetCollectionItems :many
|
||||||
|
SELECT item_id, collection_id, site_id, template_id, html_content, position, created_at, updated_at, last_edited_by
|
||||||
|
FROM collection_items
|
||||||
|
WHERE collection_id = $1 AND site_id = $2
|
||||||
|
ORDER BY position ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetCollectionItemsParams struct {
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetCollectionItems(ctx context.Context, arg GetCollectionItemsParams) ([]CollectionItem, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, getCollectionItems, arg.CollectionID, arg.SiteID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []CollectionItem
|
||||||
|
for rows.Next() {
|
||||||
|
var i CollectionItem
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ItemID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.HtmlContent,
|
||||||
|
&i.Position,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.LastEditedBy,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCollectionItemsWithTemplate = `-- name: GetCollectionItemsWithTemplate :many
|
||||||
|
SELECT
|
||||||
|
ci.item_id, ci.collection_id, ci.site_id, ci.template_id, ci.html_content, ci.position, ci.created_at, ci.updated_at, ci.last_edited_by,
|
||||||
|
ct.name as template_name, ct.html_template, ct.is_default
|
||||||
|
FROM collection_items ci
|
||||||
|
JOIN collection_templates ct ON ci.template_id = ct.template_id
|
||||||
|
WHERE ci.collection_id = $1 AND ci.site_id = $2
|
||||||
|
ORDER BY ci.position ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetCollectionItemsWithTemplateParams struct {
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetCollectionItemsWithTemplateRow struct {
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
TemplateID int32 `json:"template_id"`
|
||||||
|
HtmlContent string `json:"html_content"`
|
||||||
|
Position int32 `json:"position"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
LastEditedBy string `json:"last_edited_by"`
|
||||||
|
TemplateName string `json:"template_name"`
|
||||||
|
HtmlTemplate string `json:"html_template"`
|
||||||
|
IsDefault bool `json:"is_default"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetCollectionItemsWithTemplate(ctx context.Context, arg GetCollectionItemsWithTemplateParams) ([]GetCollectionItemsWithTemplateRow, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, getCollectionItemsWithTemplate, arg.CollectionID, arg.SiteID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetCollectionItemsWithTemplateRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetCollectionItemsWithTemplateRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ItemID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.HtmlContent,
|
||||||
|
&i.Position,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.LastEditedBy,
|
||||||
|
&i.TemplateName,
|
||||||
|
&i.HtmlTemplate,
|
||||||
|
&i.IsDefault,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMaxPosition = `-- name: GetMaxPosition :one
|
||||||
|
SELECT COALESCE(MAX(position), 0) as max_position
|
||||||
|
FROM collection_items
|
||||||
|
WHERE collection_id = $1 AND site_id = $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetMaxPositionParams struct {
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetMaxPosition(ctx context.Context, arg GetMaxPositionParams) (interface{}, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getMaxPosition, arg.CollectionID, arg.SiteID)
|
||||||
|
var max_position interface{}
|
||||||
|
err := row.Scan(&max_position)
|
||||||
|
return max_position, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const reorderCollectionItems = `-- name: ReorderCollectionItems :exec
|
||||||
|
UPDATE collection_items
|
||||||
|
SET position = position + $1
|
||||||
|
WHERE collection_id = $2 AND site_id = $3
|
||||||
|
AND position >= $4
|
||||||
|
`
|
||||||
|
|
||||||
|
type ReorderCollectionItemsParams struct {
|
||||||
|
PositionDelta int32 `json:"position_delta"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
StartPosition int32 `json:"start_position"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) ReorderCollectionItems(ctx context.Context, arg ReorderCollectionItemsParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, reorderCollectionItems,
|
||||||
|
arg.PositionDelta,
|
||||||
|
arg.CollectionID,
|
||||||
|
arg.SiteID,
|
||||||
|
arg.StartPosition,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCollectionItem = `-- name: UpdateCollectionItem :one
|
||||||
|
UPDATE collection_items
|
||||||
|
SET html_content = $1, last_edited_by = $2
|
||||||
|
WHERE item_id = $3 AND collection_id = $4 AND site_id = $5
|
||||||
|
RETURNING item_id, collection_id, site_id, template_id, html_content, position, created_at, updated_at, last_edited_by
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateCollectionItemParams struct {
|
||||||
|
HtmlContent string `json:"html_content"`
|
||||||
|
LastEditedBy string `json:"last_edited_by"`
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateCollectionItem(ctx context.Context, arg UpdateCollectionItemParams) (CollectionItem, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, updateCollectionItem,
|
||||||
|
arg.HtmlContent,
|
||||||
|
arg.LastEditedBy,
|
||||||
|
arg.ItemID,
|
||||||
|
arg.CollectionID,
|
||||||
|
arg.SiteID,
|
||||||
|
)
|
||||||
|
var i CollectionItem
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ItemID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.HtmlContent,
|
||||||
|
&i.Position,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.LastEditedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCollectionItemPosition = `-- name: UpdateCollectionItemPosition :exec
|
||||||
|
UPDATE collection_items
|
||||||
|
SET position = $1
|
||||||
|
WHERE item_id = $2 AND collection_id = $3 AND site_id = $4
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateCollectionItemPositionParams struct {
|
||||||
|
Position int32 `json:"position"`
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateCollectionItemPosition(ctx context.Context, arg UpdateCollectionItemPositionParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, updateCollectionItemPosition,
|
||||||
|
arg.Position,
|
||||||
|
arg.ItemID,
|
||||||
|
arg.CollectionID,
|
||||||
|
arg.SiteID,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
217
internal/db/postgresql/collection_templates.sql.go
Normal file
217
internal/db/postgresql/collection_templates.sql.go
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: collection_templates.sql
|
||||||
|
|
||||||
|
package postgresql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createCollectionTemplate = `-- name: CreateCollectionTemplate :one
|
||||||
|
INSERT INTO collection_templates (collection_id, site_id, name, html_template, is_default)
|
||||||
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
|
RETURNING template_id, collection_id, site_id, name, html_template, is_default, created_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateCollectionTemplateParams struct {
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
HtmlTemplate string `json:"html_template"`
|
||||||
|
IsDefault bool `json:"is_default"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateCollectionTemplate(ctx context.Context, arg CreateCollectionTemplateParams) (CollectionTemplate, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, createCollectionTemplate,
|
||||||
|
arg.CollectionID,
|
||||||
|
arg.SiteID,
|
||||||
|
arg.Name,
|
||||||
|
arg.HtmlTemplate,
|
||||||
|
arg.IsDefault,
|
||||||
|
)
|
||||||
|
var i CollectionTemplate
|
||||||
|
err := row.Scan(
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.Name,
|
||||||
|
&i.HtmlTemplate,
|
||||||
|
&i.IsDefault,
|
||||||
|
&i.CreatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteCollectionTemplate = `-- name: DeleteCollectionTemplate :exec
|
||||||
|
DELETE FROM collection_templates
|
||||||
|
WHERE template_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteCollectionTemplate(ctx context.Context, templateID int32) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, deleteCollectionTemplate, templateID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteCollectionTemplates = `-- name: DeleteCollectionTemplates :exec
|
||||||
|
DELETE FROM collection_templates
|
||||||
|
WHERE collection_id = $1 AND site_id = $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type DeleteCollectionTemplatesParams struct {
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) DeleteCollectionTemplates(ctx context.Context, arg DeleteCollectionTemplatesParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, deleteCollectionTemplates, arg.CollectionID, arg.SiteID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCollectionTemplate = `-- name: GetCollectionTemplate :one
|
||||||
|
|
||||||
|
SELECT template_id, collection_id, site_id, name, html_template, is_default, created_at
|
||||||
|
FROM collection_templates
|
||||||
|
WHERE template_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
// Collection templates table queries
|
||||||
|
func (q *Queries) GetCollectionTemplate(ctx context.Context, templateID int32) (CollectionTemplate, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getCollectionTemplate, templateID)
|
||||||
|
var i CollectionTemplate
|
||||||
|
err := row.Scan(
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.Name,
|
||||||
|
&i.HtmlTemplate,
|
||||||
|
&i.IsDefault,
|
||||||
|
&i.CreatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCollectionTemplates = `-- name: GetCollectionTemplates :many
|
||||||
|
SELECT template_id, collection_id, site_id, name, html_template, is_default, created_at
|
||||||
|
FROM collection_templates
|
||||||
|
WHERE collection_id = $1 AND site_id = $2
|
||||||
|
ORDER BY is_default DESC, created_at ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetCollectionTemplatesParams struct {
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetCollectionTemplates(ctx context.Context, arg GetCollectionTemplatesParams) ([]CollectionTemplate, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, getCollectionTemplates, arg.CollectionID, arg.SiteID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []CollectionTemplate
|
||||||
|
for rows.Next() {
|
||||||
|
var i CollectionTemplate
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.Name,
|
||||||
|
&i.HtmlTemplate,
|
||||||
|
&i.IsDefault,
|
||||||
|
&i.CreatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDefaultTemplate = `-- name: GetDefaultTemplate :one
|
||||||
|
SELECT template_id, collection_id, site_id, name, html_template, is_default, created_at
|
||||||
|
FROM collection_templates
|
||||||
|
WHERE collection_id = $1 AND site_id = $2 AND is_default = TRUE
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetDefaultTemplateParams struct {
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetDefaultTemplate(ctx context.Context, arg GetDefaultTemplateParams) (CollectionTemplate, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getDefaultTemplate, arg.CollectionID, arg.SiteID)
|
||||||
|
var i CollectionTemplate
|
||||||
|
err := row.Scan(
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.Name,
|
||||||
|
&i.HtmlTemplate,
|
||||||
|
&i.IsDefault,
|
||||||
|
&i.CreatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const setTemplateAsDefault = `-- name: SetTemplateAsDefault :exec
|
||||||
|
UPDATE collection_templates
|
||||||
|
SET is_default = CASE
|
||||||
|
WHEN template_id = $1 THEN TRUE
|
||||||
|
ELSE FALSE
|
||||||
|
END
|
||||||
|
WHERE collection_id = $2 AND site_id = $3
|
||||||
|
`
|
||||||
|
|
||||||
|
type SetTemplateAsDefaultParams struct {
|
||||||
|
TemplateID int32 `json:"template_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) SetTemplateAsDefault(ctx context.Context, arg SetTemplateAsDefaultParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, setTemplateAsDefault, arg.TemplateID, arg.CollectionID, arg.SiteID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCollectionTemplate = `-- name: UpdateCollectionTemplate :one
|
||||||
|
UPDATE collection_templates
|
||||||
|
SET name = $1, html_template = $2, is_default = $3
|
||||||
|
WHERE template_id = $4
|
||||||
|
RETURNING template_id, collection_id, site_id, name, html_template, is_default, created_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateCollectionTemplateParams struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
HtmlTemplate string `json:"html_template"`
|
||||||
|
IsDefault bool `json:"is_default"`
|
||||||
|
TemplateID int32 `json:"template_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateCollectionTemplate(ctx context.Context, arg UpdateCollectionTemplateParams) (CollectionTemplate, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, updateCollectionTemplate,
|
||||||
|
arg.Name,
|
||||||
|
arg.HtmlTemplate,
|
||||||
|
arg.IsDefault,
|
||||||
|
arg.TemplateID,
|
||||||
|
)
|
||||||
|
var i CollectionTemplate
|
||||||
|
err := row.Scan(
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.Name,
|
||||||
|
&i.HtmlTemplate,
|
||||||
|
&i.IsDefault,
|
||||||
|
&i.CreatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
199
internal/db/postgresql/collections.sql.go
Normal file
199
internal/db/postgresql/collections.sql.go
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: collections.sql
|
||||||
|
|
||||||
|
package postgresql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createCollection = `-- name: CreateCollection :one
|
||||||
|
INSERT INTO collections (id, site_id, container_html, last_edited_by)
|
||||||
|
VALUES ($1, $2, $3, $4)
|
||||||
|
RETURNING id, site_id, container_html, created_at, updated_at, last_edited_by
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateCollectionParams struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
ContainerHtml string `json:"container_html"`
|
||||||
|
LastEditedBy string `json:"last_edited_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateCollection(ctx context.Context, arg CreateCollectionParams) (Collection, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, createCollection,
|
||||||
|
arg.ID,
|
||||||
|
arg.SiteID,
|
||||||
|
arg.ContainerHtml,
|
||||||
|
arg.LastEditedBy,
|
||||||
|
)
|
||||||
|
var i Collection
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.ContainerHtml,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.LastEditedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteAllSiteCollections = `-- name: DeleteAllSiteCollections :exec
|
||||||
|
DELETE FROM collections
|
||||||
|
WHERE site_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteAllSiteCollections(ctx context.Context, siteID string) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, deleteAllSiteCollections, siteID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteCollection = `-- name: DeleteCollection :exec
|
||||||
|
DELETE FROM collections
|
||||||
|
WHERE id = $1 AND site_id = $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type DeleteCollectionParams struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) DeleteCollection(ctx context.Context, arg DeleteCollectionParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, deleteCollection, arg.ID, arg.SiteID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAllCollections = `-- name: GetAllCollections :many
|
||||||
|
SELECT id, site_id, container_html, created_at, updated_at, last_edited_by
|
||||||
|
FROM collections
|
||||||
|
WHERE site_id = $1
|
||||||
|
ORDER BY updated_at DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetAllCollections(ctx context.Context, siteID string) ([]Collection, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, getAllCollections, siteID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []Collection
|
||||||
|
for rows.Next() {
|
||||||
|
var i Collection
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.ContainerHtml,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.LastEditedBy,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCollection = `-- name: GetCollection :one
|
||||||
|
|
||||||
|
SELECT id, site_id, container_html, created_at, updated_at, last_edited_by
|
||||||
|
FROM collections
|
||||||
|
WHERE id = $1 AND site_id = $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetCollectionParams struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collections table queries
|
||||||
|
func (q *Queries) GetCollection(ctx context.Context, arg GetCollectionParams) (Collection, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getCollection, arg.ID, arg.SiteID)
|
||||||
|
var i Collection
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.ContainerHtml,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.LastEditedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCollection = `-- name: UpdateCollection :one
|
||||||
|
UPDATE collections
|
||||||
|
SET container_html = $1, last_edited_by = $2
|
||||||
|
WHERE id = $3 AND site_id = $4
|
||||||
|
RETURNING id, site_id, container_html, created_at, updated_at, last_edited_by
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateCollectionParams struct {
|
||||||
|
ContainerHtml string `json:"container_html"`
|
||||||
|
LastEditedBy string `json:"last_edited_by"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateCollection(ctx context.Context, arg UpdateCollectionParams) (Collection, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, updateCollection,
|
||||||
|
arg.ContainerHtml,
|
||||||
|
arg.LastEditedBy,
|
||||||
|
arg.ID,
|
||||||
|
arg.SiteID,
|
||||||
|
)
|
||||||
|
var i Collection
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.ContainerHtml,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.LastEditedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const upsertCollection = `-- name: UpsertCollection :one
|
||||||
|
INSERT INTO collections (id, site_id, container_html, last_edited_by)
|
||||||
|
VALUES ($1, $2, $3, $4)
|
||||||
|
ON CONFLICT(id, site_id) DO UPDATE SET
|
||||||
|
container_html = EXCLUDED.container_html,
|
||||||
|
last_edited_by = EXCLUDED.last_edited_by
|
||||||
|
RETURNING id, site_id, container_html, created_at, updated_at, last_edited_by
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpsertCollectionParams struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
ContainerHtml string `json:"container_html"`
|
||||||
|
LastEditedBy string `json:"last_edited_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpsertCollection(ctx context.Context, arg UpsertCollectionParams) (Collection, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, upsertCollection,
|
||||||
|
arg.ID,
|
||||||
|
arg.SiteID,
|
||||||
|
arg.ContainerHtml,
|
||||||
|
arg.LastEditedBy,
|
||||||
|
)
|
||||||
|
var i Collection
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.ContainerHtml,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.LastEditedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
@@ -8,6 +8,49 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Collection struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
ContainerHtml string `json:"container_html"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
LastEditedBy string `json:"last_edited_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CollectionItem struct {
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
TemplateID int32 `json:"template_id"`
|
||||||
|
HtmlContent string `json:"html_content"`
|
||||||
|
Position int32 `json:"position"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
LastEditedBy string `json:"last_edited_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CollectionItemVersion struct {
|
||||||
|
VersionID int32 `json:"version_id"`
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
HtmlContent string `json:"html_content"`
|
||||||
|
TemplateID int32 `json:"template_id"`
|
||||||
|
Position int32 `json:"position"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
CreatedBy string `json:"created_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CollectionTemplate struct {
|
||||||
|
TemplateID int32 `json:"template_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
HtmlTemplate string `json:"html_template"`
|
||||||
|
IsDefault bool `json:"is_default"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
type Content struct {
|
type Content struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
SiteID string `json:"site_id"`
|
SiteID string `json:"site_id"`
|
||||||
|
|||||||
@@ -9,24 +9,70 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Querier interface {
|
type Querier interface {
|
||||||
|
CreateCollection(ctx context.Context, arg CreateCollectionParams) (Collection, error)
|
||||||
|
CreateCollectionItem(ctx context.Context, arg CreateCollectionItemParams) (CollectionItem, error)
|
||||||
|
// Collection item versions table queries
|
||||||
|
CreateCollectionItemVersion(ctx context.Context, arg CreateCollectionItemVersionParams) error
|
||||||
|
CreateCollectionItemVersionsLookupIndex(ctx context.Context) error
|
||||||
|
CreateCollectionItemsLookupIndex(ctx context.Context) error
|
||||||
|
CreateCollectionItemsTemplateIndex(ctx context.Context) error
|
||||||
|
CreateCollectionTemplate(ctx context.Context, arg CreateCollectionTemplateParams) (CollectionTemplate, error)
|
||||||
|
CreateCollectionTemplatesDefaultIndex(ctx context.Context) error
|
||||||
|
CreateCollectionTemplatesLookupIndex(ctx context.Context) error
|
||||||
|
CreateCollectionTemplatesOneDefaultIndex(ctx context.Context) error
|
||||||
|
CreateCollectionsSiteIndex(ctx context.Context) error
|
||||||
|
CreateCollectionsUpdatedAtIndex(ctx context.Context) error
|
||||||
CreateContent(ctx context.Context, arg CreateContentParams) (Content, error)
|
CreateContent(ctx context.Context, arg CreateContentParams) (Content, error)
|
||||||
CreateContentSiteIndex(ctx context.Context) error
|
CreateContentSiteIndex(ctx context.Context) error
|
||||||
CreateContentUpdatedAtIndex(ctx context.Context) error
|
CreateContentUpdatedAtIndex(ctx context.Context) error
|
||||||
CreateContentVersion(ctx context.Context, arg CreateContentVersionParams) error
|
CreateContentVersion(ctx context.Context, arg CreateContentVersionParams) error
|
||||||
CreateUpdateFunction(ctx context.Context) error
|
CreateUpdateFunction(ctx context.Context) error
|
||||||
CreateVersionsLookupIndex(ctx context.Context) error
|
CreateVersionsLookupIndex(ctx context.Context) error
|
||||||
|
DeleteAllSiteCollections(ctx context.Context, siteID string) error
|
||||||
DeleteAllSiteContent(ctx context.Context, siteID string) error
|
DeleteAllSiteContent(ctx context.Context, siteID string) error
|
||||||
|
DeleteCollection(ctx context.Context, arg DeleteCollectionParams) error
|
||||||
|
DeleteCollectionItem(ctx context.Context, arg DeleteCollectionItemParams) error
|
||||||
|
DeleteCollectionItems(ctx context.Context, arg DeleteCollectionItemsParams) error
|
||||||
|
DeleteCollectionTemplate(ctx context.Context, templateID int32) error
|
||||||
|
DeleteCollectionTemplates(ctx context.Context, arg DeleteCollectionTemplatesParams) error
|
||||||
DeleteContent(ctx context.Context, arg DeleteContentParams) error
|
DeleteContent(ctx context.Context, arg DeleteContentParams) error
|
||||||
|
DeleteOldCollectionItemVersions(ctx context.Context, arg DeleteOldCollectionItemVersionsParams) error
|
||||||
DeleteOldVersions(ctx context.Context, arg DeleteOldVersionsParams) error
|
DeleteOldVersions(ctx context.Context, arg DeleteOldVersionsParams) error
|
||||||
|
GetAllCollectionItemVersionsForSite(ctx context.Context, arg GetAllCollectionItemVersionsForSiteParams) ([]GetAllCollectionItemVersionsForSiteRow, error)
|
||||||
|
GetAllCollections(ctx context.Context, siteID string) ([]Collection, error)
|
||||||
GetAllContent(ctx context.Context, siteID string) ([]Content, error)
|
GetAllContent(ctx context.Context, siteID string) ([]Content, error)
|
||||||
GetAllVersionsForSite(ctx context.Context, arg GetAllVersionsForSiteParams) ([]GetAllVersionsForSiteRow, error)
|
GetAllVersionsForSite(ctx context.Context, arg GetAllVersionsForSiteParams) ([]GetAllVersionsForSiteRow, error)
|
||||||
GetBulkContent(ctx context.Context, arg GetBulkContentParams) ([]Content, error)
|
GetBulkContent(ctx context.Context, arg GetBulkContentParams) ([]Content, error)
|
||||||
|
// Collections table queries
|
||||||
|
GetCollection(ctx context.Context, arg GetCollectionParams) (Collection, error)
|
||||||
|
// Collection items table queries
|
||||||
|
GetCollectionItem(ctx context.Context, arg GetCollectionItemParams) (CollectionItem, error)
|
||||||
|
GetCollectionItemVersion(ctx context.Context, versionID int32) (CollectionItemVersion, error)
|
||||||
|
GetCollectionItemVersionHistory(ctx context.Context, arg GetCollectionItemVersionHistoryParams) ([]CollectionItemVersion, error)
|
||||||
|
GetCollectionItems(ctx context.Context, arg GetCollectionItemsParams) ([]CollectionItem, error)
|
||||||
|
GetCollectionItemsWithTemplate(ctx context.Context, arg GetCollectionItemsWithTemplateParams) ([]GetCollectionItemsWithTemplateRow, error)
|
||||||
|
// Collection templates table queries
|
||||||
|
GetCollectionTemplate(ctx context.Context, templateID int32) (CollectionTemplate, error)
|
||||||
|
GetCollectionTemplates(ctx context.Context, arg GetCollectionTemplatesParams) ([]CollectionTemplate, error)
|
||||||
GetContent(ctx context.Context, arg GetContentParams) (Content, error)
|
GetContent(ctx context.Context, arg GetContentParams) (Content, error)
|
||||||
GetContentVersion(ctx context.Context, versionID int32) (ContentVersion, error)
|
GetContentVersion(ctx context.Context, versionID int32) (ContentVersion, error)
|
||||||
GetContentVersionHistory(ctx context.Context, arg GetContentVersionHistoryParams) ([]ContentVersion, error)
|
GetContentVersionHistory(ctx context.Context, arg GetContentVersionHistoryParams) ([]ContentVersion, error)
|
||||||
|
GetDefaultTemplate(ctx context.Context, arg GetDefaultTemplateParams) (CollectionTemplate, error)
|
||||||
|
GetMaxPosition(ctx context.Context, arg GetMaxPositionParams) (interface{}, error)
|
||||||
|
InitializeCollectionItemVersionsTable(ctx context.Context) error
|
||||||
|
InitializeCollectionItemsTable(ctx context.Context) error
|
||||||
|
InitializeCollectionTemplatesTable(ctx context.Context) error
|
||||||
|
InitializeCollectionsTable(ctx context.Context) error
|
||||||
InitializeSchema(ctx context.Context) error
|
InitializeSchema(ctx context.Context) error
|
||||||
InitializeVersionsTable(ctx context.Context) error
|
InitializeVersionsTable(ctx context.Context) error
|
||||||
|
ReorderCollectionItems(ctx context.Context, arg ReorderCollectionItemsParams) error
|
||||||
|
SetTemplateAsDefault(ctx context.Context, arg SetTemplateAsDefaultParams) error
|
||||||
|
UpdateCollection(ctx context.Context, arg UpdateCollectionParams) (Collection, error)
|
||||||
|
UpdateCollectionItem(ctx context.Context, arg UpdateCollectionItemParams) (CollectionItem, error)
|
||||||
|
UpdateCollectionItemPosition(ctx context.Context, arg UpdateCollectionItemPositionParams) error
|
||||||
|
UpdateCollectionTemplate(ctx context.Context, arg UpdateCollectionTemplateParams) (CollectionTemplate, error)
|
||||||
UpdateContent(ctx context.Context, arg UpdateContentParams) (Content, error)
|
UpdateContent(ctx context.Context, arg UpdateContentParams) (Content, error)
|
||||||
|
UpsertCollection(ctx context.Context, arg UpsertCollectionParams) (Collection, error)
|
||||||
UpsertContent(ctx context.Context, arg UpsertContentParams) (Content, error)
|
UpsertContent(ctx context.Context, arg UpsertContentParams) (Content, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,11 +22,78 @@ CREATE TABLE content_versions (
|
|||||||
created_by TEXT DEFAULT 'system' NOT NULL
|
created_by TEXT DEFAULT 'system' NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Collections table - manages .insertr-add containers
|
||||||
|
CREATE TABLE collections (
|
||||||
|
id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
container_html TEXT NOT NULL,
|
||||||
|
created_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL,
|
||||||
|
updated_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL,
|
||||||
|
last_edited_by TEXT DEFAULT 'system' NOT NULL,
|
||||||
|
PRIMARY KEY (id, site_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Collection templates - multiple template variants per collection
|
||||||
|
CREATE TABLE collection_templates (
|
||||||
|
template_id SERIAL PRIMARY KEY,
|
||||||
|
collection_id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
html_template TEXT NOT NULL,
|
||||||
|
is_default BOOLEAN DEFAULT FALSE NOT NULL,
|
||||||
|
created_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL,
|
||||||
|
FOREIGN KEY (collection_id, site_id) REFERENCES collections(id, site_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Collection items - individual items within collections
|
||||||
|
CREATE TABLE collection_items (
|
||||||
|
item_id TEXT NOT NULL,
|
||||||
|
collection_id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
template_id INTEGER NOT NULL,
|
||||||
|
html_content TEXT NOT NULL,
|
||||||
|
position INTEGER NOT NULL,
|
||||||
|
created_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL,
|
||||||
|
updated_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL,
|
||||||
|
last_edited_by TEXT DEFAULT 'system' NOT NULL,
|
||||||
|
PRIMARY KEY (item_id, collection_id, site_id),
|
||||||
|
FOREIGN KEY (collection_id, site_id) REFERENCES collections(id, site_id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (template_id) REFERENCES collection_templates(template_id) ON DELETE RESTRICT
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Collection item version history
|
||||||
|
CREATE TABLE collection_item_versions (
|
||||||
|
version_id SERIAL PRIMARY KEY,
|
||||||
|
item_id TEXT NOT NULL,
|
||||||
|
collection_id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
html_content TEXT NOT NULL,
|
||||||
|
template_id INTEGER NOT NULL,
|
||||||
|
position INTEGER NOT NULL,
|
||||||
|
created_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL,
|
||||||
|
created_by TEXT DEFAULT 'system' NOT NULL,
|
||||||
|
FOREIGN KEY (item_id, collection_id, site_id) REFERENCES collection_items(item_id, collection_id, site_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
-- Indexes for performance
|
-- Indexes for performance
|
||||||
CREATE INDEX IF NOT EXISTS idx_content_site_id ON content(site_id);
|
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_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_content_versions_lookup ON content_versions(content_id, site_id, created_at DESC);
|
||||||
|
|
||||||
|
-- Collection indexes for performance
|
||||||
|
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);
|
||||||
|
|
||||||
|
-- Constraint to ensure only one default template per collection
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_collection_templates_one_default
|
||||||
|
ON collection_templates(collection_id, site_id)
|
||||||
|
WHERE is_default = TRUE;
|
||||||
|
|
||||||
-- Function and trigger to automatically update updated_at timestamp
|
-- Function and trigger to automatically update updated_at timestamp
|
||||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||||
RETURNS TRIGGER AS $$
|
RETURNS TRIGGER AS $$
|
||||||
@@ -40,3 +107,14 @@ CREATE TRIGGER update_content_updated_at
|
|||||||
BEFORE UPDATE ON content
|
BEFORE UPDATE ON content
|
||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
EXECUTE FUNCTION update_updated_at_column();
|
EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- Triggers for collection timestamps
|
||||||
|
CREATE TRIGGER update_collections_updated_at
|
||||||
|
BEFORE UPDATE ON collections
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
CREATE TRIGGER update_collection_items_updated_at
|
||||||
|
BEFORE UPDATE ON collection_items
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_updated_at_column();
|
||||||
@@ -21,6 +21,59 @@ CREATE TABLE IF NOT EXISTS content_versions (
|
|||||||
created_by TEXT DEFAULT 'system' NOT NULL
|
created_by TEXT DEFAULT 'system' NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- name: InitializeCollectionsTable :exec
|
||||||
|
CREATE TABLE IF NOT EXISTS collections (
|
||||||
|
id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
container_html TEXT NOT NULL,
|
||||||
|
created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
|
||||||
|
updated_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
|
||||||
|
last_edited_by TEXT DEFAULT 'system' NOT NULL,
|
||||||
|
PRIMARY KEY (id, site_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- name: InitializeCollectionTemplatesTable :exec
|
||||||
|
CREATE TABLE IF NOT EXISTS collection_templates (
|
||||||
|
template_id SERIAL PRIMARY KEY,
|
||||||
|
collection_id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
html_template TEXT NOT NULL,
|
||||||
|
is_default BOOLEAN DEFAULT FALSE NOT NULL,
|
||||||
|
created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
|
||||||
|
FOREIGN KEY (collection_id, site_id) REFERENCES collections(id, site_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- name: InitializeCollectionItemsTable :exec
|
||||||
|
CREATE TABLE IF NOT EXISTS collection_items (
|
||||||
|
item_id TEXT NOT NULL,
|
||||||
|
collection_id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
template_id INTEGER NOT NULL,
|
||||||
|
html_content TEXT NOT NULL,
|
||||||
|
position INTEGER NOT NULL,
|
||||||
|
created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
|
||||||
|
updated_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
|
||||||
|
last_edited_by TEXT DEFAULT 'system' NOT NULL,
|
||||||
|
PRIMARY KEY (item_id, collection_id, site_id),
|
||||||
|
FOREIGN KEY (collection_id, site_id) REFERENCES collections(id, site_id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (template_id) REFERENCES collection_templates(template_id) ON DELETE RESTRICT
|
||||||
|
);
|
||||||
|
|
||||||
|
-- name: InitializeCollectionItemVersionsTable :exec
|
||||||
|
CREATE TABLE IF NOT EXISTS collection_item_versions (
|
||||||
|
version_id SERIAL PRIMARY KEY,
|
||||||
|
item_id TEXT NOT NULL,
|
||||||
|
collection_id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
html_content TEXT NOT NULL,
|
||||||
|
template_id INTEGER NOT NULL,
|
||||||
|
position INTEGER NOT NULL,
|
||||||
|
created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
|
||||||
|
created_by TEXT DEFAULT 'system' NOT NULL,
|
||||||
|
FOREIGN KEY (item_id, collection_id, site_id) REFERENCES collection_items(item_id, collection_id, site_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
-- name: CreateContentSiteIndex :exec
|
-- name: CreateContentSiteIndex :exec
|
||||||
CREATE INDEX IF NOT EXISTS idx_content_site_id ON content(site_id);
|
CREATE INDEX IF NOT EXISTS idx_content_site_id ON content(site_id);
|
||||||
|
|
||||||
@@ -30,6 +83,32 @@ CREATE INDEX IF NOT EXISTS idx_content_updated_at ON content(updated_at);
|
|||||||
-- name: CreateVersionsLookupIndex :exec
|
-- name: CreateVersionsLookupIndex :exec
|
||||||
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_content_versions_lookup ON content_versions(content_id, site_id, created_at DESC);
|
||||||
|
|
||||||
|
-- name: CreateCollectionsSiteIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collections_site_id ON collections(site_id);
|
||||||
|
|
||||||
|
-- name: CreateCollectionsUpdatedAtIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collections_updated_at ON collections(updated_at);
|
||||||
|
|
||||||
|
-- name: CreateCollectionTemplatesLookupIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collection_templates_lookup ON collection_templates(collection_id, site_id);
|
||||||
|
|
||||||
|
-- name: CreateCollectionTemplatesDefaultIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collection_templates_default ON collection_templates(collection_id, site_id, is_default DESC);
|
||||||
|
|
||||||
|
-- name: CreateCollectionItemsLookupIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collection_items_lookup ON collection_items(collection_id, site_id, position);
|
||||||
|
|
||||||
|
-- name: CreateCollectionItemsTemplateIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collection_items_template ON collection_items(template_id);
|
||||||
|
|
||||||
|
-- name: CreateCollectionItemVersionsLookupIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collection_item_versions_lookup ON collection_item_versions(item_id, collection_id, site_id, created_at DESC);
|
||||||
|
|
||||||
|
-- name: CreateCollectionTemplatesOneDefaultIndex :exec
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_collection_templates_one_default
|
||||||
|
ON collection_templates(collection_id, site_id)
|
||||||
|
WHERE is_default = TRUE;
|
||||||
|
|
||||||
-- name: CreateUpdateFunction :exec
|
-- name: CreateUpdateFunction :exec
|
||||||
CREATE OR REPLACE FUNCTION update_content_timestamp()
|
CREATE OR REPLACE FUNCTION update_content_timestamp()
|
||||||
RETURNS TRIGGER AS $$
|
RETURNS TRIGGER AS $$
|
||||||
@@ -45,3 +124,17 @@ CREATE TRIGGER update_content_updated_at
|
|||||||
BEFORE UPDATE ON content
|
BEFORE UPDATE ON content
|
||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
EXECUTE FUNCTION update_content_timestamp();
|
EXECUTE FUNCTION update_content_timestamp();
|
||||||
|
|
||||||
|
-- name: CreateCollectionsUpdateTrigger :exec
|
||||||
|
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();
|
||||||
|
|
||||||
|
-- name: CreateCollectionItemsUpdateTrigger :exec
|
||||||
|
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();
|
||||||
@@ -9,6 +9,80 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const createCollectionItemVersionsLookupIndex = `-- name: CreateCollectionItemVersionsLookupIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collection_item_versions_lookup ON collection_item_versions(item_id, collection_id, site_id, created_at DESC)
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) CreateCollectionItemVersionsLookupIndex(ctx context.Context) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, createCollectionItemVersionsLookupIndex)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const createCollectionItemsLookupIndex = `-- name: CreateCollectionItemsLookupIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collection_items_lookup ON collection_items(collection_id, site_id, position)
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) CreateCollectionItemsLookupIndex(ctx context.Context) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, createCollectionItemsLookupIndex)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const createCollectionItemsTemplateIndex = `-- name: CreateCollectionItemsTemplateIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collection_items_template ON collection_items(template_id)
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) CreateCollectionItemsTemplateIndex(ctx context.Context) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, createCollectionItemsTemplateIndex)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const createCollectionTemplatesDefaultIndex = `-- name: CreateCollectionTemplatesDefaultIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collection_templates_default ON collection_templates(collection_id, site_id, is_default DESC)
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) CreateCollectionTemplatesDefaultIndex(ctx context.Context) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, createCollectionTemplatesDefaultIndex)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const createCollectionTemplatesLookupIndex = `-- name: CreateCollectionTemplatesLookupIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collection_templates_lookup ON collection_templates(collection_id, site_id)
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) CreateCollectionTemplatesLookupIndex(ctx context.Context) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, createCollectionTemplatesLookupIndex)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const createCollectionTemplatesOneDefaultIndex = `-- name: CreateCollectionTemplatesOneDefaultIndex :exec
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_collection_templates_one_default
|
||||||
|
ON collection_templates(collection_id, site_id)
|
||||||
|
WHERE is_default = TRUE
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) CreateCollectionTemplatesOneDefaultIndex(ctx context.Context) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, createCollectionTemplatesOneDefaultIndex)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const createCollectionsSiteIndex = `-- name: CreateCollectionsSiteIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collections_site_id ON collections(site_id)
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) CreateCollectionsSiteIndex(ctx context.Context) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, createCollectionsSiteIndex)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const createCollectionsUpdatedAtIndex = `-- name: CreateCollectionsUpdatedAtIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collections_updated_at ON collections(updated_at)
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) CreateCollectionsUpdatedAtIndex(ctx context.Context) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, createCollectionsUpdatedAtIndex)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
const createContentSiteIndex = `-- name: CreateContentSiteIndex :exec
|
const createContentSiteIndex = `-- name: CreateContentSiteIndex :exec
|
||||||
CREATE INDEX IF NOT EXISTS idx_content_site_id ON content(site_id)
|
CREATE INDEX IF NOT EXISTS idx_content_site_id ON content(site_id)
|
||||||
`
|
`
|
||||||
@@ -51,6 +125,83 @@ func (q *Queries) CreateVersionsLookupIndex(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const initializeCollectionItemVersionsTable = `-- name: InitializeCollectionItemVersionsTable :exec
|
||||||
|
CREATE TABLE IF NOT EXISTS collection_item_versions (
|
||||||
|
version_id SERIAL PRIMARY KEY,
|
||||||
|
item_id TEXT NOT NULL,
|
||||||
|
collection_id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
html_content TEXT NOT NULL,
|
||||||
|
template_id INTEGER NOT NULL,
|
||||||
|
position INTEGER NOT NULL,
|
||||||
|
created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
|
||||||
|
created_by TEXT DEFAULT 'system' NOT NULL,
|
||||||
|
FOREIGN KEY (item_id, collection_id, site_id) REFERENCES collection_items(item_id, collection_id, site_id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) InitializeCollectionItemVersionsTable(ctx context.Context) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, initializeCollectionItemVersionsTable)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const initializeCollectionItemsTable = `-- name: InitializeCollectionItemsTable :exec
|
||||||
|
CREATE TABLE IF NOT EXISTS collection_items (
|
||||||
|
item_id TEXT NOT NULL,
|
||||||
|
collection_id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
template_id INTEGER NOT NULL,
|
||||||
|
html_content TEXT NOT NULL,
|
||||||
|
position INTEGER NOT NULL,
|
||||||
|
created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
|
||||||
|
updated_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
|
||||||
|
last_edited_by TEXT DEFAULT 'system' NOT NULL,
|
||||||
|
PRIMARY KEY (item_id, collection_id, site_id),
|
||||||
|
FOREIGN KEY (collection_id, site_id) REFERENCES collections(id, site_id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (template_id) REFERENCES collection_templates(template_id) ON DELETE RESTRICT
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) InitializeCollectionItemsTable(ctx context.Context) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, initializeCollectionItemsTable)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const initializeCollectionTemplatesTable = `-- name: InitializeCollectionTemplatesTable :exec
|
||||||
|
CREATE TABLE IF NOT EXISTS collection_templates (
|
||||||
|
template_id SERIAL PRIMARY KEY,
|
||||||
|
collection_id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
html_template TEXT NOT NULL,
|
||||||
|
is_default BOOLEAN DEFAULT FALSE NOT NULL,
|
||||||
|
created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
|
||||||
|
FOREIGN KEY (collection_id, site_id) REFERENCES collections(id, site_id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) InitializeCollectionTemplatesTable(ctx context.Context) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, initializeCollectionTemplatesTable)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const initializeCollectionsTable = `-- name: InitializeCollectionsTable :exec
|
||||||
|
CREATE TABLE IF NOT EXISTS collections (
|
||||||
|
id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
container_html TEXT NOT NULL,
|
||||||
|
created_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
|
||||||
|
updated_at BIGINT DEFAULT (EXTRACT(EPOCH FROM NOW())) NOT NULL,
|
||||||
|
last_edited_by TEXT DEFAULT 'system' NOT NULL,
|
||||||
|
PRIMARY KEY (id, site_id)
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) InitializeCollectionsTable(ctx context.Context) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, initializeCollectionsTable)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
const initializeSchema = `-- name: InitializeSchema :exec
|
const initializeSchema = `-- name: InitializeSchema :exec
|
||||||
CREATE TABLE IF NOT EXISTS content (
|
CREATE TABLE IF NOT EXISTS content (
|
||||||
id TEXT NOT NULL,
|
id TEXT NOT NULL,
|
||||||
|
|||||||
31
internal/db/queries/collection_item_versions.sql
Normal file
31
internal/db/queries/collection_item_versions.sql
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
-- Collection item versions table queries
|
||||||
|
|
||||||
|
-- name: CreateCollectionItemVersion :exec
|
||||||
|
INSERT INTO collection_item_versions (item_id, collection_id, site_id, html_content, template_id, position, created_by)
|
||||||
|
VALUES (sqlc.arg(item_id), sqlc.arg(collection_id), sqlc.arg(site_id), sqlc.arg(html_content), sqlc.arg(template_id), sqlc.arg(position), sqlc.arg(created_by));
|
||||||
|
|
||||||
|
-- name: GetCollectionItemVersionHistory :many
|
||||||
|
SELECT version_id, item_id, collection_id, site_id, html_content, template_id, position, created_at, created_by
|
||||||
|
FROM collection_item_versions
|
||||||
|
WHERE item_id = sqlc.arg(item_id) AND collection_id = sqlc.arg(collection_id) AND site_id = sqlc.arg(site_id)
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT sqlc.arg(limit_count);
|
||||||
|
|
||||||
|
-- name: GetCollectionItemVersion :one
|
||||||
|
SELECT version_id, item_id, collection_id, site_id, html_content, template_id, position, created_at, created_by
|
||||||
|
FROM collection_item_versions
|
||||||
|
WHERE version_id = sqlc.arg(version_id);
|
||||||
|
|
||||||
|
-- name: GetAllCollectionItemVersionsForSite :many
|
||||||
|
SELECT
|
||||||
|
civ.version_id, civ.item_id, civ.collection_id, civ.site_id, civ.html_content, civ.template_id, civ.position, civ.created_at, civ.created_by,
|
||||||
|
ci.html_content as current_html_content, ci.position as current_position
|
||||||
|
FROM collection_item_versions civ
|
||||||
|
LEFT JOIN collection_items ci ON civ.item_id = ci.item_id AND civ.collection_id = ci.collection_id AND civ.site_id = ci.site_id
|
||||||
|
WHERE civ.site_id = sqlc.arg(site_id)
|
||||||
|
ORDER BY civ.created_at DESC
|
||||||
|
LIMIT sqlc.arg(limit_count);
|
||||||
|
|
||||||
|
-- name: DeleteOldCollectionItemVersions :exec
|
||||||
|
DELETE FROM collection_item_versions
|
||||||
|
WHERE created_at < sqlc.arg(created_before) AND site_id = sqlc.arg(site_id);
|
||||||
57
internal/db/queries/collection_items.sql
Normal file
57
internal/db/queries/collection_items.sql
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
-- Collection items table queries
|
||||||
|
|
||||||
|
-- name: GetCollectionItem :one
|
||||||
|
SELECT item_id, collection_id, site_id, template_id, html_content, position, created_at, updated_at, last_edited_by
|
||||||
|
FROM collection_items
|
||||||
|
WHERE item_id = sqlc.arg(item_id) AND collection_id = sqlc.arg(collection_id) AND site_id = sqlc.arg(site_id);
|
||||||
|
|
||||||
|
-- name: GetCollectionItems :many
|
||||||
|
SELECT item_id, collection_id, site_id, template_id, html_content, position, created_at, updated_at, last_edited_by
|
||||||
|
FROM collection_items
|
||||||
|
WHERE collection_id = sqlc.arg(collection_id) AND site_id = sqlc.arg(site_id)
|
||||||
|
ORDER BY position ASC;
|
||||||
|
|
||||||
|
-- name: GetCollectionItemsWithTemplate :many
|
||||||
|
SELECT
|
||||||
|
ci.item_id, ci.collection_id, ci.site_id, ci.template_id, ci.html_content, ci.position, ci.created_at, ci.updated_at, ci.last_edited_by,
|
||||||
|
ct.name as template_name, ct.html_template, ct.is_default
|
||||||
|
FROM collection_items ci
|
||||||
|
JOIN collection_templates ct ON ci.template_id = ct.template_id
|
||||||
|
WHERE ci.collection_id = sqlc.arg(collection_id) AND ci.site_id = sqlc.arg(site_id)
|
||||||
|
ORDER BY ci.position ASC;
|
||||||
|
|
||||||
|
-- name: GetMaxPosition :one
|
||||||
|
SELECT COALESCE(MAX(position), 0) as max_position
|
||||||
|
FROM collection_items
|
||||||
|
WHERE collection_id = sqlc.arg(collection_id) AND site_id = sqlc.arg(site_id);
|
||||||
|
|
||||||
|
-- name: CreateCollectionItem :one
|
||||||
|
INSERT INTO collection_items (item_id, collection_id, site_id, template_id, html_content, position, last_edited_by)
|
||||||
|
VALUES (sqlc.arg(item_id), sqlc.arg(collection_id), sqlc.arg(site_id), sqlc.arg(template_id), sqlc.arg(html_content), sqlc.arg(position), sqlc.arg(last_edited_by))
|
||||||
|
RETURNING item_id, collection_id, site_id, template_id, html_content, position, created_at, updated_at, last_edited_by;
|
||||||
|
|
||||||
|
-- name: UpdateCollectionItem :one
|
||||||
|
UPDATE collection_items
|
||||||
|
SET html_content = sqlc.arg(html_content), last_edited_by = sqlc.arg(last_edited_by)
|
||||||
|
WHERE item_id = sqlc.arg(item_id) AND collection_id = sqlc.arg(collection_id) AND site_id = sqlc.arg(site_id)
|
||||||
|
RETURNING item_id, collection_id, site_id, template_id, html_content, position, created_at, updated_at, last_edited_by;
|
||||||
|
|
||||||
|
-- name: UpdateCollectionItemPosition :exec
|
||||||
|
UPDATE collection_items
|
||||||
|
SET position = sqlc.arg(position)
|
||||||
|
WHERE item_id = sqlc.arg(item_id) AND collection_id = sqlc.arg(collection_id) AND site_id = sqlc.arg(site_id);
|
||||||
|
|
||||||
|
-- name: ReorderCollectionItems :exec
|
||||||
|
UPDATE collection_items
|
||||||
|
SET position = position + sqlc.arg(position_delta)
|
||||||
|
WHERE collection_id = sqlc.arg(collection_id) AND site_id = sqlc.arg(site_id)
|
||||||
|
AND position >= sqlc.arg(start_position);
|
||||||
|
|
||||||
|
-- name: DeleteCollectionItem :exec
|
||||||
|
DELETE FROM collection_items
|
||||||
|
WHERE item_id = sqlc.arg(item_id) AND collection_id = sqlc.arg(collection_id) AND site_id = sqlc.arg(site_id);
|
||||||
|
|
||||||
|
-- name: DeleteCollectionItems :exec
|
||||||
|
DELETE FROM collection_items
|
||||||
|
WHERE collection_id = sqlc.arg(collection_id) AND site_id = sqlc.arg(site_id);
|
||||||
|
|
||||||
45
internal/db/queries/collection_templates.sql
Normal file
45
internal/db/queries/collection_templates.sql
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
-- Collection templates table queries
|
||||||
|
|
||||||
|
-- name: GetCollectionTemplate :one
|
||||||
|
SELECT template_id, collection_id, site_id, name, html_template, is_default, created_at
|
||||||
|
FROM collection_templates
|
||||||
|
WHERE template_id = sqlc.arg(template_id);
|
||||||
|
|
||||||
|
-- name: GetCollectionTemplates :many
|
||||||
|
SELECT template_id, collection_id, site_id, name, html_template, is_default, created_at
|
||||||
|
FROM collection_templates
|
||||||
|
WHERE collection_id = sqlc.arg(collection_id) AND site_id = sqlc.arg(site_id)
|
||||||
|
ORDER BY is_default DESC, created_at ASC;
|
||||||
|
|
||||||
|
-- name: GetDefaultTemplate :one
|
||||||
|
SELECT template_id, collection_id, site_id, name, html_template, is_default, created_at
|
||||||
|
FROM collection_templates
|
||||||
|
WHERE collection_id = sqlc.arg(collection_id) AND site_id = sqlc.arg(site_id) AND is_default = TRUE
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
-- name: CreateCollectionTemplate :one
|
||||||
|
INSERT INTO collection_templates (collection_id, site_id, name, html_template, is_default)
|
||||||
|
VALUES (sqlc.arg(collection_id), sqlc.arg(site_id), sqlc.arg(name), sqlc.arg(html_template), sqlc.arg(is_default))
|
||||||
|
RETURNING template_id, collection_id, site_id, name, html_template, is_default, created_at;
|
||||||
|
|
||||||
|
-- name: UpdateCollectionTemplate :one
|
||||||
|
UPDATE collection_templates
|
||||||
|
SET name = sqlc.arg(name), html_template = sqlc.arg(html_template), is_default = sqlc.arg(is_default)
|
||||||
|
WHERE template_id = sqlc.arg(template_id)
|
||||||
|
RETURNING template_id, collection_id, site_id, name, html_template, is_default, created_at;
|
||||||
|
|
||||||
|
-- name: SetTemplateAsDefault :exec
|
||||||
|
UPDATE collection_templates
|
||||||
|
SET is_default = CASE
|
||||||
|
WHEN template_id = sqlc.arg(template_id) THEN TRUE
|
||||||
|
ELSE FALSE
|
||||||
|
END
|
||||||
|
WHERE collection_id = sqlc.arg(collection_id) AND site_id = sqlc.arg(site_id);
|
||||||
|
|
||||||
|
-- name: DeleteCollectionTemplate :exec
|
||||||
|
DELETE FROM collection_templates
|
||||||
|
WHERE template_id = sqlc.arg(template_id);
|
||||||
|
|
||||||
|
-- name: DeleteCollectionTemplates :exec
|
||||||
|
DELETE FROM collection_templates
|
||||||
|
WHERE collection_id = sqlc.arg(collection_id) AND site_id = sqlc.arg(site_id);
|
||||||
39
internal/db/queries/collections.sql
Normal file
39
internal/db/queries/collections.sql
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
-- Collections table queries
|
||||||
|
|
||||||
|
-- name: GetCollection :one
|
||||||
|
SELECT id, site_id, container_html, created_at, updated_at, last_edited_by
|
||||||
|
FROM collections
|
||||||
|
WHERE id = sqlc.arg(id) AND site_id = sqlc.arg(site_id);
|
||||||
|
|
||||||
|
-- name: GetAllCollections :many
|
||||||
|
SELECT id, site_id, container_html, created_at, updated_at, last_edited_by
|
||||||
|
FROM collections
|
||||||
|
WHERE site_id = sqlc.arg(site_id)
|
||||||
|
ORDER BY updated_at DESC;
|
||||||
|
|
||||||
|
-- name: CreateCollection :one
|
||||||
|
INSERT INTO collections (id, site_id, container_html, last_edited_by)
|
||||||
|
VALUES (sqlc.arg(id), sqlc.arg(site_id), sqlc.arg(container_html), sqlc.arg(last_edited_by))
|
||||||
|
RETURNING id, site_id, container_html, created_at, updated_at, last_edited_by;
|
||||||
|
|
||||||
|
-- name: UpdateCollection :one
|
||||||
|
UPDATE collections
|
||||||
|
SET container_html = sqlc.arg(container_html), last_edited_by = sqlc.arg(last_edited_by)
|
||||||
|
WHERE id = sqlc.arg(id) AND site_id = sqlc.arg(site_id)
|
||||||
|
RETURNING id, site_id, container_html, created_at, updated_at, last_edited_by;
|
||||||
|
|
||||||
|
-- name: UpsertCollection :one
|
||||||
|
INSERT INTO collections (id, site_id, container_html, last_edited_by)
|
||||||
|
VALUES (sqlc.arg(id), sqlc.arg(site_id), sqlc.arg(container_html), sqlc.arg(last_edited_by))
|
||||||
|
ON CONFLICT(id, site_id) DO UPDATE SET
|
||||||
|
container_html = EXCLUDED.container_html,
|
||||||
|
last_edited_by = EXCLUDED.last_edited_by
|
||||||
|
RETURNING id, site_id, container_html, created_at, updated_at, last_edited_by;
|
||||||
|
|
||||||
|
-- name: DeleteCollection :exec
|
||||||
|
DELETE FROM collections
|
||||||
|
WHERE id = sqlc.arg(id) AND site_id = sqlc.arg(site_id);
|
||||||
|
|
||||||
|
-- name: DeleteAllSiteCollections :exec
|
||||||
|
DELETE FROM collections
|
||||||
|
WHERE site_id = sqlc.arg(site_id);
|
||||||
197
internal/db/sqlite/collection_item_versions.sql.go
Normal file
197
internal/db/sqlite/collection_item_versions.sql.go
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: collection_item_versions.sql
|
||||||
|
|
||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createCollectionItemVersion = `-- name: CreateCollectionItemVersion :exec
|
||||||
|
|
||||||
|
INSERT INTO collection_item_versions (item_id, collection_id, site_id, html_content, template_id, position, created_by)
|
||||||
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateCollectionItemVersionParams struct {
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
HtmlContent string `json:"html_content"`
|
||||||
|
TemplateID int64 `json:"template_id"`
|
||||||
|
Position int64 `json:"position"`
|
||||||
|
CreatedBy string `json:"created_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collection item versions table queries
|
||||||
|
func (q *Queries) CreateCollectionItemVersion(ctx context.Context, arg CreateCollectionItemVersionParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, createCollectionItemVersion,
|
||||||
|
arg.ItemID,
|
||||||
|
arg.CollectionID,
|
||||||
|
arg.SiteID,
|
||||||
|
arg.HtmlContent,
|
||||||
|
arg.TemplateID,
|
||||||
|
arg.Position,
|
||||||
|
arg.CreatedBy,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteOldCollectionItemVersions = `-- name: DeleteOldCollectionItemVersions :exec
|
||||||
|
DELETE FROM collection_item_versions
|
||||||
|
WHERE created_at < ?1 AND site_id = ?2
|
||||||
|
`
|
||||||
|
|
||||||
|
type DeleteOldCollectionItemVersionsParams struct {
|
||||||
|
CreatedBefore int64 `json:"created_before"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) DeleteOldCollectionItemVersions(ctx context.Context, arg DeleteOldCollectionItemVersionsParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, deleteOldCollectionItemVersions, arg.CreatedBefore, arg.SiteID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAllCollectionItemVersionsForSite = `-- name: GetAllCollectionItemVersionsForSite :many
|
||||||
|
SELECT
|
||||||
|
civ.version_id, civ.item_id, civ.collection_id, civ.site_id, civ.html_content, civ.template_id, civ.position, civ.created_at, civ.created_by,
|
||||||
|
ci.html_content as current_html_content, ci.position as current_position
|
||||||
|
FROM collection_item_versions civ
|
||||||
|
LEFT JOIN collection_items ci ON civ.item_id = ci.item_id AND civ.collection_id = ci.collection_id AND civ.site_id = ci.site_id
|
||||||
|
WHERE civ.site_id = ?1
|
||||||
|
ORDER BY civ.created_at DESC
|
||||||
|
LIMIT ?2
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetAllCollectionItemVersionsForSiteParams struct {
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
LimitCount int64 `json:"limit_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetAllCollectionItemVersionsForSiteRow struct {
|
||||||
|
VersionID int64 `json:"version_id"`
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
HtmlContent string `json:"html_content"`
|
||||||
|
TemplateID int64 `json:"template_id"`
|
||||||
|
Position int64 `json:"position"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
CreatedBy string `json:"created_by"`
|
||||||
|
CurrentHtmlContent sql.NullString `json:"current_html_content"`
|
||||||
|
CurrentPosition sql.NullInt64 `json:"current_position"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetAllCollectionItemVersionsForSite(ctx context.Context, arg GetAllCollectionItemVersionsForSiteParams) ([]GetAllCollectionItemVersionsForSiteRow, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, getAllCollectionItemVersionsForSite, arg.SiteID, arg.LimitCount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetAllCollectionItemVersionsForSiteRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetAllCollectionItemVersionsForSiteRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.VersionID,
|
||||||
|
&i.ItemID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.HtmlContent,
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.Position,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.CreatedBy,
|
||||||
|
&i.CurrentHtmlContent,
|
||||||
|
&i.CurrentPosition,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCollectionItemVersion = `-- name: GetCollectionItemVersion :one
|
||||||
|
SELECT version_id, item_id, collection_id, site_id, html_content, template_id, position, created_at, created_by
|
||||||
|
FROM collection_item_versions
|
||||||
|
WHERE version_id = ?1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetCollectionItemVersion(ctx context.Context, versionID int64) (CollectionItemVersion, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getCollectionItemVersion, versionID)
|
||||||
|
var i CollectionItemVersion
|
||||||
|
err := row.Scan(
|
||||||
|
&i.VersionID,
|
||||||
|
&i.ItemID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.HtmlContent,
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.Position,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.CreatedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCollectionItemVersionHistory = `-- name: GetCollectionItemVersionHistory :many
|
||||||
|
SELECT version_id, item_id, collection_id, site_id, html_content, template_id, position, created_at, created_by
|
||||||
|
FROM collection_item_versions
|
||||||
|
WHERE item_id = ?1 AND collection_id = ?2 AND site_id = ?3
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT ?4
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetCollectionItemVersionHistoryParams struct {
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
LimitCount int64 `json:"limit_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetCollectionItemVersionHistory(ctx context.Context, arg GetCollectionItemVersionHistoryParams) ([]CollectionItemVersion, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, getCollectionItemVersionHistory,
|
||||||
|
arg.ItemID,
|
||||||
|
arg.CollectionID,
|
||||||
|
arg.SiteID,
|
||||||
|
arg.LimitCount,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []CollectionItemVersion
|
||||||
|
for rows.Next() {
|
||||||
|
var i CollectionItemVersion
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.VersionID,
|
||||||
|
&i.ItemID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.HtmlContent,
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.Position,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.CreatedBy,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
327
internal/db/sqlite/collection_items.sql.go
Normal file
327
internal/db/sqlite/collection_items.sql.go
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: collection_items.sql
|
||||||
|
|
||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createCollectionItem = `-- name: CreateCollectionItem :one
|
||||||
|
INSERT INTO collection_items (item_id, collection_id, site_id, template_id, html_content, position, last_edited_by)
|
||||||
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
|
||||||
|
RETURNING item_id, collection_id, site_id, template_id, html_content, position, created_at, updated_at, last_edited_by
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateCollectionItemParams struct {
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
TemplateID int64 `json:"template_id"`
|
||||||
|
HtmlContent string `json:"html_content"`
|
||||||
|
Position int64 `json:"position"`
|
||||||
|
LastEditedBy string `json:"last_edited_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateCollectionItem(ctx context.Context, arg CreateCollectionItemParams) (CollectionItem, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, createCollectionItem,
|
||||||
|
arg.ItemID,
|
||||||
|
arg.CollectionID,
|
||||||
|
arg.SiteID,
|
||||||
|
arg.TemplateID,
|
||||||
|
arg.HtmlContent,
|
||||||
|
arg.Position,
|
||||||
|
arg.LastEditedBy,
|
||||||
|
)
|
||||||
|
var i CollectionItem
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ItemID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.HtmlContent,
|
||||||
|
&i.Position,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.LastEditedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteCollectionItem = `-- name: DeleteCollectionItem :exec
|
||||||
|
DELETE FROM collection_items
|
||||||
|
WHERE item_id = ?1 AND collection_id = ?2 AND site_id = ?3
|
||||||
|
`
|
||||||
|
|
||||||
|
type DeleteCollectionItemParams struct {
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) DeleteCollectionItem(ctx context.Context, arg DeleteCollectionItemParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, deleteCollectionItem, arg.ItemID, arg.CollectionID, arg.SiteID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteCollectionItems = `-- name: DeleteCollectionItems :exec
|
||||||
|
DELETE FROM collection_items
|
||||||
|
WHERE collection_id = ?1 AND site_id = ?2
|
||||||
|
`
|
||||||
|
|
||||||
|
type DeleteCollectionItemsParams struct {
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) DeleteCollectionItems(ctx context.Context, arg DeleteCollectionItemsParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, deleteCollectionItems, arg.CollectionID, arg.SiteID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCollectionItem = `-- name: GetCollectionItem :one
|
||||||
|
|
||||||
|
SELECT item_id, collection_id, site_id, template_id, html_content, position, created_at, updated_at, last_edited_by
|
||||||
|
FROM collection_items
|
||||||
|
WHERE item_id = ?1 AND collection_id = ?2 AND site_id = ?3
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetCollectionItemParams struct {
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collection items table queries
|
||||||
|
func (q *Queries) GetCollectionItem(ctx context.Context, arg GetCollectionItemParams) (CollectionItem, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getCollectionItem, arg.ItemID, arg.CollectionID, arg.SiteID)
|
||||||
|
var i CollectionItem
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ItemID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.HtmlContent,
|
||||||
|
&i.Position,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.LastEditedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCollectionItems = `-- name: GetCollectionItems :many
|
||||||
|
SELECT item_id, collection_id, site_id, template_id, html_content, position, created_at, updated_at, last_edited_by
|
||||||
|
FROM collection_items
|
||||||
|
WHERE collection_id = ?1 AND site_id = ?2
|
||||||
|
ORDER BY position ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetCollectionItemsParams struct {
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetCollectionItems(ctx context.Context, arg GetCollectionItemsParams) ([]CollectionItem, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, getCollectionItems, arg.CollectionID, arg.SiteID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []CollectionItem
|
||||||
|
for rows.Next() {
|
||||||
|
var i CollectionItem
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ItemID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.HtmlContent,
|
||||||
|
&i.Position,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.LastEditedBy,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCollectionItemsWithTemplate = `-- name: GetCollectionItemsWithTemplate :many
|
||||||
|
SELECT
|
||||||
|
ci.item_id, ci.collection_id, ci.site_id, ci.template_id, ci.html_content, ci.position, ci.created_at, ci.updated_at, ci.last_edited_by,
|
||||||
|
ct.name as template_name, ct.html_template, ct.is_default
|
||||||
|
FROM collection_items ci
|
||||||
|
JOIN collection_templates ct ON ci.template_id = ct.template_id
|
||||||
|
WHERE ci.collection_id = ?1 AND ci.site_id = ?2
|
||||||
|
ORDER BY ci.position ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetCollectionItemsWithTemplateParams struct {
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetCollectionItemsWithTemplateRow struct {
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
TemplateID int64 `json:"template_id"`
|
||||||
|
HtmlContent string `json:"html_content"`
|
||||||
|
Position int64 `json:"position"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
LastEditedBy string `json:"last_edited_by"`
|
||||||
|
TemplateName string `json:"template_name"`
|
||||||
|
HtmlTemplate string `json:"html_template"`
|
||||||
|
IsDefault int64 `json:"is_default"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetCollectionItemsWithTemplate(ctx context.Context, arg GetCollectionItemsWithTemplateParams) ([]GetCollectionItemsWithTemplateRow, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, getCollectionItemsWithTemplate, arg.CollectionID, arg.SiteID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetCollectionItemsWithTemplateRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetCollectionItemsWithTemplateRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ItemID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.HtmlContent,
|
||||||
|
&i.Position,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.LastEditedBy,
|
||||||
|
&i.TemplateName,
|
||||||
|
&i.HtmlTemplate,
|
||||||
|
&i.IsDefault,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMaxPosition = `-- name: GetMaxPosition :one
|
||||||
|
SELECT COALESCE(MAX(position), 0) as max_position
|
||||||
|
FROM collection_items
|
||||||
|
WHERE collection_id = ?1 AND site_id = ?2
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetMaxPositionParams struct {
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetMaxPosition(ctx context.Context, arg GetMaxPositionParams) (interface{}, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getMaxPosition, arg.CollectionID, arg.SiteID)
|
||||||
|
var max_position interface{}
|
||||||
|
err := row.Scan(&max_position)
|
||||||
|
return max_position, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const reorderCollectionItems = `-- name: ReorderCollectionItems :exec
|
||||||
|
UPDATE collection_items
|
||||||
|
SET position = position + ?1
|
||||||
|
WHERE collection_id = ?2 AND site_id = ?3
|
||||||
|
AND position >= ?4
|
||||||
|
`
|
||||||
|
|
||||||
|
type ReorderCollectionItemsParams struct {
|
||||||
|
PositionDelta int64 `json:"position_delta"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
StartPosition int64 `json:"start_position"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) ReorderCollectionItems(ctx context.Context, arg ReorderCollectionItemsParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, reorderCollectionItems,
|
||||||
|
arg.PositionDelta,
|
||||||
|
arg.CollectionID,
|
||||||
|
arg.SiteID,
|
||||||
|
arg.StartPosition,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCollectionItem = `-- name: UpdateCollectionItem :one
|
||||||
|
UPDATE collection_items
|
||||||
|
SET html_content = ?1, last_edited_by = ?2
|
||||||
|
WHERE item_id = ?3 AND collection_id = ?4 AND site_id = ?5
|
||||||
|
RETURNING item_id, collection_id, site_id, template_id, html_content, position, created_at, updated_at, last_edited_by
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateCollectionItemParams struct {
|
||||||
|
HtmlContent string `json:"html_content"`
|
||||||
|
LastEditedBy string `json:"last_edited_by"`
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateCollectionItem(ctx context.Context, arg UpdateCollectionItemParams) (CollectionItem, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, updateCollectionItem,
|
||||||
|
arg.HtmlContent,
|
||||||
|
arg.LastEditedBy,
|
||||||
|
arg.ItemID,
|
||||||
|
arg.CollectionID,
|
||||||
|
arg.SiteID,
|
||||||
|
)
|
||||||
|
var i CollectionItem
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ItemID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.HtmlContent,
|
||||||
|
&i.Position,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.LastEditedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCollectionItemPosition = `-- name: UpdateCollectionItemPosition :exec
|
||||||
|
UPDATE collection_items
|
||||||
|
SET position = ?1
|
||||||
|
WHERE item_id = ?2 AND collection_id = ?3 AND site_id = ?4
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateCollectionItemPositionParams struct {
|
||||||
|
Position int64 `json:"position"`
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateCollectionItemPosition(ctx context.Context, arg UpdateCollectionItemPositionParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, updateCollectionItemPosition,
|
||||||
|
arg.Position,
|
||||||
|
arg.ItemID,
|
||||||
|
arg.CollectionID,
|
||||||
|
arg.SiteID,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
217
internal/db/sqlite/collection_templates.sql.go
Normal file
217
internal/db/sqlite/collection_templates.sql.go
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: collection_templates.sql
|
||||||
|
|
||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createCollectionTemplate = `-- name: CreateCollectionTemplate :one
|
||||||
|
INSERT INTO collection_templates (collection_id, site_id, name, html_template, is_default)
|
||||||
|
VALUES (?1, ?2, ?3, ?4, ?5)
|
||||||
|
RETURNING template_id, collection_id, site_id, name, html_template, is_default, created_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateCollectionTemplateParams struct {
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
HtmlTemplate string `json:"html_template"`
|
||||||
|
IsDefault int64 `json:"is_default"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateCollectionTemplate(ctx context.Context, arg CreateCollectionTemplateParams) (CollectionTemplate, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, createCollectionTemplate,
|
||||||
|
arg.CollectionID,
|
||||||
|
arg.SiteID,
|
||||||
|
arg.Name,
|
||||||
|
arg.HtmlTemplate,
|
||||||
|
arg.IsDefault,
|
||||||
|
)
|
||||||
|
var i CollectionTemplate
|
||||||
|
err := row.Scan(
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.Name,
|
||||||
|
&i.HtmlTemplate,
|
||||||
|
&i.IsDefault,
|
||||||
|
&i.CreatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteCollectionTemplate = `-- name: DeleteCollectionTemplate :exec
|
||||||
|
DELETE FROM collection_templates
|
||||||
|
WHERE template_id = ?1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteCollectionTemplate(ctx context.Context, templateID int64) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, deleteCollectionTemplate, templateID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteCollectionTemplates = `-- name: DeleteCollectionTemplates :exec
|
||||||
|
DELETE FROM collection_templates
|
||||||
|
WHERE collection_id = ?1 AND site_id = ?2
|
||||||
|
`
|
||||||
|
|
||||||
|
type DeleteCollectionTemplatesParams struct {
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) DeleteCollectionTemplates(ctx context.Context, arg DeleteCollectionTemplatesParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, deleteCollectionTemplates, arg.CollectionID, arg.SiteID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCollectionTemplate = `-- name: GetCollectionTemplate :one
|
||||||
|
|
||||||
|
SELECT template_id, collection_id, site_id, name, html_template, is_default, created_at
|
||||||
|
FROM collection_templates
|
||||||
|
WHERE template_id = ?1
|
||||||
|
`
|
||||||
|
|
||||||
|
// Collection templates table queries
|
||||||
|
func (q *Queries) GetCollectionTemplate(ctx context.Context, templateID int64) (CollectionTemplate, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getCollectionTemplate, templateID)
|
||||||
|
var i CollectionTemplate
|
||||||
|
err := row.Scan(
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.Name,
|
||||||
|
&i.HtmlTemplate,
|
||||||
|
&i.IsDefault,
|
||||||
|
&i.CreatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCollectionTemplates = `-- name: GetCollectionTemplates :many
|
||||||
|
SELECT template_id, collection_id, site_id, name, html_template, is_default, created_at
|
||||||
|
FROM collection_templates
|
||||||
|
WHERE collection_id = ?1 AND site_id = ?2
|
||||||
|
ORDER BY is_default DESC, created_at ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetCollectionTemplatesParams struct {
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetCollectionTemplates(ctx context.Context, arg GetCollectionTemplatesParams) ([]CollectionTemplate, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, getCollectionTemplates, arg.CollectionID, arg.SiteID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []CollectionTemplate
|
||||||
|
for rows.Next() {
|
||||||
|
var i CollectionTemplate
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.Name,
|
||||||
|
&i.HtmlTemplate,
|
||||||
|
&i.IsDefault,
|
||||||
|
&i.CreatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDefaultTemplate = `-- name: GetDefaultTemplate :one
|
||||||
|
SELECT template_id, collection_id, site_id, name, html_template, is_default, created_at
|
||||||
|
FROM collection_templates
|
||||||
|
WHERE collection_id = ?1 AND site_id = ?2 AND is_default = TRUE
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetDefaultTemplateParams struct {
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetDefaultTemplate(ctx context.Context, arg GetDefaultTemplateParams) (CollectionTemplate, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getDefaultTemplate, arg.CollectionID, arg.SiteID)
|
||||||
|
var i CollectionTemplate
|
||||||
|
err := row.Scan(
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.Name,
|
||||||
|
&i.HtmlTemplate,
|
||||||
|
&i.IsDefault,
|
||||||
|
&i.CreatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const setTemplateAsDefault = `-- name: SetTemplateAsDefault :exec
|
||||||
|
UPDATE collection_templates
|
||||||
|
SET is_default = CASE
|
||||||
|
WHEN template_id = ?1 THEN TRUE
|
||||||
|
ELSE FALSE
|
||||||
|
END
|
||||||
|
WHERE collection_id = ?2 AND site_id = ?3
|
||||||
|
`
|
||||||
|
|
||||||
|
type SetTemplateAsDefaultParams struct {
|
||||||
|
TemplateID int64 `json:"template_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) SetTemplateAsDefault(ctx context.Context, arg SetTemplateAsDefaultParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, setTemplateAsDefault, arg.TemplateID, arg.CollectionID, arg.SiteID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCollectionTemplate = `-- name: UpdateCollectionTemplate :one
|
||||||
|
UPDATE collection_templates
|
||||||
|
SET name = ?1, html_template = ?2, is_default = ?3
|
||||||
|
WHERE template_id = ?4
|
||||||
|
RETURNING template_id, collection_id, site_id, name, html_template, is_default, created_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateCollectionTemplateParams struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
HtmlTemplate string `json:"html_template"`
|
||||||
|
IsDefault int64 `json:"is_default"`
|
||||||
|
TemplateID int64 `json:"template_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateCollectionTemplate(ctx context.Context, arg UpdateCollectionTemplateParams) (CollectionTemplate, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, updateCollectionTemplate,
|
||||||
|
arg.Name,
|
||||||
|
arg.HtmlTemplate,
|
||||||
|
arg.IsDefault,
|
||||||
|
arg.TemplateID,
|
||||||
|
)
|
||||||
|
var i CollectionTemplate
|
||||||
|
err := row.Scan(
|
||||||
|
&i.TemplateID,
|
||||||
|
&i.CollectionID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.Name,
|
||||||
|
&i.HtmlTemplate,
|
||||||
|
&i.IsDefault,
|
||||||
|
&i.CreatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
199
internal/db/sqlite/collections.sql.go
Normal file
199
internal/db/sqlite/collections.sql.go
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: collections.sql
|
||||||
|
|
||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createCollection = `-- name: CreateCollection :one
|
||||||
|
INSERT INTO collections (id, site_id, container_html, last_edited_by)
|
||||||
|
VALUES (?1, ?2, ?3, ?4)
|
||||||
|
RETURNING id, site_id, container_html, created_at, updated_at, last_edited_by
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateCollectionParams struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
ContainerHtml string `json:"container_html"`
|
||||||
|
LastEditedBy string `json:"last_edited_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateCollection(ctx context.Context, arg CreateCollectionParams) (Collection, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, createCollection,
|
||||||
|
arg.ID,
|
||||||
|
arg.SiteID,
|
||||||
|
arg.ContainerHtml,
|
||||||
|
arg.LastEditedBy,
|
||||||
|
)
|
||||||
|
var i Collection
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.ContainerHtml,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.LastEditedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteAllSiteCollections = `-- name: DeleteAllSiteCollections :exec
|
||||||
|
DELETE FROM collections
|
||||||
|
WHERE site_id = ?1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteAllSiteCollections(ctx context.Context, siteID string) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, deleteAllSiteCollections, siteID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteCollection = `-- name: DeleteCollection :exec
|
||||||
|
DELETE FROM collections
|
||||||
|
WHERE id = ?1 AND site_id = ?2
|
||||||
|
`
|
||||||
|
|
||||||
|
type DeleteCollectionParams struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) DeleteCollection(ctx context.Context, arg DeleteCollectionParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, deleteCollection, arg.ID, arg.SiteID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAllCollections = `-- name: GetAllCollections :many
|
||||||
|
SELECT id, site_id, container_html, created_at, updated_at, last_edited_by
|
||||||
|
FROM collections
|
||||||
|
WHERE site_id = ?1
|
||||||
|
ORDER BY updated_at DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetAllCollections(ctx context.Context, siteID string) ([]Collection, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, getAllCollections, siteID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []Collection
|
||||||
|
for rows.Next() {
|
||||||
|
var i Collection
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.ContainerHtml,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.LastEditedBy,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCollection = `-- name: GetCollection :one
|
||||||
|
|
||||||
|
SELECT id, site_id, container_html, created_at, updated_at, last_edited_by
|
||||||
|
FROM collections
|
||||||
|
WHERE id = ?1 AND site_id = ?2
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetCollectionParams struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collections table queries
|
||||||
|
func (q *Queries) GetCollection(ctx context.Context, arg GetCollectionParams) (Collection, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getCollection, arg.ID, arg.SiteID)
|
||||||
|
var i Collection
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.ContainerHtml,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.LastEditedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCollection = `-- name: UpdateCollection :one
|
||||||
|
UPDATE collections
|
||||||
|
SET container_html = ?1, last_edited_by = ?2
|
||||||
|
WHERE id = ?3 AND site_id = ?4
|
||||||
|
RETURNING id, site_id, container_html, created_at, updated_at, last_edited_by
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateCollectionParams struct {
|
||||||
|
ContainerHtml string `json:"container_html"`
|
||||||
|
LastEditedBy string `json:"last_edited_by"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateCollection(ctx context.Context, arg UpdateCollectionParams) (Collection, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, updateCollection,
|
||||||
|
arg.ContainerHtml,
|
||||||
|
arg.LastEditedBy,
|
||||||
|
arg.ID,
|
||||||
|
arg.SiteID,
|
||||||
|
)
|
||||||
|
var i Collection
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.ContainerHtml,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.LastEditedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const upsertCollection = `-- name: UpsertCollection :one
|
||||||
|
INSERT INTO collections (id, site_id, container_html, last_edited_by)
|
||||||
|
VALUES (?1, ?2, ?3, ?4)
|
||||||
|
ON CONFLICT(id, site_id) DO UPDATE SET
|
||||||
|
container_html = EXCLUDED.container_html,
|
||||||
|
last_edited_by = EXCLUDED.last_edited_by
|
||||||
|
RETURNING id, site_id, container_html, created_at, updated_at, last_edited_by
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpsertCollectionParams struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
ContainerHtml string `json:"container_html"`
|
||||||
|
LastEditedBy string `json:"last_edited_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpsertCollection(ctx context.Context, arg UpsertCollectionParams) (Collection, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, upsertCollection,
|
||||||
|
arg.ID,
|
||||||
|
arg.SiteID,
|
||||||
|
arg.ContainerHtml,
|
||||||
|
arg.LastEditedBy,
|
||||||
|
)
|
||||||
|
var i Collection
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.SiteID,
|
||||||
|
&i.ContainerHtml,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.LastEditedBy,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
@@ -8,6 +8,49 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Collection struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
ContainerHtml string `json:"container_html"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
LastEditedBy string `json:"last_edited_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CollectionItem struct {
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
TemplateID int64 `json:"template_id"`
|
||||||
|
HtmlContent string `json:"html_content"`
|
||||||
|
Position int64 `json:"position"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
LastEditedBy string `json:"last_edited_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CollectionItemVersion struct {
|
||||||
|
VersionID int64 `json:"version_id"`
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
HtmlContent string `json:"html_content"`
|
||||||
|
TemplateID int64 `json:"template_id"`
|
||||||
|
Position int64 `json:"position"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
CreatedBy string `json:"created_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CollectionTemplate struct {
|
||||||
|
TemplateID int64 `json:"template_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
HtmlTemplate string `json:"html_template"`
|
||||||
|
IsDefault int64 `json:"is_default"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
type Content struct {
|
type Content struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
SiteID string `json:"site_id"`
|
SiteID string `json:"site_id"`
|
||||||
|
|||||||
@@ -9,20 +9,58 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Querier interface {
|
type Querier interface {
|
||||||
|
CreateCollection(ctx context.Context, arg CreateCollectionParams) (Collection, error)
|
||||||
|
CreateCollectionItem(ctx context.Context, arg CreateCollectionItemParams) (CollectionItem, error)
|
||||||
|
// Collection item versions table queries
|
||||||
|
CreateCollectionItemVersion(ctx context.Context, arg CreateCollectionItemVersionParams) error
|
||||||
|
CreateCollectionTemplate(ctx context.Context, arg CreateCollectionTemplateParams) (CollectionTemplate, error)
|
||||||
CreateContent(ctx context.Context, arg CreateContentParams) (Content, error)
|
CreateContent(ctx context.Context, arg CreateContentParams) (Content, error)
|
||||||
CreateContentVersion(ctx context.Context, arg CreateContentVersionParams) error
|
CreateContentVersion(ctx context.Context, arg CreateContentVersionParams) error
|
||||||
|
DeleteAllSiteCollections(ctx context.Context, siteID string) error
|
||||||
DeleteAllSiteContent(ctx context.Context, siteID string) error
|
DeleteAllSiteContent(ctx context.Context, siteID string) error
|
||||||
|
DeleteCollection(ctx context.Context, arg DeleteCollectionParams) error
|
||||||
|
DeleteCollectionItem(ctx context.Context, arg DeleteCollectionItemParams) error
|
||||||
|
DeleteCollectionItems(ctx context.Context, arg DeleteCollectionItemsParams) error
|
||||||
|
DeleteCollectionTemplate(ctx context.Context, templateID int64) error
|
||||||
|
DeleteCollectionTemplates(ctx context.Context, arg DeleteCollectionTemplatesParams) error
|
||||||
DeleteContent(ctx context.Context, arg DeleteContentParams) error
|
DeleteContent(ctx context.Context, arg DeleteContentParams) error
|
||||||
|
DeleteOldCollectionItemVersions(ctx context.Context, arg DeleteOldCollectionItemVersionsParams) error
|
||||||
DeleteOldVersions(ctx context.Context, arg DeleteOldVersionsParams) error
|
DeleteOldVersions(ctx context.Context, arg DeleteOldVersionsParams) error
|
||||||
|
GetAllCollectionItemVersionsForSite(ctx context.Context, arg GetAllCollectionItemVersionsForSiteParams) ([]GetAllCollectionItemVersionsForSiteRow, error)
|
||||||
|
GetAllCollections(ctx context.Context, siteID string) ([]Collection, error)
|
||||||
GetAllContent(ctx context.Context, siteID string) ([]Content, error)
|
GetAllContent(ctx context.Context, siteID string) ([]Content, error)
|
||||||
GetAllVersionsForSite(ctx context.Context, arg GetAllVersionsForSiteParams) ([]GetAllVersionsForSiteRow, error)
|
GetAllVersionsForSite(ctx context.Context, arg GetAllVersionsForSiteParams) ([]GetAllVersionsForSiteRow, error)
|
||||||
GetBulkContent(ctx context.Context, arg GetBulkContentParams) ([]Content, error)
|
GetBulkContent(ctx context.Context, arg GetBulkContentParams) ([]Content, error)
|
||||||
|
// Collections table queries
|
||||||
|
GetCollection(ctx context.Context, arg GetCollectionParams) (Collection, error)
|
||||||
|
// Collection items table queries
|
||||||
|
GetCollectionItem(ctx context.Context, arg GetCollectionItemParams) (CollectionItem, error)
|
||||||
|
GetCollectionItemVersion(ctx context.Context, versionID int64) (CollectionItemVersion, error)
|
||||||
|
GetCollectionItemVersionHistory(ctx context.Context, arg GetCollectionItemVersionHistoryParams) ([]CollectionItemVersion, error)
|
||||||
|
GetCollectionItems(ctx context.Context, arg GetCollectionItemsParams) ([]CollectionItem, error)
|
||||||
|
GetCollectionItemsWithTemplate(ctx context.Context, arg GetCollectionItemsWithTemplateParams) ([]GetCollectionItemsWithTemplateRow, error)
|
||||||
|
// Collection templates table queries
|
||||||
|
GetCollectionTemplate(ctx context.Context, templateID int64) (CollectionTemplate, error)
|
||||||
|
GetCollectionTemplates(ctx context.Context, arg GetCollectionTemplatesParams) ([]CollectionTemplate, error)
|
||||||
GetContent(ctx context.Context, arg GetContentParams) (Content, error)
|
GetContent(ctx context.Context, arg GetContentParams) (Content, error)
|
||||||
GetContentVersion(ctx context.Context, versionID int64) (ContentVersion, error)
|
GetContentVersion(ctx context.Context, versionID int64) (ContentVersion, error)
|
||||||
GetContentVersionHistory(ctx context.Context, arg GetContentVersionHistoryParams) ([]ContentVersion, error)
|
GetContentVersionHistory(ctx context.Context, arg GetContentVersionHistoryParams) ([]ContentVersion, error)
|
||||||
|
GetDefaultTemplate(ctx context.Context, arg GetDefaultTemplateParams) (CollectionTemplate, error)
|
||||||
|
GetMaxPosition(ctx context.Context, arg GetMaxPositionParams) (interface{}, error)
|
||||||
|
InitializeCollectionItemVersionsTable(ctx context.Context) error
|
||||||
|
InitializeCollectionItemsTable(ctx context.Context) error
|
||||||
|
InitializeCollectionTemplatesTable(ctx context.Context) error
|
||||||
|
InitializeCollectionsTable(ctx context.Context) error
|
||||||
InitializeSchema(ctx context.Context) error
|
InitializeSchema(ctx context.Context) error
|
||||||
InitializeVersionsTable(ctx context.Context) error
|
InitializeVersionsTable(ctx context.Context) error
|
||||||
|
ReorderCollectionItems(ctx context.Context, arg ReorderCollectionItemsParams) error
|
||||||
|
SetTemplateAsDefault(ctx context.Context, arg SetTemplateAsDefaultParams) error
|
||||||
|
UpdateCollection(ctx context.Context, arg UpdateCollectionParams) (Collection, error)
|
||||||
|
UpdateCollectionItem(ctx context.Context, arg UpdateCollectionItemParams) (CollectionItem, error)
|
||||||
|
UpdateCollectionItemPosition(ctx context.Context, arg UpdateCollectionItemPositionParams) error
|
||||||
|
UpdateCollectionTemplate(ctx context.Context, arg UpdateCollectionTemplateParams) (CollectionTemplate, error)
|
||||||
UpdateContent(ctx context.Context, arg UpdateContentParams) (Content, error)
|
UpdateContent(ctx context.Context, arg UpdateContentParams) (Content, error)
|
||||||
|
UpsertCollection(ctx context.Context, arg UpsertCollectionParams) (Collection, error)
|
||||||
UpsertContent(ctx context.Context, arg UpsertContentParams) (Content, error)
|
UpsertContent(ctx context.Context, arg UpsertContentParams) (Content, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,11 +22,78 @@ CREATE TABLE content_versions (
|
|||||||
created_by TEXT DEFAULT 'system' NOT NULL
|
created_by TEXT DEFAULT 'system' NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Collections table - manages .insertr-add containers
|
||||||
|
CREATE TABLE collections (
|
||||||
|
id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
container_html TEXT NOT NULL,
|
||||||
|
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
updated_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
last_edited_by TEXT DEFAULT 'system' NOT NULL,
|
||||||
|
PRIMARY KEY (id, site_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Collection templates - multiple template variants per collection
|
||||||
|
CREATE TABLE collection_templates (
|
||||||
|
template_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
collection_id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
html_template TEXT NOT NULL,
|
||||||
|
is_default INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
FOREIGN KEY (collection_id, site_id) REFERENCES collections(id, site_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Collection items - individual items within collections
|
||||||
|
CREATE TABLE collection_items (
|
||||||
|
item_id TEXT NOT NULL,
|
||||||
|
collection_id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
template_id INTEGER NOT NULL,
|
||||||
|
html_content TEXT NOT NULL,
|
||||||
|
position INTEGER NOT NULL,
|
||||||
|
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
updated_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
last_edited_by TEXT DEFAULT 'system' NOT NULL,
|
||||||
|
PRIMARY KEY (item_id, collection_id, site_id),
|
||||||
|
FOREIGN KEY (collection_id, site_id) REFERENCES collections(id, site_id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (template_id) REFERENCES collection_templates(template_id) ON DELETE RESTRICT
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Collection item version history
|
||||||
|
CREATE TABLE collection_item_versions (
|
||||||
|
version_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
item_id TEXT NOT NULL,
|
||||||
|
collection_id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
html_content TEXT NOT NULL,
|
||||||
|
template_id INTEGER NOT NULL,
|
||||||
|
position INTEGER NOT NULL,
|
||||||
|
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
created_by TEXT DEFAULT 'system' NOT NULL,
|
||||||
|
FOREIGN KEY (item_id, collection_id, site_id) REFERENCES collection_items(item_id, collection_id, site_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
-- Indexes for performance
|
-- Indexes for performance
|
||||||
CREATE INDEX IF NOT EXISTS idx_content_site_id ON content(site_id);
|
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_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_content_versions_lookup ON content_versions(content_id, site_id, created_at DESC);
|
||||||
|
|
||||||
|
-- Collection indexes for performance
|
||||||
|
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);
|
||||||
|
|
||||||
|
-- Constraint to ensure only one default template per collection
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_collection_templates_one_default
|
||||||
|
ON collection_templates(collection_id, site_id)
|
||||||
|
WHERE is_default = 1;
|
||||||
|
|
||||||
-- Trigger to automatically update updated_at timestamp
|
-- Trigger to automatically update updated_at timestamp
|
||||||
CREATE TRIGGER IF NOT EXISTS update_content_updated_at
|
CREATE TRIGGER IF NOT EXISTS update_content_updated_at
|
||||||
AFTER UPDATE ON content
|
AFTER UPDATE ON content
|
||||||
@@ -34,3 +101,18 @@ FOR EACH ROW
|
|||||||
BEGIN
|
BEGIN
|
||||||
UPDATE content SET updated_at = strftime('%s', 'now') WHERE id = NEW.id AND site_id = NEW.site_id;
|
UPDATE content SET updated_at = strftime('%s', 'now') WHERE id = NEW.id AND site_id = NEW.site_id;
|
||||||
END;
|
END;
|
||||||
|
|
||||||
|
-- Triggers for collection timestamps
|
||||||
|
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;
|
||||||
@@ -21,6 +21,59 @@ CREATE TABLE IF NOT EXISTS content_versions (
|
|||||||
created_by TEXT DEFAULT 'system' NOT NULL
|
created_by TEXT DEFAULT 'system' NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- name: InitializeCollectionsTable :exec
|
||||||
|
CREATE TABLE IF NOT EXISTS collections (
|
||||||
|
id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
container_html TEXT NOT NULL,
|
||||||
|
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
updated_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
last_edited_by TEXT DEFAULT 'system' NOT NULL,
|
||||||
|
PRIMARY KEY (id, site_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- name: InitializeCollectionTemplatesTable :exec
|
||||||
|
CREATE TABLE IF NOT EXISTS collection_templates (
|
||||||
|
template_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
collection_id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
html_template TEXT NOT NULL,
|
||||||
|
is_default INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
FOREIGN KEY (collection_id, site_id) REFERENCES collections(id, site_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- name: InitializeCollectionItemsTable :exec
|
||||||
|
CREATE TABLE IF NOT EXISTS collection_items (
|
||||||
|
item_id TEXT NOT NULL,
|
||||||
|
collection_id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
template_id INTEGER NOT NULL,
|
||||||
|
html_content TEXT NOT NULL,
|
||||||
|
position INTEGER NOT NULL,
|
||||||
|
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
updated_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
last_edited_by TEXT DEFAULT 'system' NOT NULL,
|
||||||
|
PRIMARY KEY (item_id, collection_id, site_id),
|
||||||
|
FOREIGN KEY (collection_id, site_id) REFERENCES collections(id, site_id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (template_id) REFERENCES collection_templates(template_id) ON DELETE RESTRICT
|
||||||
|
);
|
||||||
|
|
||||||
|
-- name: InitializeCollectionItemVersionsTable :exec
|
||||||
|
CREATE TABLE IF NOT EXISTS collection_item_versions (
|
||||||
|
version_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
item_id TEXT NOT NULL,
|
||||||
|
collection_id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
html_content TEXT NOT NULL,
|
||||||
|
template_id INTEGER NOT NULL,
|
||||||
|
position INTEGER NOT NULL,
|
||||||
|
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
created_by TEXT DEFAULT 'system' NOT NULL,
|
||||||
|
FOREIGN KEY (item_id, collection_id, site_id) REFERENCES collection_items(item_id, collection_id, site_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
-- name: CreateContentSiteIndex :exec
|
-- name: CreateContentSiteIndex :exec
|
||||||
CREATE INDEX IF NOT EXISTS idx_content_site_id ON content(site_id);
|
CREATE INDEX IF NOT EXISTS idx_content_site_id ON content(site_id);
|
||||||
|
|
||||||
@@ -30,6 +83,32 @@ CREATE INDEX IF NOT EXISTS idx_content_updated_at ON content(updated_at);
|
|||||||
-- name: CreateVersionsLookupIndex :exec
|
-- name: CreateVersionsLookupIndex :exec
|
||||||
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_content_versions_lookup ON content_versions(content_id, site_id, created_at DESC);
|
||||||
|
|
||||||
|
-- name: CreateCollectionsSiteIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collections_site_id ON collections(site_id);
|
||||||
|
|
||||||
|
-- name: CreateCollectionsUpdatedAtIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collections_updated_at ON collections(updated_at);
|
||||||
|
|
||||||
|
-- name: CreateCollectionTemplatesLookupIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collection_templates_lookup ON collection_templates(collection_id, site_id);
|
||||||
|
|
||||||
|
-- name: CreateCollectionTemplatesDefaultIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collection_templates_default ON collection_templates(collection_id, site_id, is_default DESC);
|
||||||
|
|
||||||
|
-- name: CreateCollectionItemsLookupIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collection_items_lookup ON collection_items(collection_id, site_id, position);
|
||||||
|
|
||||||
|
-- name: CreateCollectionItemsTemplateIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collection_items_template ON collection_items(template_id);
|
||||||
|
|
||||||
|
-- name: CreateCollectionItemVersionsLookupIndex :exec
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_collection_item_versions_lookup ON collection_item_versions(item_id, collection_id, site_id, created_at DESC);
|
||||||
|
|
||||||
|
-- name: CreateCollectionTemplatesOneDefaultIndex :exec
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_collection_templates_one_default
|
||||||
|
ON collection_templates(collection_id, site_id)
|
||||||
|
WHERE is_default = 1;
|
||||||
|
|
||||||
-- name: CreateUpdateTrigger :exec
|
-- name: CreateUpdateTrigger :exec
|
||||||
CREATE TRIGGER IF NOT EXISTS update_content_updated_at
|
CREATE TRIGGER IF NOT EXISTS update_content_updated_at
|
||||||
AFTER UPDATE ON content
|
AFTER UPDATE ON content
|
||||||
@@ -37,3 +116,19 @@ FOR EACH ROW
|
|||||||
BEGIN
|
BEGIN
|
||||||
UPDATE content SET updated_at = strftime('%s', 'now') WHERE id = NEW.id AND site_id = NEW.site_id;
|
UPDATE content SET updated_at = strftime('%s', 'now') WHERE id = NEW.id AND site_id = NEW.site_id;
|
||||||
END;
|
END;
|
||||||
|
|
||||||
|
-- name: CreateCollectionsUpdateTrigger :exec
|
||||||
|
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;
|
||||||
|
|
||||||
|
-- name: CreateCollectionItemsUpdateTrigger :exec
|
||||||
|
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;
|
||||||
@@ -9,6 +9,83 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const initializeCollectionItemVersionsTable = `-- name: InitializeCollectionItemVersionsTable :exec
|
||||||
|
CREATE TABLE IF NOT EXISTS collection_item_versions (
|
||||||
|
version_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
item_id TEXT NOT NULL,
|
||||||
|
collection_id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
html_content TEXT NOT NULL,
|
||||||
|
template_id INTEGER NOT NULL,
|
||||||
|
position INTEGER NOT NULL,
|
||||||
|
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
created_by TEXT DEFAULT 'system' NOT NULL,
|
||||||
|
FOREIGN KEY (item_id, collection_id, site_id) REFERENCES collection_items(item_id, collection_id, site_id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) InitializeCollectionItemVersionsTable(ctx context.Context) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, initializeCollectionItemVersionsTable)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const initializeCollectionItemsTable = `-- name: InitializeCollectionItemsTable :exec
|
||||||
|
CREATE TABLE IF NOT EXISTS collection_items (
|
||||||
|
item_id TEXT NOT NULL,
|
||||||
|
collection_id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
template_id INTEGER NOT NULL,
|
||||||
|
html_content TEXT NOT NULL,
|
||||||
|
position INTEGER NOT NULL,
|
||||||
|
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
updated_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
last_edited_by TEXT DEFAULT 'system' NOT NULL,
|
||||||
|
PRIMARY KEY (item_id, collection_id, site_id),
|
||||||
|
FOREIGN KEY (collection_id, site_id) REFERENCES collections(id, site_id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (template_id) REFERENCES collection_templates(template_id) ON DELETE RESTRICT
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) InitializeCollectionItemsTable(ctx context.Context) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, initializeCollectionItemsTable)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const initializeCollectionTemplatesTable = `-- name: InitializeCollectionTemplatesTable :exec
|
||||||
|
CREATE TABLE IF NOT EXISTS collection_templates (
|
||||||
|
template_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
collection_id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
html_template TEXT NOT NULL,
|
||||||
|
is_default INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
FOREIGN KEY (collection_id, site_id) REFERENCES collections(id, site_id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) InitializeCollectionTemplatesTable(ctx context.Context) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, initializeCollectionTemplatesTable)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const initializeCollectionsTable = `-- name: InitializeCollectionsTable :exec
|
||||||
|
CREATE TABLE IF NOT EXISTS collections (
|
||||||
|
id TEXT NOT NULL,
|
||||||
|
site_id TEXT NOT NULL,
|
||||||
|
container_html TEXT NOT NULL,
|
||||||
|
created_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
updated_at INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
|
last_edited_by TEXT DEFAULT 'system' NOT NULL,
|
||||||
|
PRIMARY KEY (id, site_id)
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) InitializeCollectionsTable(ctx context.Context) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, initializeCollectionsTable)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
const initializeSchema = `-- name: InitializeSchema :exec
|
const initializeSchema = `-- name: InitializeSchema :exec
|
||||||
CREATE TABLE IF NOT EXISTS content (
|
CREATE TABLE IF NOT EXISTS content (
|
||||||
id TEXT NOT NULL,
|
id TEXT NOT NULL,
|
||||||
|
|||||||
@@ -217,3 +217,196 @@ func toNullString(s string) sql.NullString {
|
|||||||
}
|
}
|
||||||
return sql.NullString{String: s, Valid: true}
|
return sql.NullString{String: s, Valid: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCollection retrieves a collection container
|
||||||
|
func (c *DatabaseClient) GetCollection(siteID, collectionID string) (*CollectionItem, error) {
|
||||||
|
switch c.database.GetDBType() {
|
||||||
|
case "sqlite3":
|
||||||
|
collection, err := c.database.GetSQLiteQueries().GetCollection(context.Background(), sqlite.GetCollectionParams{
|
||||||
|
ID: collectionID,
|
||||||
|
SiteID: siteID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &CollectionItem{
|
||||||
|
ID: collection.ID,
|
||||||
|
SiteID: collection.SiteID,
|
||||||
|
ContainerHTML: collection.ContainerHtml,
|
||||||
|
LastEditedBy: collection.LastEditedBy,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "postgresql":
|
||||||
|
collection, err := c.database.GetPostgreSQLQueries().GetCollection(context.Background(), postgresql.GetCollectionParams{
|
||||||
|
ID: collectionID,
|
||||||
|
SiteID: siteID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &CollectionItem{
|
||||||
|
ID: collection.ID,
|
||||||
|
SiteID: collection.SiteID,
|
||||||
|
ContainerHTML: collection.ContainerHtml,
|
||||||
|
LastEditedBy: collection.LastEditedBy,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported database type: %s", c.database.GetDBType())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCollection creates a new collection container
|
||||||
|
func (c *DatabaseClient) CreateCollection(siteID, collectionID, containerHTML, lastEditedBy string) (*CollectionItem, error) {
|
||||||
|
switch c.database.GetDBType() {
|
||||||
|
case "sqlite3":
|
||||||
|
collection, err := c.database.GetSQLiteQueries().CreateCollection(context.Background(), sqlite.CreateCollectionParams{
|
||||||
|
ID: collectionID,
|
||||||
|
SiteID: siteID,
|
||||||
|
ContainerHtml: containerHTML,
|
||||||
|
LastEditedBy: lastEditedBy,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &CollectionItem{
|
||||||
|
ID: collection.ID,
|
||||||
|
SiteID: collection.SiteID,
|
||||||
|
ContainerHTML: collection.ContainerHtml,
|
||||||
|
LastEditedBy: collection.LastEditedBy,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "postgresql":
|
||||||
|
collection, err := c.database.GetPostgreSQLQueries().CreateCollection(context.Background(), postgresql.CreateCollectionParams{
|
||||||
|
ID: collectionID,
|
||||||
|
SiteID: siteID,
|
||||||
|
ContainerHtml: containerHTML,
|
||||||
|
LastEditedBy: lastEditedBy,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &CollectionItem{
|
||||||
|
ID: collection.ID,
|
||||||
|
SiteID: collection.SiteID,
|
||||||
|
ContainerHTML: collection.ContainerHtml,
|
||||||
|
LastEditedBy: collection.LastEditedBy,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported database type: %s", c.database.GetDBType())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCollectionItems retrieves all items in a collection with template information
|
||||||
|
func (c *DatabaseClient) GetCollectionItems(siteID, collectionID string) ([]CollectionItemWithTemplate, error) {
|
||||||
|
switch c.database.GetDBType() {
|
||||||
|
case "sqlite3":
|
||||||
|
items, err := c.database.GetSQLiteQueries().GetCollectionItemsWithTemplate(context.Background(), sqlite.GetCollectionItemsWithTemplateParams{
|
||||||
|
CollectionID: collectionID,
|
||||||
|
SiteID: siteID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]CollectionItemWithTemplate, len(items))
|
||||||
|
for i, item := range items {
|
||||||
|
result[i] = CollectionItemWithTemplate{
|
||||||
|
ItemID: item.ItemID,
|
||||||
|
CollectionID: item.CollectionID,
|
||||||
|
SiteID: item.SiteID,
|
||||||
|
TemplateID: int(item.TemplateID),
|
||||||
|
HTMLContent: item.HtmlContent,
|
||||||
|
Position: int(item.Position),
|
||||||
|
LastEditedBy: item.LastEditedBy,
|
||||||
|
TemplateName: item.TemplateName,
|
||||||
|
HTMLTemplate: item.HtmlTemplate,
|
||||||
|
IsDefault: item.IsDefault != 0, // SQLite uses INTEGER for boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
|
||||||
|
case "postgresql":
|
||||||
|
items, err := c.database.GetPostgreSQLQueries().GetCollectionItemsWithTemplate(context.Background(), postgresql.GetCollectionItemsWithTemplateParams{
|
||||||
|
CollectionID: collectionID,
|
||||||
|
SiteID: siteID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]CollectionItemWithTemplate, len(items))
|
||||||
|
for i, item := range items {
|
||||||
|
result[i] = CollectionItemWithTemplate{
|
||||||
|
ItemID: item.ItemID,
|
||||||
|
CollectionID: item.CollectionID,
|
||||||
|
SiteID: item.SiteID,
|
||||||
|
TemplateID: int(item.TemplateID),
|
||||||
|
HTMLContent: item.HtmlContent,
|
||||||
|
Position: int(item.Position),
|
||||||
|
LastEditedBy: item.LastEditedBy,
|
||||||
|
TemplateName: item.TemplateName,
|
||||||
|
HTMLTemplate: item.HtmlTemplate,
|
||||||
|
IsDefault: item.IsDefault, // PostgreSQL uses BOOLEAN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported database type: %s", c.database.GetDBType())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCollectionTemplate creates a new template for a collection
|
||||||
|
func (c *DatabaseClient) CreateCollectionTemplate(siteID, collectionID, name, htmlTemplate string, isDefault bool) (*CollectionTemplateItem, error) {
|
||||||
|
switch c.database.GetDBType() {
|
||||||
|
case "sqlite3":
|
||||||
|
var isDefaultInt int64
|
||||||
|
if isDefault {
|
||||||
|
isDefaultInt = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
template, err := c.database.GetSQLiteQueries().CreateCollectionTemplate(context.Background(), sqlite.CreateCollectionTemplateParams{
|
||||||
|
CollectionID: collectionID,
|
||||||
|
SiteID: siteID,
|
||||||
|
Name: name,
|
||||||
|
HtmlTemplate: htmlTemplate,
|
||||||
|
IsDefault: isDefaultInt,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &CollectionTemplateItem{
|
||||||
|
TemplateID: int(template.TemplateID),
|
||||||
|
CollectionID: template.CollectionID,
|
||||||
|
SiteID: template.SiteID,
|
||||||
|
Name: template.Name,
|
||||||
|
HTMLTemplate: template.HtmlTemplate,
|
||||||
|
IsDefault: template.IsDefault != 0,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "postgresql":
|
||||||
|
template, err := c.database.GetPostgreSQLQueries().CreateCollectionTemplate(context.Background(), postgresql.CreateCollectionTemplateParams{
|
||||||
|
CollectionID: collectionID,
|
||||||
|
SiteID: siteID,
|
||||||
|
Name: name,
|
||||||
|
HtmlTemplate: htmlTemplate,
|
||||||
|
IsDefault: isDefault,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &CollectionTemplateItem{
|
||||||
|
TemplateID: int(template.TemplateID),
|
||||||
|
CollectionID: template.CollectionID,
|
||||||
|
SiteID: template.SiteID,
|
||||||
|
Name: template.Name,
|
||||||
|
HTMLTemplate: template.HtmlTemplate,
|
||||||
|
IsDefault: template.IsDefault,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported database type: %s", c.database.GetDBType())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,14 +52,14 @@ func (e *ContentEngine) ProcessContent(input ContentInput) (*ContentResult, erro
|
|||||||
return nil, fmt.Errorf("parsing HTML: %w", err)
|
return nil, fmt.Errorf("parsing HTML: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Find insertr elements
|
// 2. Find insertr and collection elements
|
||||||
elements := e.findInsertrElements(doc)
|
insertrElements, collectionElements := e.findEditableElements(doc)
|
||||||
|
|
||||||
// 3. Generate IDs for elements
|
// 3. Process regular .insertr elements
|
||||||
generatedIDs := make(map[string]string)
|
generatedIDs := make(map[string]string)
|
||||||
processedElements := make([]ProcessedElement, len(elements))
|
processedElements := make([]ProcessedElement, len(insertrElements))
|
||||||
|
|
||||||
for i, elem := range elements {
|
for i, elem := range insertrElements {
|
||||||
// Generate structural ID (always deterministic)
|
// Generate structural ID (always deterministic)
|
||||||
id := e.idGenerator.Generate(elem.Node, input.FilePath)
|
id := e.idGenerator.Generate(elem.Node, input.FilePath)
|
||||||
|
|
||||||
@@ -97,7 +97,26 @@ func (e *ContentEngine) ProcessContent(input ContentInput) (*ContentResult, erro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Inject content if required by mode
|
// 4. Process .insertr-add collection elements
|
||||||
|
for _, collectionElem := range collectionElements {
|
||||||
|
// Generate structural ID for the collection container
|
||||||
|
collectionID := e.idGenerator.Generate(collectionElem.Node, input.FilePath)
|
||||||
|
|
||||||
|
// Add data-content-id attribute to the collection container
|
||||||
|
e.setAttribute(collectionElem.Node, "data-content-id", collectionID)
|
||||||
|
|
||||||
|
// Process collection during enhancement or content injection
|
||||||
|
if input.Mode == Enhancement || input.Mode == ContentInjection {
|
||||||
|
err := e.processCollection(collectionElem.Node, collectionID, input.SiteID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("⚠️ Failed to process collection %s: %v\n", collectionID, err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("✅ Processed collection: %s\n", collectionID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Inject content if required by mode
|
||||||
if input.Mode == Enhancement || input.Mode == ContentInjection {
|
if input.Mode == Enhancement || input.Mode == ContentInjection {
|
||||||
err = e.injectContent(processedElements, input.SiteID)
|
err = e.injectContent(processedElements, input.SiteID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -105,7 +124,9 @@ func (e *ContentEngine) ProcessContent(input ContentInput) (*ContentResult, erro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Inject editor assets for enhancement mode (development)
|
// TODO: Implement collection-specific content injection here if needed
|
||||||
|
|
||||||
|
// 6. Inject editor assets for enhancement mode (development)
|
||||||
if input.Mode == Enhancement {
|
if input.Mode == Enhancement {
|
||||||
injector := NewInjectorWithAuth(e.client, input.SiteID, e.authProvider)
|
injector := NewInjectorWithAuth(e.client, input.SiteID, e.authProvider)
|
||||||
injector.InjectEditorAssets(doc, true, "")
|
injector.InjectEditorAssets(doc, true, "")
|
||||||
@@ -123,31 +144,40 @@ type InsertrElement struct {
|
|||||||
Node *html.Node
|
Node *html.Node
|
||||||
}
|
}
|
||||||
|
|
||||||
// findInsertrElements finds all elements with class="insertr" and applies container transformation
|
// CollectionElement represents an insertr-add collection element found in HTML
|
||||||
// This implements the "syntactic sugar transformation" from CLASSES.md:
|
type CollectionElement struct {
|
||||||
// - Containers with .insertr get their .insertr class removed
|
Node *html.Node
|
||||||
// - Viable children of those containers get .insertr class added
|
}
|
||||||
// - Regular elements with .insertr are kept as-is
|
|
||||||
func (e *ContentEngine) findInsertrElements(doc *html.Node) []InsertrElement {
|
// findEditableElements finds all editable elements (.insertr and .insertr-add)
|
||||||
var elements []InsertrElement
|
func (e *ContentEngine) findEditableElements(doc *html.Node) ([]InsertrElement, []CollectionElement) {
|
||||||
|
var insertrElements []InsertrElement
|
||||||
|
var collectionElements []CollectionElement
|
||||||
var containersToTransform []*html.Node
|
var containersToTransform []*html.Node
|
||||||
|
|
||||||
// First pass: find all .insertr elements and identify containers
|
// First pass: find all .insertr and .insertr-add elements
|
||||||
e.walkNodes(doc, func(n *html.Node) {
|
e.walkNodes(doc, func(n *html.Node) {
|
||||||
if n.Type == html.ElementNode && e.hasInsertrClass(n) {
|
if n.Type == html.ElementNode {
|
||||||
|
if e.hasInsertrClass(n) {
|
||||||
if isContainer(n) {
|
if isContainer(n) {
|
||||||
// Container element - mark for transformation
|
// Container element - mark for transformation
|
||||||
containersToTransform = append(containersToTransform, n)
|
containersToTransform = append(containersToTransform, n)
|
||||||
} else {
|
} else {
|
||||||
// Regular element - add directly
|
// Regular element - add directly
|
||||||
elements = append(elements, InsertrElement{
|
insertrElements = append(insertrElements, InsertrElement{
|
||||||
|
Node: n,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if e.hasInsertrAddClass(n) {
|
||||||
|
// Collection element - add directly (no container transformation for collections)
|
||||||
|
collectionElements = append(collectionElements, CollectionElement{
|
||||||
Node: n,
|
Node: n,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Second pass: transform containers (remove .insertr from container, add to children)
|
// Second pass: transform .insertr containers (remove .insertr from container, add to children)
|
||||||
for _, container := range containersToTransform {
|
for _, container := range containersToTransform {
|
||||||
// Remove .insertr class from container
|
// Remove .insertr class from container
|
||||||
e.removeClass(container, "insertr")
|
e.removeClass(container, "insertr")
|
||||||
@@ -156,13 +186,23 @@ func (e *ContentEngine) findInsertrElements(doc *html.Node) []InsertrElement {
|
|||||||
viableChildren := FindViableChildren(container)
|
viableChildren := FindViableChildren(container)
|
||||||
for _, child := range viableChildren {
|
for _, child := range viableChildren {
|
||||||
e.addClass(child, "insertr")
|
e.addClass(child, "insertr")
|
||||||
elements = append(elements, InsertrElement{
|
insertrElements = append(insertrElements, InsertrElement{
|
||||||
Node: child,
|
Node: child,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return elements
|
return insertrElements, collectionElements
|
||||||
|
}
|
||||||
|
|
||||||
|
// findInsertrElements finds all elements with class="insertr" and applies container transformation
|
||||||
|
// This implements the "syntactic sugar transformation" from CLASSES.md:
|
||||||
|
// - Containers with .insertr get their .insertr class removed
|
||||||
|
// - Viable children of those containers get .insertr class added
|
||||||
|
// - Regular elements with .insertr are kept as-is
|
||||||
|
func (e *ContentEngine) findInsertrElements(doc *html.Node) []InsertrElement {
|
||||||
|
insertrElements, _ := e.findEditableElements(doc)
|
||||||
|
return insertrElements
|
||||||
}
|
}
|
||||||
|
|
||||||
// walkNodes walks through all nodes in the document
|
// walkNodes walks through all nodes in the document
|
||||||
@@ -184,6 +224,17 @@ func (e *ContentEngine) hasInsertrClass(node *html.Node) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hasInsertrAddClass checks if node has class="insertr-add" (collection)
|
||||||
|
func (e *ContentEngine) hasInsertrAddClass(node *html.Node) bool {
|
||||||
|
classes := GetClasses(node)
|
||||||
|
for _, class := range classes {
|
||||||
|
if class == "insertr-add" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// addContentAttributes adds data-content-id attribute only
|
// addContentAttributes adds data-content-id attribute only
|
||||||
// HTML-first approach: no content-type attribute needed
|
// HTML-first approach: no content-type attribute needed
|
||||||
func (e *ContentEngine) addContentAttributes(node *html.Node, contentID string) {
|
func (e *ContentEngine) addContentAttributes(node *html.Node, contentID string) {
|
||||||
@@ -342,3 +393,124 @@ func (e *ContentEngine) extractOriginalTemplate(node *html.Node) string {
|
|||||||
}
|
}
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// processCollection handles collection detection, persistence and reconstruction
|
||||||
|
func (e *ContentEngine) processCollection(collectionNode *html.Node, collectionID, siteID string) error {
|
||||||
|
// 1. Check if collection exists in database
|
||||||
|
existingCollection, err := e.client.GetCollection(siteID, collectionID)
|
||||||
|
collectionExists := (err == nil && existingCollection != nil)
|
||||||
|
|
||||||
|
if !collectionExists {
|
||||||
|
// 2. New collection: extract container HTML and create collection record
|
||||||
|
containerHTML := e.extractOriginalTemplate(collectionNode)
|
||||||
|
|
||||||
|
_, err := e.client.CreateCollection(siteID, collectionID, containerHTML, "system")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create collection %s: %w", collectionID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Extract templates from existing children
|
||||||
|
err = e.extractAndStoreTemplates(collectionNode, collectionID, siteID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to extract templates for collection %s: %w", collectionID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("✅ Created new collection: %s with templates\n", collectionID)
|
||||||
|
} else {
|
||||||
|
// 4. Existing collection: reconstruct items from database
|
||||||
|
err = e.reconstructCollectionItems(collectionNode, collectionID, siteID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to reconstruct collection %s: %w", collectionID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("✅ Reconstructed collection: %s from database\n", collectionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractAndStoreTemplates extracts template patterns from existing collection children
|
||||||
|
func (e *ContentEngine) extractAndStoreTemplates(collectionNode *html.Node, collectionID, siteID string) error {
|
||||||
|
// Find existing children elements to use as templates
|
||||||
|
var templateElements []*html.Node
|
||||||
|
|
||||||
|
// Walk through direct children of the collection
|
||||||
|
for child := collectionNode.FirstChild; child != nil; child = child.NextSibling {
|
||||||
|
if child.Type == html.ElementNode {
|
||||||
|
templateElements = append(templateElements, child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(templateElements) == 0 {
|
||||||
|
// No existing children - create a default empty template
|
||||||
|
_, err := e.client.CreateCollectionTemplate(siteID, collectionID, "default", "<div>New item</div>", true)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create default template: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("✅ Created default template for collection %s\n", collectionID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract templates from existing children
|
||||||
|
for i, templateElement := range templateElements {
|
||||||
|
templateHTML := e.extractOriginalTemplate(templateElement)
|
||||||
|
templateName := fmt.Sprintf("template-%d", i+1)
|
||||||
|
isDefault := (i == 0) // First template is default
|
||||||
|
|
||||||
|
_, err := e.client.CreateCollectionTemplate(siteID, collectionID, templateName, templateHTML, isDefault)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create template %s: %w", templateName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("✅ Created template '%s' for collection %s\n", templateName, collectionID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// reconstructCollectionItems rebuilds collection items from database and adds them to DOM
|
||||||
|
func (e *ContentEngine) reconstructCollectionItems(collectionNode *html.Node, collectionID, siteID string) error {
|
||||||
|
// Get all items for this collection from database
|
||||||
|
items, err := e.client.GetCollectionItems(siteID, collectionID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get collection items: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear existing children (they will be replaced with database items)
|
||||||
|
for child := collectionNode.FirstChild; child != nil; {
|
||||||
|
next := child.NextSibling
|
||||||
|
collectionNode.RemoveChild(child)
|
||||||
|
child = next
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add items from database in position order
|
||||||
|
for _, item := range items {
|
||||||
|
// Parse the item HTML content
|
||||||
|
itemDoc, err := html.Parse(strings.NewReader(item.HTMLContent))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("⚠️ Failed to parse item HTML for %s: %v\n", item.ItemID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the body element and extract its children
|
||||||
|
var bodyNode *html.Node
|
||||||
|
e.walkNodes(itemDoc, func(n *html.Node) {
|
||||||
|
if n.Type == html.ElementNode && n.Data == "body" {
|
||||||
|
bodyNode = n
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if bodyNode != nil {
|
||||||
|
// Move all children from body to collection
|
||||||
|
for bodyChild := bodyNode.FirstChild; bodyChild != nil; {
|
||||||
|
next := bodyChild.NextSibling
|
||||||
|
bodyNode.RemoveChild(bodyChild)
|
||||||
|
collectionNode.AppendChild(bodyChild)
|
||||||
|
bodyChild = next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("✅ Reconstructed %d items for collection %s\n", len(items), collectionID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,6 +48,12 @@ type ContentClient interface {
|
|||||||
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, lastEditedBy string) (*ContentItem, error)
|
CreateContent(siteID, contentID, htmlContent, originalTemplate, lastEditedBy string) (*ContentItem, error)
|
||||||
|
|
||||||
|
// Collection operations
|
||||||
|
GetCollection(siteID, collectionID string) (*CollectionItem, error)
|
||||||
|
CreateCollection(siteID, collectionID, containerHTML, lastEditedBy string) (*CollectionItem, error)
|
||||||
|
GetCollectionItems(siteID, collectionID string) ([]CollectionItemWithTemplate, error)
|
||||||
|
CreateCollectionTemplate(siteID, collectionID, name, htmlTemplate string, isDefault bool) (*CollectionTemplateItem, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContentItem represents a piece of content from the database
|
// ContentItem represents a piece of content from the database
|
||||||
@@ -65,3 +71,39 @@ type ContentResponse struct {
|
|||||||
Content []ContentItem `json:"content"`
|
Content []ContentItem `json:"content"`
|
||||||
Error string `json:"error,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CollectionItem represents a collection container from the database
|
||||||
|
type CollectionItem struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
ContainerHTML string `json:"container_html"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
LastEditedBy string `json:"last_edited_by,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CollectionTemplateItem represents a collection template from the database
|
||||||
|
type CollectionTemplateItem struct {
|
||||||
|
TemplateID int `json:"template_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
HTMLTemplate string `json:"html_template"`
|
||||||
|
IsDefault bool `json:"is_default"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CollectionItemWithTemplate represents a collection item with its template information
|
||||||
|
type CollectionItemWithTemplate struct {
|
||||||
|
ItemID string `json:"item_id"`
|
||||||
|
CollectionID string `json:"collection_id"`
|
||||||
|
SiteID string `json:"site_id"`
|
||||||
|
TemplateID int `json:"template_id"`
|
||||||
|
HTMLContent string `json:"html_content"`
|
||||||
|
Position int `json:"position"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
LastEditedBy string `json:"last_edited_by"`
|
||||||
|
|
||||||
|
// Template information
|
||||||
|
TemplateName string `json:"template_name"`
|
||||||
|
HTMLTemplate string `json:"html_template"`
|
||||||
|
IsDefault bool `json:"is_default"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { InsertrFormRenderer } from '../ui/form-renderer.js';
|
import { InsertrFormRenderer } from '../ui/form-renderer.js';
|
||||||
|
import { CollectionManager } from '../ui/collection-manager.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* InsertrEditor - Content editing workflow and business logic
|
* InsertrEditor - Content editing workflow and business logic
|
||||||
@@ -67,9 +68,19 @@ export class InsertrEditor {
|
|||||||
initializeElement(meta) {
|
initializeElement(meta) {
|
||||||
const { element } = meta;
|
const { element } = meta;
|
||||||
|
|
||||||
// Add click handler for editing
|
if (meta.editorType === 'collection') {
|
||||||
|
// Initialize collection management
|
||||||
|
this.initializeCollection(meta);
|
||||||
|
} else {
|
||||||
|
// Add click handler for editing individual elements
|
||||||
this.addClickHandler(element, meta);
|
this.addClickHandler(element, meta);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeCollection(meta) {
|
||||||
|
const collectionManager = new CollectionManager(meta, this.apiClient, this.auth);
|
||||||
|
collectionManager.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
addClickHandler(element, meta) {
|
addClickHandler(element, meta) {
|
||||||
element.addEventListener('click', (e) => {
|
element.addEventListener('click', (e) => {
|
||||||
|
|||||||
@@ -12,17 +12,19 @@ export class InsertrCore {
|
|||||||
|
|
||||||
// Find all enhanced elements on the page
|
// Find all enhanced elements on the page
|
||||||
findEnhancedElements() {
|
findEnhancedElements() {
|
||||||
return document.querySelectorAll('.insertr');
|
return document.querySelectorAll('.insertr, .insertr-add');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get element metadata
|
// Get element metadata
|
||||||
getElementMetadata(element) {
|
getElementMetadata(element) {
|
||||||
const existingId = element.getAttribute('data-content-id');
|
const existingId = element.getAttribute('data-content-id');
|
||||||
|
const isCollection = element.classList.contains('insertr-add');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
contentId: existingId,
|
contentId: existingId,
|
||||||
element: element,
|
element: element,
|
||||||
htmlMarkup: element.outerHTML // Server will generate ID from this
|
htmlMarkup: element.outerHTML, // Server will generate ID from this
|
||||||
|
editorType: isCollection ? 'collection' : 'element'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -828,3 +828,129 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
|
|||||||
order: -1;
|
order: -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
COLLECTION MANAGEMENT STYLES (.insertr-add)
|
||||||
|
================================================================= */
|
||||||
|
|
||||||
|
/* Collection container when active */
|
||||||
|
.insertr-collection-active {
|
||||||
|
outline: 2px dashed var(--insertr-primary);
|
||||||
|
outline-offset: 4px;
|
||||||
|
border-radius: var(--insertr-border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add button positioned in top right of container */
|
||||||
|
.insertr-add-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: -12px;
|
||||||
|
right: -12px;
|
||||||
|
background: var(--insertr-primary);
|
||||||
|
color: var(--insertr-text-inverse);
|
||||||
|
border: none;
|
||||||
|
padding: var(--insertr-spacing-xs) var(--insertr-spacing-sm);
|
||||||
|
border-radius: var(--insertr-border-radius);
|
||||||
|
font-size: var(--insertr-font-size-sm);
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: var(--insertr-z-floating);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-add-btn:hover {
|
||||||
|
background: var(--insertr-primary-hover);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-add-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Item controls positioned in top right corner of each item */
|
||||||
|
.insertr-item-controls {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
z-index: var(--insertr-z-floating);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Individual control buttons */
|
||||||
|
.insertr-control-btn {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: var(--insertr-bg-primary);
|
||||||
|
border: 1px solid var(--insertr-border-color);
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--insertr-text-primary);
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-control-btn:hover {
|
||||||
|
background: var(--insertr-bg-secondary);
|
||||||
|
border-color: var(--insertr-primary);
|
||||||
|
color: var(--insertr-primary);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove button specific styling */
|
||||||
|
.insertr-control-btn:last-child {
|
||||||
|
color: var(--insertr-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-control-btn:last-child:hover {
|
||||||
|
background: var(--insertr-danger);
|
||||||
|
color: var(--insertr-text-inverse);
|
||||||
|
border-color: var(--insertr-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Collection items hover state */
|
||||||
|
.insertr-collection-active > *:hover {
|
||||||
|
background: rgba(0, 123, 255, 0.03);
|
||||||
|
outline: 1px solid rgba(var(--insertr-primary), 0.2);
|
||||||
|
outline-offset: 2px;
|
||||||
|
border-radius: var(--insertr-border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show item controls on hover */
|
||||||
|
.insertr-collection-active > *:hover .insertr-item-controls {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments for collection management */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.insertr-add-btn {
|
||||||
|
position: static;
|
||||||
|
display: block;
|
||||||
|
margin: var(--insertr-spacing-sm) auto 0;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-item-controls {
|
||||||
|
position: relative;
|
||||||
|
opacity: 1;
|
||||||
|
top: auto;
|
||||||
|
right: auto;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: var(--insertr-spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-control-btn {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
610
lib/src/ui/collection-manager.js
Normal file
610
lib/src/ui/collection-manager.js
Normal file
@@ -0,0 +1,610 @@
|
|||||||
|
/**
|
||||||
|
* CollectionManager - Dynamic content collection management for .insertr-add elements
|
||||||
|
*
|
||||||
|
* Handles:
|
||||||
|
* - Template detection from existing children
|
||||||
|
* - Add/remove/reorder UI controls
|
||||||
|
* - Collection data management
|
||||||
|
* - Integration with existing .insertr editing system
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { InsertrFormRenderer } from './form-renderer.js';
|
||||||
|
|
||||||
|
export class CollectionManager {
|
||||||
|
constructor(meta, apiClient, auth) {
|
||||||
|
this.meta = meta;
|
||||||
|
this.container = meta.element;
|
||||||
|
this.apiClient = apiClient;
|
||||||
|
this.auth = auth;
|
||||||
|
|
||||||
|
// Collection state
|
||||||
|
this.template = null;
|
||||||
|
this.items = [];
|
||||||
|
this.isActive = false;
|
||||||
|
|
||||||
|
// UI elements
|
||||||
|
this.addButton = null;
|
||||||
|
this.itemControls = new Map(); // Map item element to its controls
|
||||||
|
|
||||||
|
console.log('🔄 CollectionManager initialized for:', this.container);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the collection manager
|
||||||
|
*/
|
||||||
|
initialize() {
|
||||||
|
if (this.isActive) return;
|
||||||
|
|
||||||
|
console.log('🚀 Starting collection management for:', this.container.className);
|
||||||
|
|
||||||
|
// Analyze existing content to detect template
|
||||||
|
this.analyzeTemplate();
|
||||||
|
|
||||||
|
// Add collection management UI only when in edit mode
|
||||||
|
this.setupEditModeDetection();
|
||||||
|
|
||||||
|
this.isActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up detection for when edit mode is activated
|
||||||
|
*/
|
||||||
|
setupEditModeDetection() {
|
||||||
|
// Check current auth state
|
||||||
|
if (this.auth.isAuthenticated() && this.auth.isEditMode()) {
|
||||||
|
this.activateCollectionUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for auth state changes (assuming the auth object has events)
|
||||||
|
// For now, we'll poll - in a real implementation we'd use events
|
||||||
|
this.authCheckInterval = setInterval(() => {
|
||||||
|
const shouldBeActive = this.auth.isAuthenticated() && this.auth.isEditMode();
|
||||||
|
if (shouldBeActive && !this.hasCollectionUI()) {
|
||||||
|
this.activateCollectionUI();
|
||||||
|
} else if (!shouldBeActive && this.hasCollectionUI()) {
|
||||||
|
this.deactivateCollectionUI();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if collection UI is currently active
|
||||||
|
*/
|
||||||
|
hasCollectionUI() {
|
||||||
|
return this.addButton && this.addButton.parentNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate collection UI when in edit mode
|
||||||
|
*/
|
||||||
|
activateCollectionUI() {
|
||||||
|
console.log('✅ Activating collection UI');
|
||||||
|
|
||||||
|
// Add visual indicator to container
|
||||||
|
this.container.classList.add('insertr-collection-active');
|
||||||
|
|
||||||
|
// Add the "+ Add" button (top right of container per spec)
|
||||||
|
this.createAddButton();
|
||||||
|
|
||||||
|
// Add control buttons to each existing item
|
||||||
|
this.addControlsToExistingItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deactivate collection UI when not in edit mode
|
||||||
|
*/
|
||||||
|
deactivateCollectionUI() {
|
||||||
|
console.log('❌ Deactivating collection UI');
|
||||||
|
|
||||||
|
// Remove visual indicator
|
||||||
|
this.container.classList.remove('insertr-collection-active');
|
||||||
|
|
||||||
|
// Remove add button
|
||||||
|
if (this.addButton) {
|
||||||
|
this.addButton.remove();
|
||||||
|
this.addButton = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all item controls
|
||||||
|
this.itemControls.forEach((controls, item) => {
|
||||||
|
controls.remove();
|
||||||
|
});
|
||||||
|
this.itemControls.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyze existing children to detect template pattern
|
||||||
|
*/
|
||||||
|
analyzeTemplate() {
|
||||||
|
const children = Array.from(this.container.children);
|
||||||
|
|
||||||
|
if (children.length === 0) {
|
||||||
|
console.warn('⚠️ No children found for template analysis');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use first child as template baseline
|
||||||
|
const firstChild = children[0];
|
||||||
|
|
||||||
|
this.template = {
|
||||||
|
structure: this.extractElementStructure(firstChild),
|
||||||
|
editableFields: this.findEditableElements(firstChild),
|
||||||
|
htmlTemplate: firstChild.outerHTML
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('📋 Template detected:', this.template);
|
||||||
|
|
||||||
|
// Store reference to current items
|
||||||
|
this.items = children.map((child, index) => ({
|
||||||
|
element: child,
|
||||||
|
index: index,
|
||||||
|
id: this.generateItemId(index)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the structural pattern of an element
|
||||||
|
*/
|
||||||
|
extractElementStructure(element) {
|
||||||
|
return {
|
||||||
|
tagName: element.tagName,
|
||||||
|
classes: Array.from(element.classList),
|
||||||
|
attributes: this.getRelevantAttributes(element),
|
||||||
|
childStructure: this.analyzeChildStructure(element)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get relevant attributes (excluding data-content-id which will be unique)
|
||||||
|
*/
|
||||||
|
getRelevantAttributes(element) {
|
||||||
|
const relevantAttrs = {};
|
||||||
|
for (const attr of element.attributes) {
|
||||||
|
if (attr.name !== 'data-content-id') {
|
||||||
|
relevantAttrs[attr.name] = attr.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return relevantAttrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyze child structure for template replication
|
||||||
|
*/
|
||||||
|
analyzeChildStructure(element) {
|
||||||
|
return Array.from(element.children).map(child => ({
|
||||||
|
tagName: child.tagName,
|
||||||
|
classes: Array.from(child.classList),
|
||||||
|
hasInsertrClass: child.classList.contains('insertr'),
|
||||||
|
content: child.classList.contains('insertr') ? '' : child.textContent
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find editable elements within a container
|
||||||
|
*/
|
||||||
|
findEditableElements(container) {
|
||||||
|
return Array.from(container.querySelectorAll('.insertr')).map(el => ({
|
||||||
|
selector: this.generateRelativeSelector(el, container),
|
||||||
|
type: this.determineFieldType(el),
|
||||||
|
placeholder: this.generatePlaceholder(el)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a relative selector for an element within a container
|
||||||
|
*/
|
||||||
|
generateRelativeSelector(element, container) {
|
||||||
|
// Simple approach: use tag name and classes
|
||||||
|
const tagName = element.tagName.toLowerCase();
|
||||||
|
const classes = Array.from(element.classList).join('.');
|
||||||
|
return classes ? `${tagName}.${classes}` : tagName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the type of field for editing
|
||||||
|
*/
|
||||||
|
determineFieldType(element) {
|
||||||
|
const tagName = element.tagName.toLowerCase();
|
||||||
|
if (tagName === 'a') return 'link';
|
||||||
|
if (tagName === 'img') return 'image';
|
||||||
|
return 'text';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate placeholder text for empty fields
|
||||||
|
*/
|
||||||
|
generatePlaceholder(element) {
|
||||||
|
const tagName = element.tagName.toLowerCase();
|
||||||
|
if (tagName === 'h1' || tagName === 'h2') return 'Enter heading...';
|
||||||
|
if (tagName === 'blockquote') return 'Enter quote...';
|
||||||
|
if (tagName === 'cite') return 'Enter author...';
|
||||||
|
return 'Enter text...';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate unique ID for new items
|
||||||
|
*/
|
||||||
|
generateItemId(index) {
|
||||||
|
return `item-${Date.now()}-${index}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the "+ Add" button positioned in top right of container
|
||||||
|
*/
|
||||||
|
createAddButton() {
|
||||||
|
if (this.addButton) return; // Already exists
|
||||||
|
|
||||||
|
this.addButton = document.createElement('button');
|
||||||
|
this.addButton.className = 'insertr-add-btn';
|
||||||
|
this.addButton.innerHTML = '+ Add Item';
|
||||||
|
this.addButton.title = 'Add new item to collection';
|
||||||
|
|
||||||
|
// Position in top right of container as per spec
|
||||||
|
this.container.style.position = 'relative';
|
||||||
|
|
||||||
|
this.addButton.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
this.addNewItem();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.container.appendChild(this.addButton);
|
||||||
|
console.log('➕ Add button created');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add control buttons to all existing items
|
||||||
|
*/
|
||||||
|
addControlsToExistingItems() {
|
||||||
|
this.items.forEach((item, index) => {
|
||||||
|
this.addItemControls(item.element, index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add management controls to an item (remove, reorder)
|
||||||
|
*/
|
||||||
|
addItemControls(itemElement, index) {
|
||||||
|
if (this.itemControls.has(itemElement)) return; // Already has controls
|
||||||
|
|
||||||
|
const controls = document.createElement('div');
|
||||||
|
controls.className = 'insertr-item-controls';
|
||||||
|
|
||||||
|
// Remove button (always present)
|
||||||
|
const removeBtn = this.createControlButton('×', 'Remove item', () =>
|
||||||
|
this.removeItem(itemElement)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Move up button (if not first item)
|
||||||
|
if (index > 0) {
|
||||||
|
const upBtn = this.createControlButton('↑', 'Move up', () =>
|
||||||
|
this.moveItem(itemElement, 'up')
|
||||||
|
);
|
||||||
|
controls.appendChild(upBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move down button (if not last item)
|
||||||
|
if (index < this.items.length - 1) {
|
||||||
|
const downBtn = this.createControlButton('↓', 'Move down', () =>
|
||||||
|
this.moveItem(itemElement, 'down')
|
||||||
|
);
|
||||||
|
controls.appendChild(downBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
controls.appendChild(removeBtn);
|
||||||
|
|
||||||
|
// Position in top right corner of item as per spec
|
||||||
|
itemElement.style.position = 'relative';
|
||||||
|
itemElement.appendChild(controls);
|
||||||
|
|
||||||
|
// Store reference
|
||||||
|
this.itemControls.set(itemElement, controls);
|
||||||
|
|
||||||
|
// Add hover behavior
|
||||||
|
this.setupItemHoverBehavior(itemElement, controls);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a control button
|
||||||
|
*/
|
||||||
|
createControlButton(text, title, onClick) {
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.className = 'insertr-control-btn';
|
||||||
|
button.textContent = text;
|
||||||
|
button.title = title;
|
||||||
|
button.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
onClick();
|
||||||
|
});
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up hover behavior for item controls
|
||||||
|
*/
|
||||||
|
setupItemHoverBehavior(itemElement, controls) {
|
||||||
|
itemElement.addEventListener('mouseenter', () => {
|
||||||
|
controls.style.opacity = '1';
|
||||||
|
});
|
||||||
|
|
||||||
|
itemElement.addEventListener('mouseleave', () => {
|
||||||
|
controls.style.opacity = '0';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new item to the collection
|
||||||
|
*/
|
||||||
|
addNewItem() {
|
||||||
|
console.log('➕ Adding new item to collection');
|
||||||
|
|
||||||
|
if (!this.template) {
|
||||||
|
console.error('❌ No template available for creating new items');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new item from template
|
||||||
|
const newItem = this.createItemFromTemplate();
|
||||||
|
|
||||||
|
// Add to DOM
|
||||||
|
this.container.insertBefore(newItem, this.addButton);
|
||||||
|
|
||||||
|
// Update items array
|
||||||
|
const newItemData = {
|
||||||
|
element: newItem,
|
||||||
|
index: this.items.length,
|
||||||
|
id: this.generateItemId(this.items.length)
|
||||||
|
};
|
||||||
|
this.items.push(newItemData);
|
||||||
|
|
||||||
|
// Add controls to new item
|
||||||
|
this.addItemControls(newItem, this.items.length - 1);
|
||||||
|
|
||||||
|
// Re-initialize any .insertr elements in the new item
|
||||||
|
// This allows the existing editor system to handle individual field editing
|
||||||
|
this.initializeInsertrElements(newItem);
|
||||||
|
|
||||||
|
// Update all item controls (indices may have changed)
|
||||||
|
this.updateAllItemControls();
|
||||||
|
|
||||||
|
console.log('✅ New item added successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new item from the template
|
||||||
|
*/
|
||||||
|
createItemFromTemplate() {
|
||||||
|
// Create element from template HTML
|
||||||
|
const tempContainer = document.createElement('div');
|
||||||
|
tempContainer.innerHTML = this.template.htmlTemplate;
|
||||||
|
const newItem = tempContainer.firstElementChild;
|
||||||
|
|
||||||
|
// Clear content from editable fields
|
||||||
|
this.template.editableFields.forEach(field => {
|
||||||
|
const element = newItem.querySelector(field.selector);
|
||||||
|
if (element) {
|
||||||
|
this.clearElementContent(element, field.type);
|
||||||
|
// Add placeholder text
|
||||||
|
if (field.type === 'text') {
|
||||||
|
element.textContent = field.placeholder;
|
||||||
|
element.style.color = '#999';
|
||||||
|
element.style.fontStyle = 'italic';
|
||||||
|
|
||||||
|
// Remove placeholder styling when user starts editing
|
||||||
|
element.addEventListener('focus', () => {
|
||||||
|
if (element.textContent === field.placeholder) {
|
||||||
|
element.textContent = '';
|
||||||
|
element.style.color = '';
|
||||||
|
element.style.fontStyle = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate unique data-content-id for the item
|
||||||
|
newItem.setAttribute('data-content-id', this.generateItemId(Date.now()));
|
||||||
|
|
||||||
|
return newItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear content from an element based on its type
|
||||||
|
*/
|
||||||
|
clearElementContent(element, type) {
|
||||||
|
if (type === 'link') {
|
||||||
|
element.textContent = '';
|
||||||
|
element.removeAttribute('href');
|
||||||
|
} else if (type === 'image') {
|
||||||
|
element.removeAttribute('src');
|
||||||
|
element.removeAttribute('alt');
|
||||||
|
} else {
|
||||||
|
element.textContent = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize .insertr elements within a new item
|
||||||
|
* This integrates with the existing editing system
|
||||||
|
*/
|
||||||
|
initializeInsertrElements(container) {
|
||||||
|
const insertrElements = container.querySelectorAll('.insertr');
|
||||||
|
insertrElements.forEach(element => {
|
||||||
|
// Add click handler for editing (same as existing system)
|
||||||
|
element.addEventListener('click', (e) => {
|
||||||
|
// Only allow editing if authenticated and in edit mode
|
||||||
|
if (!this.auth.isAuthenticated() || !this.auth.isEditMode()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Use the existing form renderer
|
||||||
|
const formRenderer = new InsertrFormRenderer(this.apiClient);
|
||||||
|
const meta = {
|
||||||
|
contentId: element.getAttribute('data-content-id'),
|
||||||
|
element: element,
|
||||||
|
htmlMarkup: element.outerHTML
|
||||||
|
};
|
||||||
|
const currentContent = this.extractCurrentContent(element);
|
||||||
|
|
||||||
|
formRenderer.showEditForm(
|
||||||
|
meta,
|
||||||
|
currentContent,
|
||||||
|
(formData) => this.handleItemSave(meta, formData),
|
||||||
|
() => formRenderer.closeForm()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract current content (simplified version of editor.js method)
|
||||||
|
*/
|
||||||
|
extractCurrentContent(element) {
|
||||||
|
if (element.tagName.toLowerCase() === 'a') {
|
||||||
|
return {
|
||||||
|
text: element.textContent.trim(),
|
||||||
|
url: element.getAttribute('href') || ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return element.textContent.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle saving of individual item content
|
||||||
|
*/
|
||||||
|
async handleItemSave(meta, formData) {
|
||||||
|
console.log('💾 Saving item content:', meta.contentId, formData);
|
||||||
|
|
||||||
|
try {
|
||||||
|
let contentValue;
|
||||||
|
if (typeof formData === 'string') {
|
||||||
|
contentValue = formData;
|
||||||
|
} else if (formData.content) {
|
||||||
|
contentValue = formData.content;
|
||||||
|
} else if (formData.text) {
|
||||||
|
contentValue = formData.text;
|
||||||
|
} else {
|
||||||
|
contentValue = formData;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result;
|
||||||
|
if (meta.contentId) {
|
||||||
|
result = await this.apiClient.updateContent(meta.contentId, contentValue);
|
||||||
|
} else {
|
||||||
|
result = await this.apiClient.createContent(contentValue, meta.htmlMarkup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
meta.element.setAttribute('data-content-id', result.id);
|
||||||
|
console.log(`✅ Item content saved: ${result.id}`);
|
||||||
|
} else {
|
||||||
|
console.error('❌ Failed to save item content to server');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error saving item content:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an item from the collection
|
||||||
|
*/
|
||||||
|
removeItem(itemElement) {
|
||||||
|
if (!confirm('Are you sure you want to remove this item?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🗑️ Removing item from collection');
|
||||||
|
|
||||||
|
// Remove controls
|
||||||
|
const controls = this.itemControls.get(itemElement);
|
||||||
|
if (controls) {
|
||||||
|
controls.remove();
|
||||||
|
this.itemControls.delete(itemElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from items array
|
||||||
|
this.items = this.items.filter(item => item.element !== itemElement);
|
||||||
|
|
||||||
|
// Remove from DOM
|
||||||
|
itemElement.remove();
|
||||||
|
|
||||||
|
// Update all item controls (indices changed)
|
||||||
|
this.updateAllItemControls();
|
||||||
|
|
||||||
|
console.log('✅ Item removed successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move an item up or down in the collection
|
||||||
|
*/
|
||||||
|
moveItem(itemElement, direction) {
|
||||||
|
console.log(`🔄 Moving item ${direction}`);
|
||||||
|
|
||||||
|
const currentIndex = this.items.findIndex(item => item.element === itemElement);
|
||||||
|
if (currentIndex === -1) return;
|
||||||
|
|
||||||
|
let newIndex;
|
||||||
|
if (direction === 'up' && currentIndex > 0) {
|
||||||
|
newIndex = currentIndex - 1;
|
||||||
|
} else if (direction === 'down' && currentIndex < this.items.length - 1) {
|
||||||
|
newIndex = currentIndex + 1;
|
||||||
|
} else {
|
||||||
|
return; // Can't move in that direction
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the target position in DOM
|
||||||
|
const targetItem = this.items[newIndex];
|
||||||
|
|
||||||
|
// Move in DOM
|
||||||
|
if (direction === 'up') {
|
||||||
|
this.container.insertBefore(itemElement, targetItem.element);
|
||||||
|
} else {
|
||||||
|
this.container.insertBefore(itemElement, targetItem.element.nextSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update items array
|
||||||
|
[this.items[currentIndex], this.items[newIndex]] = [this.items[newIndex], this.items[currentIndex]];
|
||||||
|
this.items[currentIndex].index = currentIndex;
|
||||||
|
this.items[newIndex].index = newIndex;
|
||||||
|
|
||||||
|
// Update all item controls
|
||||||
|
this.updateAllItemControls();
|
||||||
|
|
||||||
|
console.log('✅ Item moved successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update controls for all items (called after reordering)
|
||||||
|
*/
|
||||||
|
updateAllItemControls() {
|
||||||
|
// Remove all existing controls
|
||||||
|
this.itemControls.forEach((controls, item) => {
|
||||||
|
controls.remove();
|
||||||
|
});
|
||||||
|
this.itemControls.clear();
|
||||||
|
|
||||||
|
// Re-add controls with correct up/down button states
|
||||||
|
this.items.forEach((item, index) => {
|
||||||
|
this.addItemControls(item.element, index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup when the collection manager is destroyed
|
||||||
|
*/
|
||||||
|
destroy() {
|
||||||
|
if (this.authCheckInterval) {
|
||||||
|
clearInterval(this.authCheckInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.deactivateCollectionUI();
|
||||||
|
this.isActive = false;
|
||||||
|
|
||||||
|
console.log('🧹 CollectionManager destroyed');
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user