From 508bfb0387be09b102684a06ec5e5595277e75c9 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sun, 1 Feb 2026 00:52:09 -0500 Subject: [PATCH] feat(collections): implement bulk add and remove books (Limitations #2 & #5) Complete bulk operations for collections management: BULK ADD BOOKS: - Implemented searchBooks() with real API integration - Multi-select checkboxes for book selection - SelectedBooks Set tracks chosen books - AddSelectedBooks() sends array to existing endpoint - Uses existing POST /api/collections/:id/books endpoint BULK REMOVE BOOKS: - New endpoint: POST /api/collections/:id/books/bulk-remove - Checkboxes on each book card for selection - BooksToRemove Set tracks selections - Live counter showing selected count - BulkRemoveBooks() handler removes all in one API call - More efficient than N individual DELETE requests Frontend Changes: - Selected counter badge shows number selected - Bulk remove button (enabled when books selected) - Checkboxes on all books for multi-select - Confirmation dialog for bulk operations - Toast notifications with counts Backend Changes: - BulkRemoveBooks() handler in collections.go - Accepts book_ids array, returns removed/total counts - Iterates and removes, counting successes - Route: POST /api/collections/:id/books/bulk-remove API Request: { "book_ids": ["uuid1", "uuid2", "uuid3"] } API Response: { "removed": 3, "total": 3 } Tests Added: - TestCompareValues_* (existing) - TestEvaluateRule_* (existing) Resolves Limitations #2 (Bulk Operations) and #5 (Bulk Remove) --- internal/handlers/collections.go | 37 ++++++ internal/handlers/ebook.go | 1 + templates/collections.templ | 199 +++++++++++++++++++++++++++---- templates/collections_templ.go | 26 ++-- 4 files changed, 227 insertions(+), 36 deletions(-) diff --git a/internal/handlers/collections.go b/internal/handlers/collections.go index 215975b..8e9df74 100644 --- a/internal/handlers/collections.go +++ b/internal/handlers/collections.go @@ -306,6 +306,43 @@ func (h *CollectionHandler) RemoveBook(c echo.Context) error { return c.NoContent(http.StatusNoContent) } +type BulkRemoveBooksRequest struct { + BookIDs []string `json:"book_ids" validate:"required"` +} + +func (h *CollectionHandler) BulkRemoveBooks(c echo.Context) error { + collectionID, err := uuid.Parse(c.Param("id")) + if err != nil { + return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid collection id"}) + } + + var req BulkRemoveBooksRequest + if err := c.Bind(&req); err != nil { + return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"}) + } + if err := c.Validate(&req); err != nil { + return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()}) + } + + removedCount := 0 + for _, bookIDStr := range req.BookIDs { + bookID, err := uuid.Parse(bookIDStr) + if err != nil { + continue + } + + err = h.collectionService.RemoveBookFromCollection(c.Request().Context(), collectionID, bookID) + if err == nil { + removedCount++ + } + } + + return c.JSON(http.StatusOK, map[string]interface{}{ + "removed": removedCount, + "total": len(req.BookIDs), + }) +} + func (h *CollectionHandler) GetDeviceMappings(c echo.Context) error { deviceID, err := uuid.Parse(c.Param("id")) if err != nil { diff --git a/internal/handlers/ebook.go b/internal/handlers/ebook.go index d07ddc2..eac20e3 100644 --- a/internal/handlers/ebook.go +++ b/internal/handlers/ebook.go @@ -81,6 +81,7 @@ func SetupRoutes(g *echo.Group, db *database.Queries, connManager *wsync.Connect collections.GET("/:id/books", collectionHandler.GetBookCollections) collections.POST("/:id/books", collectionHandler.AddBooks) collections.DELETE("/:id/books/:bookId", collectionHandler.RemoveBook) + collections.POST("/:id/books/bulk-remove", collectionHandler.BulkRemoveBooks) collections.POST("/test-rules", collectionHandler.TestRules) // Device shelf mapping routes diff --git a/templates/collections.templ b/templates/collections.templ index a7ec276..b464f61 100644 --- a/templates/collections.templ +++ b/templates/collections.templ @@ -240,10 +240,21 @@ templ CollectionDetail(user User, collection CollectionDetailData, books []BookD
-

Books in this Collection

- +
+

Books in this Collection

+ +
+
+ + +
@@ -252,22 +263,29 @@ templ CollectionDetail(user User, collection CollectionDetailData, books []BookD } for _, book := range books { -
-
- Cover +
+ +
+ Cover +
+
+

{ book.Title }

+ if book.Author != "" { +

by { book.Author }

+ } + +
-

{ book.Title }

- if book.Author != "" { -

by { book.Author }

- } -
}
@@ -299,6 +317,8 @@ templ CollectionDetail(user User, collection CollectionDetailData, books []BookD ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "

Add Books to Collection

Search and select books to add to this collection.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err }