Files
insertr/internal/db/http_client.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

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")
}