diff --git a/cmd/server/main.go b/cmd/server/main.go index d9b8924..3ff3b24 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -607,7 +607,6 @@ func main() { return c.HTML(http.StatusOK, buf.String()) }) - // Queue Management route (protected) - SSR version protected.GET("/queue", func(c echo.Context) error { userID := c.Get("user_id").(string) @@ -659,7 +658,7 @@ func main() { }) // Collections management route (protected) - SSR version - collectionHandler := handlers.NewCollectionHandler(queries) + collectionHandler := handlers.NewCollectionHandler(queries, connManager) collections := protected.Group("/collections") collections.GET("", func(c echo.Context) error { user, err := getTemplateUserWithTheme(c, queries) diff --git a/internal/handlers/collections.go b/internal/handlers/collections.go index 8e9df74..42a882e 100644 --- a/internal/handlers/collections.go +++ b/internal/handlers/collections.go @@ -3,11 +3,13 @@ package handlers import ( "bookmann/internal/database" "bookmann/internal/services" + wsync "bookmann/internal/sync" "encoding/json" "fmt" "net/http" "strconv" "strings" + "time" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" @@ -17,12 +19,14 @@ import ( type CollectionHandler struct { db *database.Queries collectionService *services.CollectionService + connManager *wsync.ConnectionManager } -func NewCollectionHandler(db *database.Queries) *CollectionHandler { +func NewCollectionHandler(db *database.Queries, connManager *wsync.ConnectionManager) *CollectionHandler { return &CollectionHandler{ db: db, collectionService: services.NewCollectionService(db), + connManager: connManager, } } @@ -276,12 +280,31 @@ func (h *CollectionHandler) AddBooks(c echo.Context) error { return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()}) } + addedCount := 0 + var addedBookIDs []string for _, bookIDStr := range req.BookIDs { bookID, err := uuid.Parse(bookIDStr) if err != nil { continue } - h.collectionService.AddBookToCollection(c.Request().Context(), collectionID, bookID, userUUID) + err = h.collectionService.AddBookToCollection(c.Request().Context(), collectionID, bookID, userUUID) + if err == nil { + addedCount++ + addedBookIDs = append(addedBookIDs, bookID.String()) + } + } + + if addedCount > 0 && h.connManager != nil { + h.connManager.Broadcast(wsync.BroadcastMessage{ + Type: "collection_updated", + Timestamp: time.Now().Format(time.RFC3339), + Data: map[string]interface{}{ + "collection_id": collectionID.String(), + "action": "books_added", + "book_ids": addedBookIDs, + "count": addedCount, + }, + }) } return c.NoContent(http.StatusNoContent) @@ -325,6 +348,7 @@ func (h *CollectionHandler) BulkRemoveBooks(c echo.Context) error { } removedCount := 0 + var removedBookIDs []string for _, bookIDStr := range req.BookIDs { bookID, err := uuid.Parse(bookIDStr) if err != nil { @@ -334,9 +358,23 @@ func (h *CollectionHandler) BulkRemoveBooks(c echo.Context) error { err = h.collectionService.RemoveBookFromCollection(c.Request().Context(), collectionID, bookID) if err == nil { removedCount++ + removedBookIDs = append(removedBookIDs, bookID.String()) } } + if removedCount > 0 && h.connManager != nil { + h.connManager.Broadcast(wsync.BroadcastMessage{ + Type: "collection_updated", + Timestamp: time.Now().Format(time.RFC3339), + Data: map[string]interface{}{ + "collection_id": collectionID.String(), + "action": "books_removed", + "book_ids": removedBookIDs, + "count": removedCount, + }, + }) + } + return c.JSON(http.StatusOK, map[string]interface{}{ "removed": removedCount, "total": len(req.BookIDs), diff --git a/internal/handlers/ebook.go b/internal/handlers/ebook.go index eac20e3..281d815 100644 --- a/internal/handlers/ebook.go +++ b/internal/handlers/ebook.go @@ -71,7 +71,7 @@ func SetupRoutes(g *echo.Group, db *database.Queries, connManager *wsync.Connect h := NewHandler(db, connManager) // Collections API routes - collectionHandler := NewCollectionHandler(db) + collectionHandler := NewCollectionHandler(db, connManager) collections := g.Group("/collections") collections.GET("", collectionHandler.GetCollections) collections.POST("", collectionHandler.CreateCollection) diff --git a/templates/collections.templ b/templates/collections.templ index a17d48c..76b8623 100644 --- a/templates/collections.templ +++ b/templates/collections.templ @@ -335,11 +335,46 @@ templ CollectionDetail(user User, collection CollectionDetailData, books []BookD }, }{ end } ]; + let ws = null; function backToCollections() { window.location.href = '/collections'; } + function connectWebSocket() { + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const wsUrl = `${protocol}//${window.location.host}/ws/sync`; + + ws = new WebSocket(wsUrl); + + ws.onopen = function() { + console.log('WebSocket connected'); + }; + + ws.onmessage = function(event) { + const message = JSON.parse(event.data); + + if (message.type === 'collection_updated' && message.data.collection_id === collectionId) { + showToast(`Collection updated: ${message.data.action} (${message.data.count} books)`, 'info'); + + setTimeout(() => { + location.reload(); + }, 1000); + } + }; + + ws.onclose = function() { + console.log('WebSocket disconnected, reconnecting in 5s...'); + setTimeout(connectWebSocket, 5000); + }; + + ws.onerror = function(error) { + console.error('WebSocket error:', error); + }; + } + + connectWebSocket(); + function filterCollectionBooks() { const searchTerm = document.getElementById('collection-search').value.toLowerCase(); const booksContainer = document.getElementById('books-container'); diff --git a/templates/collections_templ.go b/templates/collections_templ.go index 2024bb3..f0c8f7e 100644 --- a/templates/collections_templ.go +++ b/templates/collections_templ.go @@ -233,7 +233,7 @@ func CollectionDetail(user User, collection CollectionDetailData, books []BookDa return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
Search and select books to add to this collection.