- Move all ContentItem, ContentClient, ContentResponse types to engine/types.go as single source of truth - Remove duplicate type definitions from content/types.go - Update all imports across codebase to use engine types - Enhance engine to extract existing data-content-id from HTML markup - Simplify frontend to always send html_markup, let server handle ID extraction/generation - Fix contentId reference errors in frontend error handling - Add getAttribute helper method to engine for ID extraction - Add GetAllContent method to engine.DatabaseClient - Update enhancer to use engine.ContentClient interface - All builds and API endpoints verified working This resolves the 400 Bad Request errors and creates a unified architecture where the server is the single source of truth for all ID generation and content type management.
167 lines
3.9 KiB
Go
167 lines
3.9 KiB
Go
package content
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/insertr/insertr/internal/engine"
|
|
)
|
|
|
|
// HTTPClient implements ContentClient 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(siteID, contentID string) (*engine.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 engine.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(siteID string, contentIDs []string) (map[string]engine.ContentItem, error) {
|
|
if len(contentIDs) == 0 {
|
|
return make(map[string]engine.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 engine.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]engine.ContentItem)
|
|
for _, item := range response.Content {
|
|
result[item.ID] = item
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// GetAllContent fetches all content for a site
|
|
func (c *HTTPClient) GetAllContent(siteID string) (map[string]engine.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 engine.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]engine.ContentItem)
|
|
for _, item := range response.Content {
|
|
result[item.ID] = item
|
|
}
|
|
|
|
return result, nil
|
|
}
|