Refactor database layer to eliminate type switching and simplify architecture
- Replace type switching with clean repository pattern using sqlc-generated code - Move ContentRepository interface and domain models to db package - Create separate SQLiteRepository and PostgreSQLRepository implementations - Remove unnecessary RepositoryAdapter and ContentClient interface duplication - Update all clients (HTTP, Mock) to implement db.ContentRepository directly - Add context.Context parameters to all repository methods (Go best practice) - Eliminate duplicate domain models and type conversions - Remove type aliases - use db package types directly throughout codebase - Update engine, content managers, and API handlers to use repositories directly Benefits: - Zero runtime type switching overhead - Single source of truth for domain models - Clean package boundaries and separation of concerns - Standard Go interface patterns with context support - Easier testing with mockable repository interface - Maintainable: adding new database types requires no changes to existing code
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package content
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -9,10 +10,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/insertr/insertr/internal/engine"
|
||||
"github.com/insertr/insertr/internal/db"
|
||||
)
|
||||
|
||||
// HTTPClient implements ContentClient for HTTP API access
|
||||
// HTTPClient implements db.ContentRepository for HTTP API access
|
||||
type HTTPClient struct {
|
||||
BaseURL string
|
||||
APIKey string
|
||||
@@ -31,7 +32,7 @@ func NewHTTPClient(baseURL, apiKey string) *HTTPClient {
|
||||
}
|
||||
|
||||
// GetContent fetches a single content item by ID
|
||||
func (c *HTTPClient) GetContent(siteID, contentID string) (*engine.ContentItem, error) {
|
||||
func (c *HTTPClient) GetContent(ctx context.Context, siteID, contentID string) (*db.ContentItem, error) {
|
||||
url := fmt.Sprintf("%s/api/content/%s?site_id=%s", c.BaseURL, contentID, siteID)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
@@ -62,7 +63,7 @@ func (c *HTTPClient) GetContent(siteID, contentID string) (*engine.ContentItem,
|
||||
return nil, fmt.Errorf("reading response: %w", err)
|
||||
}
|
||||
|
||||
var item engine.ContentItem
|
||||
var item db.ContentItem
|
||||
if err := json.Unmarshal(body, &item); err != nil {
|
||||
return nil, fmt.Errorf("parsing response: %w", err)
|
||||
}
|
||||
@@ -71,9 +72,9 @@ func (c *HTTPClient) GetContent(siteID, contentID string) (*engine.ContentItem,
|
||||
}
|
||||
|
||||
// GetBulkContent fetches multiple content items by IDs
|
||||
func (c *HTTPClient) GetBulkContent(siteID string, contentIDs []string) (map[string]engine.ContentItem, error) {
|
||||
func (c *HTTPClient) GetBulkContent(ctx context.Context, siteID string, contentIDs []string) (map[string]db.ContentItem, error) {
|
||||
if len(contentIDs) == 0 {
|
||||
return make(map[string]engine.ContentItem), nil
|
||||
return make(map[string]db.ContentItem), nil
|
||||
}
|
||||
|
||||
// Build query parameters
|
||||
@@ -109,13 +110,13 @@ func (c *HTTPClient) GetBulkContent(siteID string, contentIDs []string) (map[str
|
||||
return nil, fmt.Errorf("reading response: %w", err)
|
||||
}
|
||||
|
||||
var response engine.ContentResponse
|
||||
var response db.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)
|
||||
result := make(map[string]db.ContentItem)
|
||||
for _, item := range response.Content {
|
||||
result[item.ID] = item
|
||||
}
|
||||
@@ -124,7 +125,7 @@ func (c *HTTPClient) GetBulkContent(siteID string, contentIDs []string) (map[str
|
||||
}
|
||||
|
||||
// GetAllContent fetches all content for a site
|
||||
func (c *HTTPClient) GetAllContent(siteID string) (map[string]engine.ContentItem, error) {
|
||||
func (c *HTTPClient) GetAllContent(ctx context.Context, siteID string) (map[string]db.ContentItem, error) {
|
||||
url := fmt.Sprintf("%s/api/content?site_id=%s", c.BaseURL, siteID)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
@@ -151,13 +152,13 @@ func (c *HTTPClient) GetAllContent(siteID string) (map[string]engine.ContentItem
|
||||
return nil, fmt.Errorf("reading response: %w", err)
|
||||
}
|
||||
|
||||
var response engine.ContentResponse
|
||||
var response db.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)
|
||||
result := make(map[string]db.ContentItem)
|
||||
for _, item := range response.Content {
|
||||
result[item.ID] = item
|
||||
}
|
||||
@@ -166,37 +167,42 @@ func (c *HTTPClient) GetAllContent(siteID string) (map[string]engine.ContentItem
|
||||
}
|
||||
|
||||
// CreateContent creates a new content item via HTTP API
|
||||
func (c *HTTPClient) CreateContent(siteID, contentID, htmlContent, originalTemplate, lastEditedBy string) (*engine.ContentItem, error) {
|
||||
func (c *HTTPClient) CreateContent(ctx context.Context, siteID, contentID, htmlContent, originalTemplate, lastEditedBy string) (*db.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(siteID, collectionID string) (*engine.CollectionItem, error) {
|
||||
func (c *HTTPClient) GetCollection(ctx context.Context, siteID, collectionID string) (*db.CollectionItem, error) {
|
||||
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
|
||||
}
|
||||
|
||||
func (c *HTTPClient) CreateCollection(siteID, collectionID, containerHTML, lastEditedBy string) (*engine.CollectionItem, error) {
|
||||
func (c *HTTPClient) CreateCollection(ctx context.Context, siteID, collectionID, containerHTML, lastEditedBy string) (*db.CollectionItem, error) {
|
||||
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
|
||||
}
|
||||
|
||||
func (c *HTTPClient) GetCollectionItems(siteID, collectionID string) ([]engine.CollectionItemWithTemplate, error) {
|
||||
func (c *HTTPClient) GetCollectionItems(ctx context.Context, siteID, collectionID string) ([]db.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) {
|
||||
func (c *HTTPClient) CreateCollectionTemplate(ctx context.Context, siteID, collectionID, name, htmlTemplate string, isDefault bool) (*db.CollectionTemplateItem, error) {
|
||||
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
|
||||
}
|
||||
|
||||
func (c *HTTPClient) GetCollectionTemplates(siteID, collectionID string) ([]engine.CollectionTemplateItem, error) {
|
||||
func (c *HTTPClient) GetCollectionTemplates(ctx context.Context, siteID, collectionID string) ([]db.CollectionTemplateItem, error) {
|
||||
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
|
||||
}
|
||||
|
||||
func (c *HTTPClient) CreateCollectionItem(siteID, collectionID, itemID string, templateID int, htmlContent string, position int, lastEditedBy string) (*engine.CollectionItemWithTemplate, error) {
|
||||
func (c *HTTPClient) CreateCollectionItem(ctx context.Context, siteID, collectionID, itemID string, templateID int, htmlContent string, position int, lastEditedBy string) (*db.CollectionItemWithTemplate, error) {
|
||||
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
|
||||
}
|
||||
|
||||
func (c *HTTPClient) CreateCollectionItemAtomic(siteID, collectionID string, templateID int, lastEditedBy string) (*engine.CollectionItemWithTemplate, error) {
|
||||
func (c *HTTPClient) CreateCollectionItemAtomic(ctx context.Context, siteID, collectionID string, templateID int, lastEditedBy string) (*db.CollectionItemWithTemplate, error) {
|
||||
return nil, fmt.Errorf("collection operations 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(db.ContentRepository) error) error {
|
||||
return fmt.Errorf("transactions not supported for HTTP client")
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/insertr/insertr/internal/config"
|
||||
"github.com/insertr/insertr/internal/db"
|
||||
"github.com/insertr/insertr/internal/engine"
|
||||
)
|
||||
|
||||
@@ -30,7 +31,7 @@ type Enhancer struct {
|
||||
}
|
||||
|
||||
// NewEnhancer creates a new HTML enhancer with unified pipeline
|
||||
func NewEnhancer(client engine.ContentClient, siteID string, config EnhancementConfig) *Enhancer {
|
||||
func NewEnhancer(client db.ContentRepository, siteID string, config EnhancementConfig) *Enhancer {
|
||||
return &Enhancer{
|
||||
engine: engine.NewContentEngine(client),
|
||||
discoverer: NewDiscoverer(),
|
||||
@@ -40,7 +41,7 @@ func NewEnhancer(client engine.ContentClient, siteID string, config EnhancementC
|
||||
}
|
||||
|
||||
// NewEnhancerWithAuth creates a new HTML enhancer with auth provider
|
||||
func NewEnhancerWithAuth(client engine.ContentClient, siteID string, config EnhancementConfig, authProvider *engine.AuthProvider) *Enhancer {
|
||||
func NewEnhancerWithAuth(client db.ContentRepository, siteID string, config EnhancementConfig, authProvider *engine.AuthProvider) *Enhancer {
|
||||
return &Enhancer{
|
||||
engine: engine.NewContentEngineWithAuth(client, authProvider),
|
||||
discoverer: NewDiscoverer(),
|
||||
@@ -50,7 +51,7 @@ func NewEnhancerWithAuth(client engine.ContentClient, siteID string, config Enha
|
||||
}
|
||||
|
||||
// NewDefaultEnhancer creates an enhancer with default configuration
|
||||
func NewDefaultEnhancer(client engine.ContentClient, siteID string) *Enhancer {
|
||||
func NewDefaultEnhancer(client db.ContentRepository, siteID string) *Enhancer {
|
||||
defaultConfig := EnhancementConfig{
|
||||
Discovery: DiscoveryConfig{
|
||||
Enabled: true,
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
package content
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/insertr/insertr/internal/engine"
|
||||
"github.com/insertr/insertr/internal/db"
|
||||
)
|
||||
|
||||
// MockClient implements ContentClient with mock data for development
|
||||
type MockClient struct {
|
||||
data map[string]engine.ContentItem
|
||||
data map[string]db.ContentItem
|
||||
}
|
||||
|
||||
// NewMockClient creates a new mock content client with sample data
|
||||
func NewMockClient() *MockClient {
|
||||
// Generate realistic mock content based on actual generated IDs
|
||||
data := map[string]engine.ContentItem{
|
||||
data := map[string]db.ContentItem{
|
||||
// Navigation (index.html has collision suffix)
|
||||
"navbar-logo-2b10ad": {
|
||||
ID: "navbar-logo-2b10ad",
|
||||
@@ -101,7 +102,7 @@ func NewMockClient() *MockClient {
|
||||
}
|
||||
|
||||
// GetContent fetches a single content item by ID
|
||||
func (m *MockClient) GetContent(siteID, contentID string) (*engine.ContentItem, error) {
|
||||
func (m *MockClient) GetContent(ctx context.Context, siteID, contentID string) (*db.ContentItem, error) {
|
||||
if item, exists := m.data[contentID]; exists && item.SiteID == siteID {
|
||||
return &item, nil
|
||||
}
|
||||
@@ -111,11 +112,11 @@ func (m *MockClient) GetContent(siteID, contentID string) (*engine.ContentItem,
|
||||
}
|
||||
|
||||
// GetBulkContent fetches multiple content items by IDs
|
||||
func (m *MockClient) GetBulkContent(siteID string, contentIDs []string) (map[string]engine.ContentItem, error) {
|
||||
result := make(map[string]engine.ContentItem)
|
||||
func (m *MockClient) GetBulkContent(ctx context.Context, siteID string, contentIDs []string) (map[string]db.ContentItem, error) {
|
||||
result := make(map[string]db.ContentItem)
|
||||
|
||||
for _, id := range contentIDs {
|
||||
item, err := m.GetContent(siteID, id)
|
||||
item, err := m.GetContent(ctx, siteID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -128,8 +129,8 @@ func (m *MockClient) GetBulkContent(siteID string, contentIDs []string) (map[str
|
||||
}
|
||||
|
||||
// GetAllContent fetches all content for a site
|
||||
func (m *MockClient) GetAllContent(siteID string) (map[string]engine.ContentItem, error) {
|
||||
result := make(map[string]engine.ContentItem)
|
||||
func (m *MockClient) GetAllContent(ctx context.Context, siteID string) (map[string]db.ContentItem, error) {
|
||||
result := make(map[string]db.ContentItem)
|
||||
|
||||
for _, item := range m.data {
|
||||
if item.SiteID == siteID {
|
||||
@@ -141,9 +142,9 @@ func (m *MockClient) GetAllContent(siteID string) (map[string]engine.ContentItem
|
||||
}
|
||||
|
||||
// CreateContent creates a new mock content item
|
||||
func (m *MockClient) CreateContent(siteID, contentID, htmlContent, originalTemplate, lastEditedBy string) (*engine.ContentItem, error) {
|
||||
func (m *MockClient) CreateContent(ctx context.Context, siteID, contentID, htmlContent, originalTemplate, lastEditedBy string) (*db.ContentItem, error) {
|
||||
// For mock client, just create and store the item
|
||||
item := engine.ContentItem{
|
||||
item := db.ContentItem{
|
||||
ID: contentID,
|
||||
SiteID: siteID,
|
||||
HTMLContent: htmlContent,
|
||||
@@ -159,30 +160,35 @@ func (m *MockClient) CreateContent(siteID, contentID, htmlContent, originalTempl
|
||||
}
|
||||
|
||||
// Collection method stubs - TODO: Implement these for mock testing
|
||||
func (m *MockClient) GetCollection(siteID, collectionID string) (*engine.CollectionItem, error) {
|
||||
func (m *MockClient) GetCollection(ctx context.Context, siteID, collectionID string) (*db.CollectionItem, error) {
|
||||
return nil, fmt.Errorf("collection operations not implemented in MockClient")
|
||||
}
|
||||
|
||||
func (m *MockClient) CreateCollection(siteID, collectionID, containerHTML, lastEditedBy string) (*engine.CollectionItem, error) {
|
||||
func (m *MockClient) CreateCollection(ctx context.Context, siteID, collectionID, containerHTML, lastEditedBy string) (*db.CollectionItem, error) {
|
||||
return nil, fmt.Errorf("collection operations not implemented in MockClient")
|
||||
}
|
||||
|
||||
func (m *MockClient) GetCollectionItems(siteID, collectionID string) ([]engine.CollectionItemWithTemplate, error) {
|
||||
func (m *MockClient) GetCollectionItems(ctx context.Context, siteID, collectionID string) ([]db.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) {
|
||||
func (m *MockClient) CreateCollectionTemplate(ctx context.Context, siteID, collectionID, name, htmlTemplate string, isDefault bool) (*db.CollectionTemplateItem, error) {
|
||||
return nil, fmt.Errorf("collection operations not implemented in MockClient")
|
||||
}
|
||||
|
||||
func (m *MockClient) GetCollectionTemplates(siteID, collectionID string) ([]engine.CollectionTemplateItem, error) {
|
||||
func (m *MockClient) GetCollectionTemplates(ctx context.Context, siteID, collectionID string) ([]db.CollectionTemplateItem, error) {
|
||||
return nil, fmt.Errorf("collection operations not implemented in MockClient")
|
||||
}
|
||||
|
||||
func (m *MockClient) CreateCollectionItem(siteID, collectionID, itemID string, templateID int, htmlContent string, position int, lastEditedBy string) (*engine.CollectionItemWithTemplate, error) {
|
||||
func (m *MockClient) CreateCollectionItem(ctx context.Context, siteID, collectionID, itemID string, templateID int, htmlContent string, position int, lastEditedBy string) (*db.CollectionItemWithTemplate, error) {
|
||||
return nil, fmt.Errorf("collection operations not implemented in MockClient")
|
||||
}
|
||||
|
||||
func (m *MockClient) CreateCollectionItemAtomic(siteID, collectionID string, templateID int, lastEditedBy string) (*engine.CollectionItemWithTemplate, error) {
|
||||
func (m *MockClient) CreateCollectionItemAtomic(ctx context.Context, siteID, collectionID string, templateID int, lastEditedBy string) (*db.CollectionItemWithTemplate, error) {
|
||||
return nil, fmt.Errorf("collection operations not implemented in MockClient")
|
||||
}
|
||||
|
||||
// WithTransaction executes a function within a transaction (not supported for mock client)
|
||||
func (m *MockClient) WithTransaction(ctx context.Context, fn func(db.ContentRepository) error) error {
|
||||
return fmt.Errorf("transactions not supported for mock client")
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/insertr/insertr/internal/config"
|
||||
"github.com/insertr/insertr/internal/db"
|
||||
"github.com/insertr/insertr/internal/engine"
|
||||
)
|
||||
|
||||
@@ -21,12 +22,12 @@ type SiteManager struct {
|
||||
enhancer *Enhancer
|
||||
mutex sync.RWMutex
|
||||
devMode bool
|
||||
contentClient engine.ContentClient
|
||||
contentClient db.ContentRepository
|
||||
authProvider *engine.AuthProvider
|
||||
}
|
||||
|
||||
// NewSiteManager creates a new site manager
|
||||
func NewSiteManager(contentClient engine.ContentClient, devMode bool) *SiteManager {
|
||||
func NewSiteManager(contentClient db.ContentRepository, devMode bool) *SiteManager {
|
||||
return &SiteManager{
|
||||
sites: make(map[string]*SiteConfig),
|
||||
enhancer: NewDefaultEnhancer(contentClient, ""), // siteID will be set per operation
|
||||
@@ -37,7 +38,7 @@ func NewSiteManager(contentClient engine.ContentClient, devMode bool) *SiteManag
|
||||
}
|
||||
|
||||
// NewSiteManagerWithAuth creates a new site manager with auth provider
|
||||
func NewSiteManagerWithAuth(contentClient engine.ContentClient, devMode bool, authProvider *engine.AuthProvider) *SiteManager {
|
||||
func NewSiteManagerWithAuth(contentClient db.ContentRepository, devMode bool, authProvider *engine.AuthProvider) *SiteManager {
|
||||
if authProvider == nil {
|
||||
authProvider = &engine.AuthProvider{Type: "mock"}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user