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.
223 lines
7.0 KiB
Go
223 lines
7.0 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// HTTPClient implements ContentRepository for HTTP API access
|
|
type HTTPClient struct {
|
|
BaseURL string
|
|
APIKey string
|
|
HTTPClient *http.Client
|
|
}
|
|
|
|
// NewHTTPClient creates a new HTTP content client
|
|
func NewHTTPClient(baseURL, apiKey string) *HTTPClient {
|
|
return &HTTPClient{
|
|
BaseURL: strings.TrimSuffix(baseURL, "/"),
|
|
APIKey: apiKey,
|
|
HTTPClient: &http.Client{
|
|
Timeout: 30 * time.Second,
|
|
},
|
|
}
|
|
}
|
|
|
|
// GetContent fetches a single content item by ID
|
|
func (c *HTTPClient) GetContent(ctx context.Context, siteID, contentID string) (*ContentItem, error) {
|
|
url := fmt.Sprintf("%s/api/content/%s?site_id=%s", c.BaseURL, contentID, siteID)
|
|
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating request: %w", err)
|
|
}
|
|
|
|
if c.APIKey != "" {
|
|
req.Header.Set("Authorization", "Bearer "+c.APIKey)
|
|
}
|
|
|
|
resp, err := c.HTTPClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("making request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == 404 {
|
|
return nil, nil // Content not found, return nil without error
|
|
}
|
|
|
|
if resp.StatusCode != 200 {
|
|
return nil, fmt.Errorf("API error: %s", resp.Status)
|
|
}
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading response: %w", err)
|
|
}
|
|
|
|
var item ContentItem
|
|
if err := json.Unmarshal(body, &item); err != nil {
|
|
return nil, fmt.Errorf("parsing response: %w", err)
|
|
}
|
|
|
|
return &item, nil
|
|
}
|
|
|
|
// GetBulkContent fetches multiple content items by IDs
|
|
func (c *HTTPClient) GetBulkContent(ctx context.Context, siteID string, contentIDs []string) (map[string]ContentItem, error) {
|
|
if len(contentIDs) == 0 {
|
|
return make(map[string]ContentItem), nil
|
|
}
|
|
|
|
// Build query parameters
|
|
params := url.Values{}
|
|
params.Set("site_id", siteID)
|
|
for _, id := range contentIDs {
|
|
params.Add("ids", id)
|
|
}
|
|
|
|
url := fmt.Sprintf("%s/api/content/bulk?%s", c.BaseURL, params.Encode())
|
|
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating request: %w", err)
|
|
}
|
|
|
|
if c.APIKey != "" {
|
|
req.Header.Set("Authorization", "Bearer "+c.APIKey)
|
|
}
|
|
|
|
resp, err := c.HTTPClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("making request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != 200 {
|
|
return nil, fmt.Errorf("API error: %s", resp.Status)
|
|
}
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading response: %w", err)
|
|
}
|
|
|
|
var response ContentResponse
|
|
if err := json.Unmarshal(body, &response); err != nil {
|
|
return nil, fmt.Errorf("parsing response: %w", err)
|
|
}
|
|
|
|
// Convert slice to map for easy lookup
|
|
result := make(map[string]ContentItem)
|
|
for _, item := range response.Content {
|
|
result[item.ID] = item
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// GetAllContent fetches all content for a site
|
|
func (c *HTTPClient) GetAllContent(ctx context.Context, siteID string) (map[string]ContentItem, error) {
|
|
url := fmt.Sprintf("%s/api/content?site_id=%s", c.BaseURL, siteID)
|
|
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating request: %w", err)
|
|
}
|
|
|
|
if c.APIKey != "" {
|
|
req.Header.Set("Authorization", "Bearer "+c.APIKey)
|
|
}
|
|
|
|
resp, err := c.HTTPClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("making request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != 200 {
|
|
return nil, fmt.Errorf("API error: %s", resp.Status)
|
|
}
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading response: %w", err)
|
|
}
|
|
|
|
var response ContentResponse
|
|
if err := json.Unmarshal(body, &response); err != nil {
|
|
return nil, fmt.Errorf("parsing response: %w", err)
|
|
}
|
|
|
|
// Convert slice to map for easy lookup
|
|
result := make(map[string]ContentItem)
|
|
for _, item := range response.Content {
|
|
result[item.ID] = item
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// CreateContent creates a new content item via HTTP API
|
|
func (c *HTTPClient) CreateContent(ctx context.Context, siteID, contentID, htmlContent, originalTemplate, lastEditedBy string) (*ContentItem, error) {
|
|
// For now, HTTPClient CreateContent is not implemented for enhancer use
|
|
// This would typically be used in API-driven enhancement scenarios
|
|
return nil, fmt.Errorf("CreateContent not implemented for HTTPClient - use DatabaseClient for enhancement")
|
|
}
|
|
|
|
// Collection method stubs - TODO: Implement these for HTTP API
|
|
func (c *HTTPClient) GetCollection(ctx context.Context, siteID, collectionID string) (*CollectionItem, error) {
|
|
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
|
|
}
|
|
|
|
func (c *HTTPClient) CreateCollection(ctx context.Context, siteID, collectionID, containerHTML, lastEditedBy string) (*CollectionItem, error) {
|
|
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
|
|
}
|
|
|
|
func (c *HTTPClient) GetCollectionItems(ctx context.Context, siteID, collectionID string) ([]CollectionItemWithTemplate, error) {
|
|
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
|
|
}
|
|
|
|
func (c *HTTPClient) CreateCollectionTemplate(ctx context.Context, siteID, collectionID, name, htmlTemplate string, isDefault bool) (*CollectionTemplateItem, error) {
|
|
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
|
|
}
|
|
|
|
func (c *HTTPClient) GetCollectionTemplates(ctx context.Context, siteID, collectionID string) ([]CollectionTemplateItem, error) {
|
|
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
|
|
}
|
|
|
|
func (c *HTTPClient) GetCollectionTemplate(ctx context.Context, templateID int) (*CollectionTemplateItem, error) {
|
|
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
|
|
}
|
|
|
|
func (c *HTTPClient) CreateCollectionItem(ctx context.Context, siteID, collectionID, itemID string, templateID int, htmlContent string, position int, lastEditedBy string) (*CollectionItemWithTemplate, error) {
|
|
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
|
|
}
|
|
|
|
func (c *HTTPClient) CreateCollectionItemAtomic(ctx context.Context, siteID, collectionID string, templateID int, lastEditedBy string) (*CollectionItemWithTemplate, error) {
|
|
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
|
|
}
|
|
|
|
func (c *HTTPClient) GetMaxPosition(ctx context.Context, siteID, collectionID string) (int, error) {
|
|
return 0, fmt.Errorf("collection operations not implemented in HTTPClient")
|
|
}
|
|
|
|
func (c *HTTPClient) UpdateContent(ctx context.Context, siteID, contentID, htmlContent, lastEditedBy string) (*ContentItem, error) {
|
|
return nil, fmt.Errorf("content update operations not implemented in HTTPClient")
|
|
}
|
|
|
|
func (c *HTTPClient) ReorderCollectionItems(ctx context.Context, siteID, collectionID string, items []CollectionItemPosition, lastEditedBy string) error {
|
|
return fmt.Errorf("collection reordering not implemented in HTTPClient")
|
|
}
|
|
|
|
// WithTransaction executes a function within a transaction (not supported for HTTP client)
|
|
func (c *HTTPClient) WithTransaction(ctx context.Context, fn func(ContentRepository) error) error {
|
|
return fmt.Errorf("transactions not supported for HTTP client")
|
|
}
|