Implement class-based template differentiation and fix collection item creation

- Add class-based template comparison to differentiate styling variants
- Implement template deduplication based on structure + class signatures
- Add GetCollectionTemplate method to repository interface and implementations
- Fix collection item creation by replacing unimplemented CreateCollectionItemAtomic
- Add template selection modal with auto-default selection in frontend
- Generate meaningful template names from distinctive CSS classes
- Fix unique constraint violations with timestamp-based collection item IDs
- Add collection templates API endpoint for frontend template fetching
- Update simple demo with featured/compact/dark testimonial variants for testing
This commit is contained in:
2025-10-27 21:02:59 +01:00
parent 0bad96d866
commit 00255cb105
10 changed files with 486 additions and 33 deletions

View File

@@ -405,6 +405,30 @@ func (h *ContentHandler) GetCollectionItems(w http.ResponseWriter, r *http.Reque
json.NewEncoder(w).Encode(items)
}
// GetCollectionTemplates handles GET /api/collections/{id}/templates
func (h *ContentHandler) GetCollectionTemplates(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
}
templates, err := h.repository.GetCollectionTemplates(context.Background(), siteID, collectionID)
if err != nil {
http.Error(w, fmt.Sprintf("Database error: %v", err), http.StatusInternalServerError)
return
}
response := map[string]interface{}{
"templates": templates,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
// CreateCollectionItem handles POST /api/collections/{id}/items
func (h *ContentHandler) CreateCollectionItem(w http.ResponseWriter, r *http.Request) {
userInfo, authErr := h.authService.ExtractUserFromRequest(r)
@@ -437,15 +461,29 @@ func (h *ContentHandler) CreateCollectionItem(w http.ResponseWriter, r *http.Req
}
if req.TemplateID == 0 {
req.TemplateID = 1 // Default to first template
http.Error(w, "template_id is required", http.StatusBadRequest)
return
}
// Use atomic collection item creation from repository
createdItem, err := h.repository.CreateCollectionItemAtomic(
context.Background(),
// Get the specific template by ID
selectedTemplate, err := h.repository.GetCollectionTemplate(context.Background(), req.TemplateID)
if err != nil {
http.Error(w, fmt.Sprintf("Template %d not found: %v", req.TemplateID, err), http.StatusBadRequest)
return
}
// Verify template belongs to the requested collection and site
if selectedTemplate.CollectionID != req.CollectionID || selectedTemplate.SiteID != req.SiteID {
http.Error(w, fmt.Sprintf("Template %d not found in collection %s", req.TemplateID, req.CollectionID), http.StatusBadRequest)
return
}
// Use engine's unified collection item creation
createdItem, err := h.engine.CreateCollectionItemFromTemplate(
req.SiteID,
req.CollectionID,
req.TemplateID,
selectedTemplate.HTMLTemplate,
userInfo.ID,
)
if err != nil {
@@ -487,10 +525,10 @@ func (h *ContentHandler) RegisterRoutes(r chi.Router) {
// COLLECTION MANAGEMENT - Groups of related content
// =============================================================================
r.Route("/collections", func(r chi.Router) {
// Public routes
r.Get("/", h.GetAllCollections) // GET /api/collections?site_id=X
r.Get("/{id}", h.GetCollection) // GET /api/collections/{id}?site_id=X
r.Get("/{id}/items", h.GetCollectionItems) // GET /api/collections/{id}/items?site_id=X
r.Get("/", h.GetAllCollections) // GET /api/collections?site_id=X
r.Get("/{id}", h.GetCollection) // GET /api/collections/{id}?site_id=X
r.Get("/{id}/items", h.GetCollectionItems) // GET /api/collections/{id}/items?site_id=X
r.Get("/{id}/templates", h.GetCollectionTemplates) // GET /api/collections/{id}/templates?site_id=X
// Protected routes
r.Group(func(r chi.Router) {