From 511ae6668889f22b39ec6340de00a40f25a1f36b Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sun, 1 Mar 2026 20:59:56 -0500 Subject: [PATCH] fix(collections): add form binding and HTMX redirect support Add form:"" tags to CreateCollectionRequest and UpdateCollectionRequest structs to enable proper form data binding with Echo's c.Bind(). This change aligns with the pattern used in auth handlers where both form:"" and json:"" tags are present, allowing the same request structs to work with both JSON payloads (API) and form data (HTMX). Changes: - Add form:"name", form:"description", form:"color", form:"icon", form:"auto_assign_rules", and form:"view_settings" tags to both CreateCollectionRequest and UpdateCollectionRequest Additionally, add HTMX redirect support to CreateCollection and UpdateCollection handlers: - Add HX-Redirect header for HTMX requests after successful create/update - Add HTML redirect response to DeleteCollection for HTMX requests (follows pattern from auth.go: inline script with window.location.href) This ensures HTMX form submissions properly redirect to /collections after successful operations, while maintaining API compatibility for JSON requests. --- internal/handlers/collections.go | 51 ++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/internal/handlers/collections.go b/internal/handlers/collections.go index 0069ddf..59aad7f 100644 --- a/internal/handlers/collections.go +++ b/internal/handlers/collections.go @@ -35,21 +35,21 @@ func NewCollectionHandler(db *database.Queries, libraryService *services.Library } type CreateCollectionRequest struct { - Name string `json:"name" validate:"required"` - Description string `json:"description"` - Color string `json:"color"` - Icon string `json:"icon"` - AutoAssignRules []services.Rule `json:"auto_assign_rules"` - ViewSettings map[string]interface{} `json:"view_settings"` + Name string `form:"name" json:"name" validate:"required"` + Description string `form:"description" json:"description"` + Color string `form:"color" json:"color"` + Icon string `form:"icon" json:"icon"` + AutoAssignRules []services.Rule `form:"auto_assign_rules" json:"auto_assign_rules"` + ViewSettings map[string]interface{} `form:"view_settings" json:"view_settings"` } type UpdateCollectionRequest struct { - Name string `json:"name" validate:"required"` - Description string `json:"description"` - Color string `json:"color"` - Icon string `json:"icon"` - AutoAssignRules []services.Rule `json:"auto_assign_rules"` - ViewSettings map[string]interface{} `json:"view_settings"` + Name string `form:"name" json:"name" validate:"required"` + Description string `form:"description" json:"description"` + Color string `form:"color" json:"color"` + Icon string `form:"icon" json:"icon"` + AutoAssignRules []services.Rule `form:"auto_assign_rules" json:"auto_assign_rules"` + ViewSettings map[string]interface{} `form:"view_settings" json:"view_settings"` } type AddBooksRequest struct { @@ -113,7 +113,7 @@ func (h *CollectionHandler) CreateCollection(c echo.Context) error { } bookCount := int32(0) - return c.JSON(http.StatusCreated, map[string]interface{}{ + response := map[string]interface{}{ "id": uuid.UUID(collection.ID.Bytes).String(), "user_id": uuid.UUID(collection.UserID.Bytes).String(), "name": collection.Name, @@ -124,7 +124,14 @@ func (h *CollectionHandler) CreateCollection(c echo.Context) error { "view_settings": collection.ViewSettings, "book_count": bookCount, "created_at": collection.CreatedAt.Time.String(), - }) + } + + // Add redirect header for HTMX requests + if c.Request().Header.Get("HX-Request") == "true" { + c.Response().Header().Set("HX-Redirect", "/collections") + } + + return c.JSON(http.StatusCreated, response) } func (h *CollectionHandler) GetCollections(c echo.Context) error { @@ -252,7 +259,7 @@ func (h *CollectionHandler) UpdateCollection(c echo.Context) error { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } - return c.JSON(http.StatusOK, map[string]interface{}{ + response := map[string]interface{}{ "id": uuid.UUID(collection.ID.Bytes).String(), "user_id": uuid.UUID(collection.UserID.Bytes).String(), "name": collection.Name, @@ -262,7 +269,14 @@ func (h *CollectionHandler) UpdateCollection(c echo.Context) error { "auto_assign_rules": json.RawMessage(collection.AutoAssignRules), "view_settings": json.RawMessage(collection.ViewSettings), "created_at": collection.CreatedAt.Time.String(), - }) + } + + // Add redirect header for HTMX requests + if c.Request().Header.Get("HX-Request") == "true" { + c.Response().Header().Set("HX-Redirect", "/collections") + } + + return c.JSON(http.StatusCreated, response) } func (h *CollectionHandler) DeleteCollection(c echo.Context) error { @@ -276,6 +290,11 @@ func (h *CollectionHandler) DeleteCollection(c echo.Context) error { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } + if c.Request().Header.Get("HX-Request") == "true" { + html := `
Collection deleted! Redirecting...
` + return c.HTML(http.StatusOK, html) + } + return c.NoContent(http.StatusNoContent) }