diff --git a/cmd/serve.go b/cmd/serve.go index c789421..f128f00 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -110,7 +110,7 @@ func runServe(cmd *cobra.Command, args []string) { } // Initialize content client for site manager - contentClient := content.NewDatabaseClient(database) + contentClient := engine.NewDatabaseClient(database) // Initialize site manager with auth provider authProvider := &engine.AuthProvider{Type: authConfig.Provider} @@ -227,6 +227,18 @@ func runServe(cmd *cobra.Command, args []string) { contentRouter.Get("/{id}/versions", contentHandler.GetContentVersions) contentRouter.Post("/{id}/rollback", contentHandler.RollbackContent) }) + + // Collection endpoints + apiRouter.Route("/collections", func(collectionRouter chi.Router) { + collectionRouter.Get("/", contentHandler.GetAllCollections) + collectionRouter.Get("/{id}", contentHandler.GetCollection) + + // Collection item endpoints + collectionRouter.Get("/{id}/items", contentHandler.GetCollectionItems) + collectionRouter.Post("/{id}/items", contentHandler.CreateCollectionItem) + collectionRouter.Put("/{id}/items/{item_id}", contentHandler.UpdateCollectionItem) + collectionRouter.Delete("/{id}/items/{item_id}", contentHandler.DeleteCollectionItem) + }) }) // Static site serving - serve registered sites at /sites/{site_id} @@ -259,13 +271,21 @@ func runServe(cmd *cobra.Command, args []string) { fmt.Printf("🌐 Server running at: http://localhost%s\n", addr) fmt.Printf("💚 Health check: http://localhost%s/health\n", addr) fmt.Printf("📊 API endpoints:\n") - fmt.Printf(" GET /api/content?site_id={site}\n") - fmt.Printf(" GET /api/content/{id}?site_id={site}\n") - fmt.Printf(" GET /api/content/bulk?site_id={site}&ids[]={id1}&ids[]={id2}\n") - fmt.Printf(" POST /api/content\n") - fmt.Printf(" PUT /api/content/{id}\n") - fmt.Printf(" GET /api/content/{id}/versions?site_id={site}\n") - fmt.Printf(" POST /api/content/{id}/rollback\n") + fmt.Printf(" Content:\n") + fmt.Printf(" GET /api/content?site_id={site}\n") + fmt.Printf(" GET /api/content/{id}?site_id={site}\n") + fmt.Printf(" GET /api/content/bulk?site_id={site}&ids[]={id1}&ids[]={id2}\n") + fmt.Printf(" POST /api/content\n") + fmt.Printf(" PUT /api/content/{id}\n") + fmt.Printf(" GET /api/content/{id}/versions?site_id={site}\n") + fmt.Printf(" POST /api/content/{id}/rollback\n") + fmt.Printf(" Collections:\n") + fmt.Printf(" GET /api/collections?site_id={site}\n") + fmt.Printf(" GET /api/collections/{id}?site_id={site}\n") + fmt.Printf(" GET /api/collections/{id}/items?site_id={site}\n") + fmt.Printf(" POST /api/collections/{id}/items\n") + fmt.Printf(" PUT /api/collections/{id}/items/{item_id}\n") + fmt.Printf(" DELETE /api/collections/{id}/items/{item_id}\n") fmt.Printf("🌐 Static sites:\n") for siteID, _ := range siteManager.GetAllSites() { fmt.Printf(" %s: http://localhost%s/sites/%s/\n", siteID, addr, siteID) diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 158936d..7eaaafe 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -858,3 +858,437 @@ func (h *ContentHandler) ServeInsertrCSS(w http.ResponseWriter, r *http.Request) // Copy file contents to response io.Copy(w, file) } + +// Collection API handlers + +// GetCollection handles GET /api/collections/{id} +func (h *ContentHandler) GetCollection(w http.ResponseWriter, r *http.Request) { + collectionID := chi.URLParam(r, "id") + siteID := r.URL.Query().Get("site_id") + + if siteID == "" { + http.Error(w, "site_id parameter is required", http.StatusBadRequest) + return + } + + var collection interface{} + var err error + + switch h.database.GetDBType() { + case "sqlite3": + collection, err = h.database.GetSQLiteQueries().GetCollection(context.Background(), sqlite.GetCollectionParams{ + ID: collectionID, + SiteID: siteID, + }) + case "postgresql": + collection, err = h.database.GetPostgreSQLQueries().GetCollection(context.Background(), postgresql.GetCollectionParams{ + ID: collectionID, + SiteID: siteID, + }) + default: + http.Error(w, "Unsupported database type", http.StatusInternalServerError) + return + } + + if err != nil { + if err == sql.ErrNoRows { + http.Error(w, "Collection not found", http.StatusNotFound) + return + } + http.Error(w, fmt.Sprintf("Database error: %v", err), http.StatusInternalServerError) + return + } + + apiCollection := h.convertToAPICollection(collection) + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(apiCollection) +} + +// GetAllCollections handles GET /api/collections +func (h *ContentHandler) GetAllCollections(w http.ResponseWriter, r *http.Request) { + siteID := r.URL.Query().Get("site_id") + if siteID == "" { + http.Error(w, "site_id parameter is required", http.StatusBadRequest) + return + } + + var collections interface{} + var err error + + switch h.database.GetDBType() { + case "sqlite3": + collections, err = h.database.GetSQLiteQueries().GetAllCollections(context.Background(), siteID) + case "postgresql": + collections, err = h.database.GetPostgreSQLQueries().GetAllCollections(context.Background(), siteID) + default: + http.Error(w, "Unsupported database type", http.StatusInternalServerError) + return + } + + if err != nil { + http.Error(w, fmt.Sprintf("Database error: %v", err), http.StatusInternalServerError) + return + } + + apiCollections := h.convertToAPICollectionList(collections) + response := CollectionResponse{Collections: apiCollections} + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + +// GetCollectionItems handles GET /api/collections/{id}/items +func (h *ContentHandler) GetCollectionItems(w http.ResponseWriter, r *http.Request) { + collectionID := chi.URLParam(r, "id") + siteID := r.URL.Query().Get("site_id") + + if siteID == "" { + http.Error(w, "site_id parameter is required", http.StatusBadRequest) + return + } + + var items interface{} + var err error + + switch h.database.GetDBType() { + case "sqlite3": + items, err = h.database.GetSQLiteQueries().GetCollectionItemsWithTemplate(context.Background(), sqlite.GetCollectionItemsWithTemplateParams{ + CollectionID: collectionID, + SiteID: siteID, + }) + case "postgresql": + items, err = h.database.GetPostgreSQLQueries().GetCollectionItemsWithTemplate(context.Background(), postgresql.GetCollectionItemsWithTemplateParams{ + CollectionID: collectionID, + SiteID: siteID, + }) + default: + http.Error(w, "Unsupported database type", http.StatusInternalServerError) + return + } + + if err != nil { + http.Error(w, fmt.Sprintf("Database error: %v", err), http.StatusInternalServerError) + return + } + + apiItems := h.convertToAPICollectionItemList(items) + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "items": apiItems, + }) +} + +// CreateCollectionItem handles POST /api/collections/{id}/items +func (h *ContentHandler) CreateCollectionItem(w http.ResponseWriter, r *http.Request) { + collectionID := chi.URLParam(r, "id") + + var req CreateCollectionItemRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid JSON", http.StatusBadRequest) + return + } + + // Set defaults + if req.SiteID == "" { + req.SiteID = r.URL.Query().Get("site_id") + } + if req.SiteID == "" { + http.Error(w, "site_id is required", http.StatusBadRequest) + return + } + if req.CreatedBy == "" { + req.CreatedBy = "api" + } + if req.CollectionID == "" { + req.CollectionID = collectionID + } + + // Generate item ID + itemID := fmt.Sprintf("%s-item-%d", collectionID, time.Now().Unix()) + + var createdItem interface{} + var err error + + switch h.database.GetDBType() { + case "sqlite3": + createdItem, err = h.database.GetSQLiteQueries().CreateCollectionItem(context.Background(), sqlite.CreateCollectionItemParams{ + ItemID: itemID, + CollectionID: req.CollectionID, + SiteID: req.SiteID, + TemplateID: int64(req.TemplateID), + HtmlContent: req.HTMLContent, + Position: int64(req.Position), + LastEditedBy: req.CreatedBy, + }) + case "postgresql": + createdItem, err = h.database.GetPostgreSQLQueries().CreateCollectionItem(context.Background(), postgresql.CreateCollectionItemParams{ + ItemID: itemID, + CollectionID: req.CollectionID, + SiteID: req.SiteID, + TemplateID: int32(req.TemplateID), + HtmlContent: req.HTMLContent, + Position: int32(req.Position), + LastEditedBy: req.CreatedBy, + }) + default: + http.Error(w, "Unsupported database type", http.StatusInternalServerError) + return + } + + if err != nil { + http.Error(w, fmt.Sprintf("Failed to create collection item: %v", err), http.StatusInternalServerError) + return + } + + apiItem := h.convertToAPICollectionItem(createdItem) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(apiItem) +} + +// UpdateCollectionItem handles PUT /api/collections/{id}/items/{item_id} +func (h *ContentHandler) UpdateCollectionItem(w http.ResponseWriter, r *http.Request) { + collectionID := chi.URLParam(r, "id") + itemID := chi.URLParam(r, "item_id") + siteID := r.URL.Query().Get("site_id") + + if siteID == "" { + http.Error(w, "site_id parameter is required", http.StatusBadRequest) + return + } + + var req UpdateCollectionItemRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid JSON", http.StatusBadRequest) + return + } + + if req.UpdatedBy == "" { + req.UpdatedBy = "api" + } + + var updatedItem interface{} + var err error + + switch h.database.GetDBType() { + case "sqlite3": + updatedItem, err = h.database.GetSQLiteQueries().UpdateCollectionItem(context.Background(), sqlite.UpdateCollectionItemParams{ + ItemID: itemID, + CollectionID: collectionID, + SiteID: siteID, + HtmlContent: req.HTMLContent, + LastEditedBy: req.UpdatedBy, + }) + case "postgresql": + updatedItem, err = h.database.GetPostgreSQLQueries().UpdateCollectionItem(context.Background(), postgresql.UpdateCollectionItemParams{ + ItemID: itemID, + CollectionID: collectionID, + SiteID: siteID, + HtmlContent: req.HTMLContent, + LastEditedBy: req.UpdatedBy, + }) + default: + http.Error(w, "Unsupported database type", http.StatusInternalServerError) + return + } + + if err != nil { + if err == sql.ErrNoRows { + http.Error(w, "Collection item not found", http.StatusNotFound) + return + } + http.Error(w, fmt.Sprintf("Failed to update collection item: %v", err), http.StatusInternalServerError) + return + } + + apiItem := h.convertToAPICollectionItem(updatedItem) + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(apiItem) +} + +// DeleteCollectionItem handles DELETE /api/collections/{id}/items/{item_id} +func (h *ContentHandler) DeleteCollectionItem(w http.ResponseWriter, r *http.Request) { + collectionID := chi.URLParam(r, "id") + itemID := chi.URLParam(r, "item_id") + siteID := r.URL.Query().Get("site_id") + + if siteID == "" { + http.Error(w, "site_id parameter is required", http.StatusBadRequest) + return + } + + var err error + + switch h.database.GetDBType() { + case "sqlite3": + err = h.database.GetSQLiteQueries().DeleteCollectionItem(context.Background(), sqlite.DeleteCollectionItemParams{ + ItemID: itemID, + CollectionID: collectionID, + SiteID: siteID, + }) + case "postgresql": + err = h.database.GetPostgreSQLQueries().DeleteCollectionItem(context.Background(), postgresql.DeleteCollectionItemParams{ + ItemID: itemID, + CollectionID: collectionID, + SiteID: siteID, + }) + default: + http.Error(w, "Unsupported database type", http.StatusInternalServerError) + return + } + + if err != nil { + http.Error(w, fmt.Sprintf("Failed to delete collection item: %v", err), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusNoContent) +} + +// Collection conversion helpers + +// convertToAPICollection converts database collection to API model +func (h *ContentHandler) convertToAPICollection(dbCollection interface{}) CollectionItem { + switch h.database.GetDBType() { + case "sqlite3": + collection := dbCollection.(sqlite.Collection) + return CollectionItem{ + ID: collection.ID, + SiteID: collection.SiteID, + ContainerHTML: collection.ContainerHtml, + CreatedAt: time.Unix(collection.CreatedAt, 0), + UpdatedAt: time.Unix(collection.UpdatedAt, 0), + LastEditedBy: collection.LastEditedBy, + } + case "postgresql": + collection := dbCollection.(postgresql.Collection) + return CollectionItem{ + ID: collection.ID, + SiteID: collection.SiteID, + ContainerHTML: collection.ContainerHtml, + CreatedAt: time.Unix(collection.CreatedAt, 0), + UpdatedAt: time.Unix(collection.UpdatedAt, 0), + LastEditedBy: collection.LastEditedBy, + } + default: + return CollectionItem{} + } +} + +// convertToAPICollectionList converts database collection list to API models +func (h *ContentHandler) convertToAPICollectionList(dbCollections interface{}) []CollectionItem { + switch h.database.GetDBType() { + case "sqlite3": + collections := dbCollections.([]sqlite.Collection) + result := make([]CollectionItem, len(collections)) + for i, collection := range collections { + result[i] = CollectionItem{ + ID: collection.ID, + SiteID: collection.SiteID, + ContainerHTML: collection.ContainerHtml, + CreatedAt: time.Unix(collection.CreatedAt, 0), + UpdatedAt: time.Unix(collection.UpdatedAt, 0), + LastEditedBy: collection.LastEditedBy, + } + } + return result + case "postgresql": + collections := dbCollections.([]postgresql.Collection) + result := make([]CollectionItem, len(collections)) + for i, collection := range collections { + result[i] = CollectionItem{ + ID: collection.ID, + SiteID: collection.SiteID, + ContainerHTML: collection.ContainerHtml, + CreatedAt: time.Unix(collection.CreatedAt, 0), + UpdatedAt: time.Unix(collection.UpdatedAt, 0), + LastEditedBy: collection.LastEditedBy, + } + } + return result + default: + return []CollectionItem{} + } +} + +// convertToAPICollectionItem converts database collection item to API model +func (h *ContentHandler) convertToAPICollectionItem(dbItem interface{}) CollectionItemData { + switch h.database.GetDBType() { + case "sqlite3": + item := dbItem.(sqlite.CollectionItem) + return CollectionItemData{ + ItemID: item.ItemID, + CollectionID: item.CollectionID, + SiteID: item.SiteID, + TemplateID: int(item.TemplateID), + HTMLContent: item.HtmlContent, + Position: int(item.Position), + CreatedAt: time.Unix(item.CreatedAt, 0), + UpdatedAt: time.Unix(item.UpdatedAt, 0), + LastEditedBy: item.LastEditedBy, + } + case "postgresql": + item := dbItem.(postgresql.CollectionItem) + return CollectionItemData{ + ItemID: item.ItemID, + CollectionID: item.CollectionID, + SiteID: item.SiteID, + TemplateID: int(item.TemplateID), + HTMLContent: item.HtmlContent, + Position: int(item.Position), + CreatedAt: time.Unix(item.CreatedAt, 0), + UpdatedAt: time.Unix(item.UpdatedAt, 0), + LastEditedBy: item.LastEditedBy, + } + default: + return CollectionItemData{} + } +} + +// convertToAPICollectionItemList converts database collection item list to API models +func (h *ContentHandler) convertToAPICollectionItemList(dbItems interface{}) []CollectionItemData { + switch h.database.GetDBType() { + case "sqlite3": + items := dbItems.([]sqlite.GetCollectionItemsWithTemplateRow) + result := make([]CollectionItemData, len(items)) + for i, item := range items { + result[i] = CollectionItemData{ + ItemID: item.ItemID, + CollectionID: item.CollectionID, + SiteID: item.SiteID, + TemplateID: int(item.TemplateID), + HTMLContent: item.HtmlContent, + Position: int(item.Position), + CreatedAt: time.Unix(item.CreatedAt, 0), + UpdatedAt: time.Unix(item.UpdatedAt, 0), + LastEditedBy: item.LastEditedBy, + TemplateName: item.TemplateName, + } + } + return result + case "postgresql": + items := dbItems.([]postgresql.GetCollectionItemsWithTemplateRow) + result := make([]CollectionItemData, len(items)) + for i, item := range items { + result[i] = CollectionItemData{ + ItemID: item.ItemID, + CollectionID: item.CollectionID, + SiteID: item.SiteID, + TemplateID: int(item.TemplateID), + HTMLContent: item.HtmlContent, + Position: int(item.Position), + CreatedAt: time.Unix(item.CreatedAt, 0), + UpdatedAt: time.Unix(item.UpdatedAt, 0), + LastEditedBy: item.LastEditedBy, + TemplateName: item.TemplateName, + } + } + return result + default: + return []CollectionItemData{} + } +} diff --git a/internal/api/models.go b/internal/api/models.go index 2e94b37..75f50f4 100644 --- a/internal/api/models.go +++ b/internal/api/models.go @@ -54,3 +54,72 @@ type RollbackContentRequest struct { VersionID int64 `json:"version_id"` RolledBackBy string `json:"rolled_back_by,omitempty"` } + +// Collection API models +type CollectionItem struct { + ID string `json:"id"` + SiteID string `json:"site_id"` + ContainerHTML string `json:"container_html"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + LastEditedBy string `json:"last_edited_by"` + Templates []CollectionTemplate `json:"templates,omitempty"` + Items []CollectionItemData `json:"items,omitempty"` +} + +type CollectionTemplate struct { + TemplateID int `json:"template_id"` + CollectionID string `json:"collection_id"` + SiteID string `json:"site_id"` + Name string `json:"name"` + HTMLTemplate string `json:"html_template"` + IsDefault bool `json:"is_default"` + CreatedAt time.Time `json:"created_at"` +} + +type CollectionItemData struct { + ItemID string `json:"item_id"` + CollectionID string `json:"collection_id"` + SiteID string `json:"site_id"` + TemplateID int `json:"template_id"` + HTMLContent string `json:"html_content"` + Position int `json:"position"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + LastEditedBy string `json:"last_edited_by"` + TemplateName string `json:"template_name,omitempty"` +} + +type CollectionResponse struct { + Collections []CollectionItem `json:"collections"` +} + +// Collection request models +type CreateCollectionRequest struct { + ContainerHTML string `json:"container_html"` + SiteID string `json:"site_id,omitempty"` + CreatedBy string `json:"created_by,omitempty"` +} + +type CreateCollectionTemplateRequest struct { + Name string `json:"name"` + HTMLTemplate string `json:"html_template"` + IsDefault bool `json:"is_default"` + CollectionID string `json:"collection_id,omitempty"` + SiteID string `json:"site_id,omitempty"` +} + +type CreateCollectionItemRequest struct { + TemplateID int `json:"template_id"` + HTMLContent string `json:"html_content"` + Position int `json:"position"` + CollectionID string `json:"collection_id,omitempty"` + SiteID string `json:"site_id,omitempty"` + CreatedBy string `json:"created_by,omitempty"` +} + +type UpdateCollectionItemRequest struct { + HTMLContent string `json:"html_content"` + Position int `json:"position"` + UpdatedBy string `json:"updated_by,omitempty"` +} diff --git a/internal/content/database.go b/internal/content/database.go deleted file mode 100644 index cbbfafd..0000000 --- a/internal/content/database.go +++ /dev/null @@ -1,252 +0,0 @@ -package content - -import ( - "context" - "database/sql" - "fmt" - "time" - - "github.com/insertr/insertr/internal/db" - "github.com/insertr/insertr/internal/db/postgresql" - "github.com/insertr/insertr/internal/db/sqlite" - "github.com/insertr/insertr/internal/engine" -) - -// Helper function to convert sql.NullString to string -func getStringFromNullString(ns sql.NullString) string { - if ns.Valid { - return ns.String - } - return "" -} - -// DatabaseClient implements ContentClient for direct database access -type DatabaseClient struct { - db *db.Database -} - -// NewDatabaseClient creates a new database content client -func NewDatabaseClient(database *db.Database) *DatabaseClient { - return &DatabaseClient{ - db: database, - } -} - -// GetContent fetches a single content item by ID -func (d *DatabaseClient) GetContent(siteID, contentID string) (*engine.ContentItem, error) { - ctx := context.Background() - var content interface{} - var err error - - switch d.db.GetDBType() { - case "sqlite3": - content, err = d.db.GetSQLiteQueries().GetContent(ctx, sqlite.GetContentParams{ - ID: contentID, - SiteID: siteID, - }) - case "postgresql": - content, err = d.db.GetPostgreSQLQueries().GetContent(ctx, postgresql.GetContentParams{ - ID: contentID, - SiteID: siteID, - }) - default: - return nil, fmt.Errorf("unsupported database type: %s", d.db.GetDBType()) - } - - if err != nil { - if err == sql.ErrNoRows { - return nil, nil // Content not found, return nil without error - } - return nil, fmt.Errorf("database error: %w", err) - } - - item := d.convertToContentItem(content) - return &item, nil -} - -// GetBulkContent fetches multiple content items by IDs -func (d *DatabaseClient) GetBulkContent(siteID string, contentIDs []string) (map[string]engine.ContentItem, error) { - if len(contentIDs) == 0 { - return make(map[string]engine.ContentItem), nil - } - - ctx := context.Background() - var dbContent interface{} - var err error - - switch d.db.GetDBType() { - case "sqlite3": - dbContent, err = d.db.GetSQLiteQueries().GetBulkContent(ctx, sqlite.GetBulkContentParams{ - SiteID: siteID, - Ids: contentIDs, - }) - case "postgresql": - dbContent, err = d.db.GetPostgreSQLQueries().GetBulkContent(ctx, postgresql.GetBulkContentParams{ - SiteID: siteID, - Ids: contentIDs, - }) - default: - return nil, fmt.Errorf("unsupported database type: %s", d.db.GetDBType()) - } - - if err != nil { - return nil, fmt.Errorf("database error: %w", err) - } - - items := d.convertToContentItemList(dbContent) - - // Convert slice to map for easy lookup - result := make(map[string]engine.ContentItem) - for _, item := range items { - result[item.ID] = item - } - - return result, nil -} - -// GetAllContent fetches all content for a site -func (d *DatabaseClient) GetAllContent(siteID string) (map[string]engine.ContentItem, error) { - ctx := context.Background() - var dbContent interface{} - var err error - - switch d.db.GetDBType() { - case "sqlite3": - dbContent, err = d.db.GetSQLiteQueries().GetAllContent(ctx, siteID) - case "postgresql": - dbContent, err = d.db.GetPostgreSQLQueries().GetAllContent(ctx, siteID) - default: - return nil, fmt.Errorf("unsupported database type: %s", d.db.GetDBType()) - } - - if err != nil { - return nil, fmt.Errorf("database error: %w", err) - } - - items := d.convertToContentItemList(dbContent) - - // Convert slice to map for easy lookup - result := make(map[string]engine.ContentItem) - for _, item := range items { - result[item.ID] = item - } - - return result, nil -} - -// convertToContentItem converts database models to engine.ContentItem -func (d *DatabaseClient) convertToContentItem(content interface{}) engine.ContentItem { - switch d.db.GetDBType() { - case "sqlite3": - c := content.(sqlite.Content) - return engine.ContentItem{ - ID: c.ID, - SiteID: c.SiteID, - HTMLContent: c.HtmlContent, - OriginalTemplate: getStringFromNullString(c.OriginalTemplate), - UpdatedAt: time.Unix(c.UpdatedAt, 0).Format(time.RFC3339), - } - case "postgresql": - c := content.(postgresql.Content) - return engine.ContentItem{ - ID: c.ID, - SiteID: c.SiteID, - HTMLContent: c.HtmlContent, - OriginalTemplate: getStringFromNullString(c.OriginalTemplate), - UpdatedAt: time.Unix(c.UpdatedAt, 0).Format(time.RFC3339), - } - } - return engine.ContentItem{} // Should never happen -} - -// convertToContentItemList converts database model lists to engine.ContentItem slice -func (d *DatabaseClient) convertToContentItemList(contentList interface{}) []engine.ContentItem { - switch d.db.GetDBType() { - case "sqlite3": - list := contentList.([]sqlite.Content) - items := make([]engine.ContentItem, len(list)) - for i, content := range list { - items[i] = d.convertToContentItem(content) - } - return items - case "postgresql": - list := contentList.([]postgresql.Content) - items := make([]engine.ContentItem, len(list)) - for i, content := range list { - items[i] = d.convertToContentItem(content) - } - return items - } - return []engine.ContentItem{} // Should never happen -} - -// CreateContent creates a new content item -func (c *DatabaseClient) CreateContent(siteID, contentID, htmlContent, originalTemplate, lastEditedBy string) (*engine.ContentItem, error) { - switch c.db.GetDBType() { - case "sqlite3": - content, err := c.db.GetSQLiteQueries().CreateContent(context.Background(), sqlite.CreateContentParams{ - ID: contentID, - SiteID: siteID, - HtmlContent: htmlContent, - OriginalTemplate: toNullString(originalTemplate), - LastEditedBy: lastEditedBy, - }) - if err != nil { - return nil, err - } - return &engine.ContentItem{ - ID: content.ID, - SiteID: content.SiteID, - HTMLContent: content.HtmlContent, - OriginalTemplate: getStringFromNullString(content.OriginalTemplate), - LastEditedBy: content.LastEditedBy, - }, nil - - case "postgresql": - content, err := c.db.GetPostgreSQLQueries().CreateContent(context.Background(), postgresql.CreateContentParams{ - ID: contentID, - SiteID: siteID, - HtmlContent: htmlContent, - OriginalTemplate: toNullString(originalTemplate), - LastEditedBy: lastEditedBy, - }) - if err != nil { - return nil, err - } - return &engine.ContentItem{ - ID: content.ID, - SiteID: content.SiteID, - HTMLContent: content.HtmlContent, - OriginalTemplate: getStringFromNullString(content.OriginalTemplate), - LastEditedBy: content.LastEditedBy, - }, nil - - default: - return nil, fmt.Errorf("unsupported database type: %s", c.db.GetDBType()) - } -} - -// Helper function to convert string to sql.NullString -func toNullString(s string) sql.NullString { - if s == "" { - return sql.NullString{Valid: false} - } - return sql.NullString{String: s, Valid: true} -} - -// Collection method stubs - TODO: Implement these -func (d *DatabaseClient) GetCollection(siteID, collectionID string) (*engine.CollectionItem, error) { - return nil, fmt.Errorf("collection operations not implemented in content.DatabaseClient") -} - -func (d *DatabaseClient) CreateCollection(siteID, collectionID, containerHTML, lastEditedBy string) (*engine.CollectionItem, error) { - return nil, fmt.Errorf("collection operations not implemented in content.DatabaseClient") -} - -func (d *DatabaseClient) GetCollectionItems(siteID, collectionID string) ([]engine.CollectionItemWithTemplate, error) { - return nil, fmt.Errorf("collection operations not implemented in content.DatabaseClient") -} - -func (d *DatabaseClient) CreateCollectionTemplate(siteID, collectionID, name, htmlTemplate string, isDefault bool) (*engine.CollectionTemplateItem, error) { - return nil, fmt.Errorf("collection operations not implemented in content.DatabaseClient") -}