feat: implement collection library filter with WebSocket improvements and test coverage

This commit adds comprehensive functionality for filtering collections by library,
improves WebSocket real-time updates with user activity detection, and adds
extensive test coverage.

## Core Features

### Collection Library Filter
- Added library_id parameter to media-items search API
- Collections can now be filtered by specific library
- Toggle UI component for enabling/disabling library filter
- Default state is "checked" when library_id is present
- Consistent behavior across partial and fuzzy search modes

### WebSocket Auto-Reload Mitigation
- Added user activity detection to prevent disruptive page reloads
- Checks if user is actively typing in INPUT/TEXTAREA/SELECT elements
- Skips auto-reload when user is interacting with form elements
- Toast notifications still show for awareness
- Prevents data loss during editing operations

## Implementation Changes

### Backend
- internal/database/queries.sql.go: Added library filter support to search queries
- internal/handlers/media.go: Enhanced search with library_id parameter validation
- internal/handlers/collections.go: Updated collection handlers with library filtering
- internal/sync/websocket.go: Improved broadcast mechanism with user-scoped updates
- internal/router/frontend.go: Pass libraryID to collection templates

### Frontend
- templates/collections.templ: Added library filter toggle UI component
- web/src/collections.ts: TypeScript implementation with WebSocket integration
- templates/collections_templ.go: Generated template code

### Testing
- cmd/server/tests/search_test.go: Added TestCollectionSearchLibraryFilter
- cmd/server/tests/websocket_test.go: Added TestWebSocketUserScopedBroadcast
- New helper functions for creating libraries and media items via API
- Comprehensive test coverage for library filtering and user-scoped broadcasts

## API Documentation Updates

### Bruno Tests (Comprehensive Documentation)
- bruno/collections/*: Added detailed API documentation for all collection endpoints
- bruno/devices/*: Added device management and sync API documentation
- bruno/devices/kobo/api.yml: Kobo-specific sync protocol docs
- bruno/devices/koreader/api.yml: KOReader-specific sync protocol docs
- bruno/opds/*: Added OPDS feed and download endpoint documentation
- bruno/library/browse-folders.yml: Library folder browsing API docs

### New Bruno Tests
- bruno/media-items/Search All Libraries.yml: Test search without library filter
- bruno/media-items/Search Specific Library.yml: Test search with library filter
- bruno/media-items/Search Invalid Library ID.yml: Test error handling

## Documentation

- docs/developer/api/media-items/search_media_items.md: Updated with library_id parameter
- IMPLEMENTATION_COLLECTION_FIX.md: Comprehensive implementation guide with test scenarios

## Testing

### Integration Tests
- Library filter tests verify correct filtering across multiple libraries
- Invalid library_id tests ensure proper error handling
- WebSocket tests verify user-scoped broadcast behavior
- User A no longer receives User B's collection updates

### Manual Testing Scenarios
- Open collection in multiple tabs - updates propagate correctly
- Type in search box while another tab adds books - no disruptive reload
- Add/remove books from collection - toast notifications appear
- Toggle library filter - results update dynamically

## Technical Details

- WebSocket broadcasts are now user-scoped for privacy
- Active element detection uses tagName and contenteditable attributes
- Library ID validation uses UUID format checking
- Progressive enhancement maintained - page works without JavaScript
- All changes follow PROJECT_GUIDELINES.md conventions
- TypeScript only for frontend logic
- TailwindCSS only for styling
- Procedural programming style throughout

## Breaking Changes

None - all changes are additive and backward compatible.
This commit is contained in:
2026-03-04 22:37:47 -05:00
parent 72f053d179
commit 9b3d8cc949
43 changed files with 2067 additions and 438 deletions
+27
View File
@@ -5,3 +5,30 @@ info:
http:
method: GET
url: '{{opds_base_url}}/devices/{{device_id}}/download/{{book_id}}'
docs: |-
## Download Book (EPUB)
Downloads a book in standard EPUB format for reading on e-reader devices.
**Method:** GET
**Endpoint:** /devices/{device_id}/download/{book_id}
**Authentication:** Required (Device token or Bearer token)
**Path Parameters:**
- `device_id` (string): Device ID
- `book_id` (string): Book ID
**Query Parameters:**
- `format` (string, optional): File format (epub, default)
**Response:**
- Binary EPUB file content
**Status Codes:**
- 200: Success (file download)
- 401: Unauthorized
- 403: Forbidden (device not authorized for this book)
- 404: Book not found
+27
View File
@@ -5,3 +5,30 @@ info:
http:
method: GET
url: '{{opds_base_url}}/devices/{{device_id}}/download/{{book_id}}?format=kepub'
docs: |-
## Download Book (KEPUB)
Downloads a book in Kobo-specific KEPUB format, optimized for Kobo e-readers.
**Method:** GET
**Endpoint:** /devices/{device_id}/download/{book_id}
**Authentication:** Required (Device token or Bearer token)
**Path Parameters:**
- `device_id` (string): Device ID
- `book_id` (string): Book ID
**Query Parameters:**
- `format` (string, required): File format (kepub for Kobo devices)
**Response:**
- Binary KEPUB file content (EPUB with Kobo-specific enhancements)
**Status Codes:**
- 200: Success (file download)
- 401: Unauthorized
- 403: Forbidden (device not authorized for this book)
- 404: Book not found
+28
View File
@@ -5,3 +5,31 @@ info:
http:
method: GET
url: '{{opds_base_url}}/devices/{{device_id}}/cover/{{book_id}}'
docs: |-
## Get Cover Image
Retrieves the cover image for a specific book.
**Method:** GET
**Endpoint:** /devices/{device_id}/cover/{book_id}
**Authentication:** Required (Device token or Bearer token)
**Path Parameters:**
- `device_id` (string): Device ID
- `book_id` (string): Book ID
**Query Parameters:**
- `width` (integer, optional): Desired width in pixels
- `height` (integer, optional): Desired height in pixels
- `quality` (integer, optional): Image quality (1-100, default 85)
**Response:**
- Binary image file (JPEG or PNG)
**Status Codes:**
- 200: Success (image file)
- 401: Unauthorized
- 404: Book or cover not found
+28
View File
@@ -5,3 +5,31 @@ info:
http:
method: GET
url: '{{opds_base_url}}/devices/{{device_id}}/catalog?page=1&per_page=50'
docs: |-
## Get Device Catalog
Retrieves a paginated catalog of books available for the device, compatible with OPDS feed format.
**Method:** GET
**Endpoint:** /devices/{device_id}/catalog
**Authentication:** Required (Device token or Bearer token)
**Path Parameters:**
- `device_id` (string): Device ID
**Query Parameters:**
- `page` (integer, optional): Page number (default 1)
- `per_page` (integer, optional): Items per page (default 50, max 200)
- `sort_by` (string, optional): Sort field (title, author, date_added)
- `order` (string, optional): Sort order (asc, desc)
**Response:**
- OPDS 2.0 feed with book entries including metadata and download links
**Status Codes:**
- 200: Success
- 401: Unauthorized
- 404: Device not found
+27
View File
@@ -5,3 +5,30 @@ info:
http:
method: GET
url: '{{opds_base_url}}/devices/{{device_id}}/nav'
docs: |-
## Get Device Navigation
Provides OPDS navigation feed for device, including collections and series groups.
**Method:** GET
**Endpoint:** /devices/{device_id}/nav
**Authentication:** Required (Device token or Bearer token)
**Path Parameters:**
- `device_id` (string): Device ID
**Response:**
- OPDS navigation feed with links to:
- All books
- Collections
- Series
- Authors
- Recent additions
**Status Codes:**
- 200: Success
- 401: Unauthorized
- 404: Device not found
+27
View File
@@ -5,3 +5,30 @@ info:
http:
method: GET
url: '{{opds_base_url}}/devices/{{device_id}}/formats/{{book_id}}'
docs: |-
## List Formats
Lists all available file formats for a specific book.
**Method:** GET
**Endpoint:** /devices/{device_id}/formats/{book_id}
**Authentication:** Required (Device token or Bearer token)
**Path Parameters:**
- `device_id` (string): Device ID
- `book_id` (string): Book ID
**Response:**
- Array of available formats:
- `format` (string): Format identifier (epub, kepub, pdf, etc.)
- `file_size` (integer): File size in bytes
- `download_url` (string): Download link
- `optimized_for` (array): Compatible device types
**Status Codes:**
- 200: Success
- 401: Unauthorized
- 404: Book not found
+28
View File
@@ -5,3 +5,31 @@ info:
http:
method: GET
url: '{{opds_base_url}}/devices/{{device_id}}/search?q=hobbit'
docs: |-
## Search Device Catalog
Searches the device's book catalog for matching titles, authors, or series.
**Method:** GET
**Endpoint:** /devices/{device_id}/search
**Authentication:** Required (Device token or Bearer token)
**Path Parameters:**
- `device_id` (string): Device ID
**Query Parameters:**
- `q` (string, required): Search query
- `page` (integer, optional): Page number (default 1)
- `per_page` (integer, optional): Items per page (default 50)
- `search_in` (string, optional): Search fields (title, author, series, all)
**Response:**
- OPDS 2.0 feed with matching book entries
**Status Codes:**
- 200: Success
- 400: Invalid query
- 401: Unauthorized