Files
insertr/internal/db/sqlite_repository.go
Joakim 900f91bc25 Improve collection management: fix template selection UI and item positioning
This commit addresses multiple collection management issues to improve user experience:

## Template Selection Modal Improvements
- Replace inline styles with CSS classes for reliable visual feedback
- Fix default template selection conflicts that showed multiple templates as selected
- Add styled template previews that show actual CSS styling differences
- Improve modal responsiveness and visual hierarchy

## Collection Item Creation Fixes
- Fix empty collection items with no content/height that were unclickable
- Preserve template placeholder content during item creation instead of clearing it
- Implement proper positioning system using GetMaxPosition to place new items at collection end
- Add position calculation logic to prevent new items from jumping to beginning

## Backend Positioning System
- Add GetMaxPosition method to all repository implementations (SQLite, PostgreSQL, HTTPClient)
- Update CreateCollectionItemFromTemplate to calculate correct position (maxPos + 1)
- Maintain reconstruction ordering by position ASC for consistent item placement

## Frontend Template Selection
- CSS class-based selection states replace problematic inline style manipulation
- Template previews now render actual HTML with real page styling
- Improved hover states and selection visual feedback
- Fixed auto-selection interference with user interaction

These changes ensure collection items appear in expected order and template selection
provides clear visual feedback with actual styling previews.
2025-10-30 22:06:44 +01:00

374 lines
11 KiB
Go

package db
import (
"context"
"database/sql"
"fmt"
"github.com/insertr/insertr/internal/db/sqlite"
)
// SQLiteRepository implements ContentRepository for SQLite databases
type SQLiteRepository struct {
queries *sqlite.Queries
db *sql.DB
}
// NewSQLiteRepository creates a new SQLite repository
func NewSQLiteRepository(db *sql.DB) *SQLiteRepository {
return &SQLiteRepository{
queries: sqlite.New(db),
db: db,
}
}
// GetContent retrieves a single content item
func (r *SQLiteRepository) GetContent(ctx context.Context, siteID, contentID string) (*ContentItem, error) {
content, err := r.queries.GetContent(ctx, sqlite.GetContentParams{
ID: contentID,
SiteID: siteID,
})
if err != nil {
return nil, err
}
return &ContentItem{
ID: content.ID,
SiteID: content.SiteID,
HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
LastEditedBy: content.LastEditedBy,
}, nil
}
// GetBulkContent retrieves multiple content items
func (r *SQLiteRepository) GetBulkContent(ctx context.Context, siteID string, contentIDs []string) (map[string]ContentItem, error) {
contents, err := r.queries.GetBulkContent(ctx, sqlite.GetBulkContentParams{
SiteID: siteID,
Ids: contentIDs,
})
if err != nil {
return nil, err
}
items := make(map[string]ContentItem)
for _, content := range contents {
items[content.ID] = ContentItem{
ID: content.ID,
SiteID: content.SiteID,
HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
LastEditedBy: content.LastEditedBy,
}
}
return items, nil
}
// GetAllContent retrieves all content items for a site
func (r *SQLiteRepository) GetAllContent(ctx context.Context, siteID string) (map[string]ContentItem, error) {
contents, err := r.queries.GetAllContent(ctx, siteID)
if err != nil {
return nil, err
}
items := make(map[string]ContentItem)
for _, content := range contents {
items[content.ID] = ContentItem{
ID: content.ID,
SiteID: content.SiteID,
HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
LastEditedBy: content.LastEditedBy,
}
}
return items, nil
}
// CreateContent creates a new content item
func (r *SQLiteRepository) CreateContent(ctx context.Context, siteID, contentID, htmlContent, originalTemplate, lastEditedBy string) (*ContentItem, error) {
content, err := r.queries.CreateContent(ctx, sqlite.CreateContentParams{
ID: contentID,
SiteID: siteID,
HtmlContent: htmlContent,
OriginalTemplate: ToNullString(originalTemplate),
LastEditedBy: lastEditedBy,
})
if err != nil {
return nil, err
}
return &ContentItem{
ID: content.ID,
SiteID: content.SiteID,
HTMLContent: content.HtmlContent,
OriginalTemplate: getStringFromNullString(content.OriginalTemplate),
LastEditedBy: content.LastEditedBy,
}, nil
}
// GetCollection retrieves a collection container
func (r *SQLiteRepository) GetCollection(ctx context.Context, siteID, collectionID string) (*CollectionItem, error) {
collection, err := r.queries.GetCollection(ctx, 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
}
// CreateCollection creates a new collection container
func (r *SQLiteRepository) CreateCollection(ctx context.Context, siteID, collectionID, containerHTML, lastEditedBy string) (*CollectionItem, error) {
collection, err := r.queries.CreateCollection(ctx, 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
}
// GetCollectionItems retrieves all items in a collection with template information
func (r *SQLiteRepository) GetCollectionItems(ctx context.Context, siteID, collectionID string) ([]CollectionItemWithTemplate, error) {
items, err := r.queries.GetCollectionItemsWithTemplate(ctx, 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
}
// CreateCollectionTemplate creates a new template for a collection
func (r *SQLiteRepository) CreateCollectionTemplate(ctx context.Context, siteID, collectionID, name, htmlTemplate string, isDefault bool) (*CollectionTemplateItem, error) {
var isDefaultInt int64
if isDefault {
isDefaultInt = 1
}
template, err := r.queries.CreateCollectionTemplate(ctx, 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
}
// GetCollectionTemplates retrieves all templates for a collection
func (r *SQLiteRepository) GetCollectionTemplates(ctx context.Context, siteID, collectionID string) ([]CollectionTemplateItem, error) {
templates, err := r.queries.GetCollectionTemplates(ctx, sqlite.GetCollectionTemplatesParams{
CollectionID: collectionID,
SiteID: siteID,
})
if err != nil {
return nil, err
}
result := make([]CollectionTemplateItem, len(templates))
for i, template := range templates {
result[i] = CollectionTemplateItem{
TemplateID: int(template.TemplateID),
CollectionID: template.CollectionID,
SiteID: template.SiteID,
Name: template.Name,
HTMLTemplate: template.HtmlTemplate,
IsDefault: template.IsDefault != 0, // SQLite uses INTEGER for boolean
}
}
return result, nil
}
// GetCollectionTemplate retrieves a single template by ID
func (r *SQLiteRepository) GetCollectionTemplate(ctx context.Context, templateID int) (*CollectionTemplateItem, error) {
template, err := r.queries.GetCollectionTemplate(ctx, int64(templateID))
if err != nil {
return nil, err
}
result := &CollectionTemplateItem{
TemplateID: int(template.TemplateID),
CollectionID: template.CollectionID,
SiteID: template.SiteID,
Name: template.Name,
HTMLTemplate: template.HtmlTemplate,
IsDefault: template.IsDefault != 0, // SQLite uses INTEGER for boolean
}
return result, nil
}
// CreateCollectionItem creates a new collection item
func (r *SQLiteRepository) CreateCollectionItem(ctx context.Context, siteID, collectionID, itemID string, templateID int, htmlContent string, position int, lastEditedBy string) (*CollectionItemWithTemplate, error) {
item, err := r.queries.CreateCollectionItem(ctx, sqlite.CreateCollectionItemParams{
ItemID: itemID,
CollectionID: collectionID,
SiteID: siteID,
TemplateID: int64(templateID),
HtmlContent: htmlContent,
Position: int64(position),
LastEditedBy: lastEditedBy,
})
if err != nil {
return nil, err
}
return &CollectionItemWithTemplate{
ItemID: item.ItemID,
CollectionID: item.CollectionID,
SiteID: item.SiteID,
TemplateID: int(item.TemplateID),
HTMLContent: item.HtmlContent,
Position: int(item.Position),
LastEditedBy: item.LastEditedBy,
}, nil
}
// CreateCollectionItemAtomic creates a collection item with all its content entries atomically
func (r *SQLiteRepository) CreateCollectionItemAtomic(ctx context.Context, siteID, collectionID string, templateID int, lastEditedBy string) (*CollectionItemWithTemplate, error) {
// Get template HTML for processing
templates, err := r.GetCollectionTemplates(ctx, siteID, collectionID)
if err != nil {
return nil, fmt.Errorf("failed to get templates: %w", err)
}
var templateHTML string
for _, template := range templates {
if template.TemplateID == templateID {
templateHTML = template.HTMLTemplate
break
}
}
if templateHTML == "" {
return nil, fmt.Errorf("template %d not found", templateID)
}
// TODO: Implement using unified engine approach
// This requires circular dependency resolution
return nil, fmt.Errorf("CreateCollectionItemAtomic not yet implemented for SQLite")
}
// GetMaxPosition returns the maximum position for items in a collection
func (r *SQLiteRepository) GetMaxPosition(ctx context.Context, siteID, collectionID string) (int, error) {
result, err := r.queries.GetMaxPosition(ctx, sqlite.GetMaxPositionParams{
CollectionID: collectionID,
SiteID: siteID,
})
if err != nil {
return 0, err
}
// Convert interface{} to int (SQLite returns int64)
if maxPos, ok := result.(int64); ok {
return int(maxPos), nil
}
return 0, nil
}
// UpdateContent updates an existing content item
func (r *SQLiteRepository) UpdateContent(ctx context.Context, siteID, contentID, htmlContent, lastEditedBy string) (*ContentItem, error) {
content, err := r.queries.UpdateContent(ctx, sqlite.UpdateContentParams{
HtmlContent: htmlContent,
LastEditedBy: lastEditedBy,
ID: contentID,
SiteID: siteID,
})
if err != nil {
return nil, err
}
return &ContentItem{
ID: content.ID,
SiteID: content.SiteID,
HTMLContent: content.HtmlContent,
OriginalTemplate: FromNullString(content.OriginalTemplate),
UpdatedAt: fmt.Sprintf("%d", content.UpdatedAt),
LastEditedBy: content.LastEditedBy,
}, nil
}
// ReorderCollectionItems reorders collection items in bulk
func (r *SQLiteRepository) ReorderCollectionItems(ctx context.Context, siteID, collectionID string, items []CollectionItemPosition, lastEditedBy string) error {
// Use transaction for atomic bulk updates
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
qtx := r.queries.WithTx(tx)
for _, item := range items {
err = qtx.UpdateCollectionItemPosition(ctx, sqlite.UpdateCollectionItemPositionParams{
ItemID: item.ItemID,
CollectionID: collectionID,
SiteID: siteID,
Position: int64(item.Position),
LastEditedBy: lastEditedBy,
})
if err != nil {
return fmt.Errorf("failed to update position for item %s: %w", item.ItemID, err)
}
}
return tx.Commit()
}
// WithTransaction executes a function within a database transaction
func (r *SQLiteRepository) WithTransaction(ctx context.Context, fn func(ContentRepository) error) error {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
txRepo := &SQLiteRepository{
queries: r.queries.WithTx(tx),
db: r.db,
}
if err := fn(txRepo); err != nil {
tx.Rollback()
return err
}
return tx.Commit()
}