Commit Graph
30 Commits
Author SHA1 Message Date
john-okeefe 81c7c9e5cc fix: update type handling for schema changes
- Fix pgtype.UUID usage in test files by properly converting string UUIDs to pgtype.UUID
- Update numericToFloat to use pgtype.Float8 instead of pgtype.Numeric for DOUBLE PRECISION support
- Fix field name from WebURL to WebUrl to match current schema

These changes align with the recent community_rating type change to DOUBLE PRECISION
and ensure consistent type handling across the codebase.
2026-03-30 17:51:06 -04:00
john-okeefe 55ae91a147 api: add comic metadata fields to API responses
Update API layer to expose all 14 new comic metadata fields in media item
responses for both list and detail endpoints.

Handler Changes (internal/handlers/media.go):
- Added encoding/json import for JSON unmarshaling
- Updated ListMediaItems() to include 15 new fields in JSON response
- Updated GetMediaItem() to include 15 new fields in JSON response
- Added numericToFloat() helper: Convert pgtype.Numeric to float64
- Added jsonBytesToMap() helper: Convert JSONB []byte to map[string]interface{}
- Field name mapping: WebUrl (not WebURL), proper pgtype handling

TypeScript Types (web/src/types/api.d.ts):
- Updated MediaItemSummary interface with 15 new optional fields
- manga_type: 'unknown' | 'no' | 'yes' | 'yes_and_right_to_left'
- reading_direction: 'auto' | 'ltr' | 'rtl' | 'vertical'
- Universal fields: series_count, volume, imprint, age_rating, web_url, metadata_notes, community_rating
- Comic-specific: story_arc, is_black_and_white, alternate_info (nested type), scan_information, summary
- Proper TypeScript typing with string literals for type safety

API Response Fields Added:
Reading Direction:
- manga_type: Raw Manga field from ComicInfo.xml
- reading_direction: Computed direction (auto/ltr/rtl/vertical)

Universal Metadata (all formats):
- series_count, volume, imprint, age_rating, web_url, metadata_notes, community_rating

Comic-Specific:
- story_arc, is_black_and_white, alternate_info (JSONB object), scan_information, summary

Part of Phase 5: API Layer Updates
Implementation: IMPLEMENTATION_PLAN_MERGE_METADATA_READING_DIRECTION.md
2026-03-29 19:12:05 -04:00
john-okeefe ba243c223d fix: implement proper 3-state boolean handling in backend
Updated the backend services and handlers to properly detect and pass
the has_cover parameter's validity state to the database layer.

Changes:
- services/search.go: Changed HasCover type from bool to pgtype.Bool
  to support 3-state logic (NULL, TRUE, FALSE)
- handlers/media.go: Fixed 3-state detection by checking if has_cover
  exists in query params before setting Valid flag
- router/search.go: Fixed 3-state detection to match media.go logic
- router/frontend.go: Use pgtype.Bool{Valid: false} for SSR initial
  load to ensure no filtering occurs on first page load

The key fix is detecting whether the has_cover parameter was actually
sent in the request:
- Parameter not sent → pgtype.Bool{Bool: false, Valid: false}
- Parameter sent as "true" → pgtype.Bool{Bool: true, Valid: true}
- Parameter sent as "false" → pgtype.Bool{Bool: false, Valid: true}

Previously, media.go was hardcoding Valid: true, which meant it was
always filtering by has_cover=false (only books without covers) when
the parameter wasn't sent, causing searches to incorrectly return
0 results for queries like "1984".

This ensures consistency between the JSON API endpoint (media.go) and
the HTML endpoint (search.go), and fixes the critical bug where SSR
was returning 0 books on initial page load.
2026-03-27 18:07:51 -04:00
john-okeefe b1446f15f8 feat(media): add ExecuteSearch wrapper and update SearchMediaItems
Add public ExecuteSearch method to MediaHandler that delegates to SearchService. Update SearchMediaItems to use the new shared service method instead of calling SearchMediaItemsUnified directly.

- Add ExecuteSearch wrapper method (line 160-162)
- Update SearchMediaItems to use searchService.ExecuteSearch
- Maintains existing JSON API behavior while enabling shared logic
2026-03-27 14:50:28 -04:00
john-okeefe b16ee343f8 feat: add tags filter and autocomplete endpoints
- Extract tags_filter query parameter in handler
- Add tags autocomplete route handler
- Add tags case to field values search endpoint
- Keep genre_filter for backward compatibility

Provides HTTP endpoints for filtering by tags and getting autocomplete
suggestions for tag values.

Relates to IMPLEMENTATION_TAGS_FILTER.md Phase 3
2026-03-25 20:38:14 -04:00
john-okeefe ebd691c404 fix: resolve handler linting issues
Fixes various linting errors in API handlers:

1. devices.go (line 559): Removes unnecessary fmt.Sprintf wrapper
   - Change: fmt.Sprintf("%s", device.ID) -> device.ID.String()
   - Directly calls String() method instead of formatting

2. media.go (line 447): Adds missing 4th argument to fmt.Sprintf
   - Change: fmt.Sprintf(format, id, library, type)
   - Adds the missing 'type' parameter to library path formatting

3. sidecar.go: Resolves linting issue (specific fix not detailed in context)

All changes maintain existing functionality while satisfying linter
requirements.
2026-03-24 16:47:42 -04:00
john-okeefe 172536f888 refactor: update handlers to use unified search endpoint
- Update SearchMediaItems handler to use SearchService
- Add autocomplete detection for field value queries (author=value, genre=value, etc.)
- Add handleFieldValuesSearch method for dropdown population
- Add sort parameter extraction with default "title ASC"
- Remove deprecated ListMediaItemsFiltered handler
- Remove deprecated /api/media-items/filtered route registration
- Update frontend.go to use SearchMediaItemsUnified instead of ListMediaItemsFiltered
- Fix parameter passing (empty filters use Valid:true with empty values, not Valid:false)
- Add SearchQuery, IsExactSearch, SearchPattern parameters for query parsing

Handler is now a thin wrapper that extracts params and delegates to service layer.
2026-03-23 22:37:44 -04:00
john-okeefe b73d58b82e feat: add autocomplete query detection to SearchMediaItems handler
This commit enhances the SearchMediaItems handler to support dual-mode
operation: unified search with filters AND autocomplete queries for
dropdown suggestions.

**Autocomplete Detection:**
- Detects autocomplete queries: author=value, genre=value, series=value, language=value
- Routes to new handleFieldValuesSearch method for dropdown population
- Returns JSON format: {"results": [{"value": "...", "count": 47, "score": 0.8}], "total": 1}

**Unified Search Integration:**
- Replaced direct DB calls (SearchMediaItems, SearchMediaItemsFuzzy) with SearchService
- Added support for all fuzzy filters: author_filter, genre_filter, series_filter, language_filter
- Added exact filters: year_min, year_max, has_cover
- Combined search query + filters in single SearchMediaItemsUnified call
- Removed fallback logic (partial → fuzzy), now single query with smart ordering

**New Method: handleFieldValuesSearch**
- Handles autocomplete queries for all field types (author, genre, series, language)
- Validates library_id requirement
- Applies default limit=50 if not specified
- Calls SearchService.SearchFieldValues() with FieldSearchParams
- Returns consistent JSON format with results array and total count

**QueryParam Handling:**
- Fixed to not use default values (Echo QueryParam only accepts single argument)
- Properly handles empty limit parameter with default fallback
- Extracts all filter parameters for unified search

**Behavior Changes:**
- SearchMediaItems no longer requires 'q' parameter (filters-only queries now valid)
- Autocomplete queries detected before filter processing (correct priority)
- Better error messages and logging

**Service Layer Pattern:**
- Follows established pattern (FiltersService, CollectionService)
- Handler is thin - extracts params and calls service
- Business logic in SearchService (created in commit 9ab2796)

**Backward Compatibility:**
- All existing query parameters still supported
- Response format unchanged for media items search
- New response format for autocomplete queries (distinct field values)
2026-03-23 21:02:36 -04:00
john-okeefe 08435c8cd4 refactor: integrate SearchService into MediaHandler
Update MediaHandler to use new SearchService:

Changes:
- Add searchService field to MediaHandler struct
- Instantiate SearchService in NewMediaHandler constructor
- Follows established pattern (FiltersService, CollectionService)
- Keeps handler dependencies self-contained, no main.go changes needed

Design rationale:
- Handler owns its service dependencies
- Simpler initialization than passing from main.go
- More testable with direct service instantiation
- Consistent with existing codebase patterns

Next steps: Handler methods will delegate to searchService for
search operations (implementation in follow-up commits).
2026-03-22 20:35:11 -04:00
john-okeefe 77f473e090 fix(handlers): use http.ServeFile for better static file serving
Replace echo's c.File() with standard library http.ServeFile() in the
ServeFile handler. This provides more reliable static file serving and
better handles edge cases in file delivery.
2026-03-06 20:18:23 -05:00
john-okeefe 1e05470fbb refactor(handlers): update all handlers for Echo v5 compatibility
Update all handler functions to use *echo.Context (pointer) instead of echo.Context (value) as required by Echo v5.

Changes across all handler files:
- analytics.go: Update handler signatures
- auth.go: Update authentication handler signatures
- book_matching.go: Update matching handler signatures
- collections.go: Update collection handler signatures
- collections_preview_test.go: Update test signatures
- commonhandlers.go: Update common handler signatures
- conflicts.go: Update conflict handler signatures
- context.go: Update context handler signatures
- dashboard.go: Update dashboard handler signatures
- devices.go: Update device handler signatures
- jobs.go: Update job handler signatures
- kobo.go: Update Kobo handler signatures
- koreader.go: Update Koreader handler signatures
- library.go: Update library handler signatures
- matching.go: Update matching handler signatures
- media.go: Update media handler signatures
- opds.go: Update OPDS handler signatures
- progress.go: Update progress handler signatures
- queue.go: Update queue handler signatures
- refresh_token.go: Update token handler signatures
- scanner.go: Update scanner handler signatures
- sidecar.go: Update sidecar handler signatures
- sync.go: Update sync handler signatures
- system_settings.go: Update settings handler signatures
- websocket.go: Update WebSocket handler signatures

All handlers now properly implement Echo v5's pointer-based context pattern.
This change is necessary for type safety and compatibility with Echo v5's
improved context handling and WebSocket support.
2026-03-06 14:00:28 -05:00
john-okeefe 2ac42a8d91 fix: correct user context handling and error responses
- Fix SearchMediaItems to retrieve user object from context instead of string
- Remove redundant UUID parsing, use user.ID directly
- Add error logging for search failures with query details
- Fix JWT middleware to use echo.NewHTTPError for consistent error format
- Improves debugging and error response consistency across API
2026-03-06 01:52:36 -05:00
john-okeefe 9b3d8cc949 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.
2026-03-04 22:37:47 -05:00
john-okeefe 037e7c1189 feat(scanner): add debounced file watching with polling fallback
- Implement event queue with 3-second debouncing for file system events
- Add configurable polling fallback (default 3 min) via SCAN_POLL_INTERVAL_MINUTES
- Add SyncFilesystemWithDatabase to detect orphaned DB entries and new files
- Integrate utils.ResolveMediaURL for consistent media file path resolution
- Add COOKIE_SECURE env var with SameSite=LaxMode for session cookies
- Update media handler to properly decode URL paths for file serving
- Refactor scanner initialization to accept poll interval configuration
2026-02-28 01:16:27 -05:00
john-okeefe 0c0ba185dc refactor(handlers): remove FilePath from API responses
Remove the FilePath field from book metadata responses in the GetShelf endpoint.
This change improves security by not exposing internal file paths to API clients,
as the application now uses relative path storage with URL resolution via the
library service.

Changes:
- Remove FilePath field from BookPreview struct in GetShelf response
- Remove FilePath field from shelf items response

Related to previous commit implementing relative path storage.
2026-02-27 21:50:11 -05:00
john-okeefe 209e9f2a3c feat: implement relative path storage and URL resolution for media files
- Add libraryService dependency to CollectionHandler and OPDSHandler for centralized path resolution
- Create internal/utils/mediaurl.go with ResolveMediaURL() function as single source of truth
- Update GetMediaItem and ListMediaItems handlers to return resolved URLs in API responses
- Update collection handlers (GetCollection, TestRules, PreviewCollection) to use resolved cover URLs
- Update progress handler (GetAllProgress) to use resolved cover URLs
- Add library_id to GetCollectionItems SQL query to enable URL resolution
- Refactor media scanner to store relative paths instead of absolute filesystem paths
- Add ResolveMediaPath() to LibraryService for resolving relative paths to absolute paths
- Add ServeFile endpoint at /uploads/library-:id/* for authenticated file serving
- Add MimeTypes map to library_service.go for consistent MIME type handling
- Update DownloadBook handler to use resolved filesystem paths
- Add getRelativePath() helper to MediaScanner for converting absolute to relative paths
- Use strings.EqualFold for case-insensitive path comparisons in zip extraction

This change enables the application to work with relative paths stored in the
database, making it portable across different server environments while
maintaining backward compatibility with existing absolute paths.
2026-02-27 16:51:44 -05:00
john-okeefe 030e8c87e3 fix: normalize negative offset to zero in media filter 2026-02-14 00:12:08 -05:00
john-okeefe 249884435c fix: allow media item creation with invalid ISBN and stabilize test
- Allow media items to be created/updated with invalid ISBN by storing empty string
- Fix test to use valid ISBN-13 format (9780306406157)
- Add small delay to prevent race condition in pagination test
2026-02-11 18:09:42 -05:00
john-okeefe 0f8db2ab07 Add ISBN-10 to ISBN-13 validation and conversion
Enhance NormalizeISBN to validate and convert ISBNs:
- Validate length (10 or 13 digits), return error if invalid
- Convert ISBN-10 to ISBN-13 by prefixing '978' and recalculating checksum
- Add NormalizeISBNSafe for backward compatibility in scanners

This ensures all ISBNs stored in database are valid ISBN-13 format.
2026-02-11 09:42:18 -05:00
john-okeefe bddb411a3d fix: add validation for empty media_item_id in bulk update
Add strict validation to return 400 Bad Request when any media_item_id
is empty in the bulk-update request, rather than treating it as a
partial failure with 200 OK.

This aligns the handler behavior with test expectations for the
BulkUpdateBooks_EmptyBookIDs test case.
2026-02-10 20:51:23 -05:00
john-okeefe 03225cf6e2 refactor: rename bulk operations from /api/books/ to /api/media-items/
- Rename routes: /api/books/bulk-{delete,update} → /api/media-items/bulk-{delete,update}
- Rename route: /api/books/:uuid/download → /api/media-items/:uuid/download
- Update request fields: book_ids → media_item_ids
- Update response fields: success → deleted/updated
- Update result fields: book_id → media_item_id
- Update collection handler: success → added

This change improves API semantic correctness as the system handles
multiple media types (ebooks, comics, manga), not just books.

BREAKING CHANGE: All bulk operation endpoints and field names renamed
2026-02-10 19:56:59 -05:00
john-okeefe 6b1815de12 Add folder validation to CreateMediaItem handler
- Check library has folders before creating media items
- Return HTTP 400 with clear error message if no folders
- Proper error code (400) instead of generic 500
- Improved user feedback for invalid operations
- Inject LibraryService into MediaHandler

Fixes: TestCollectionsBulkOperations HTTP 500 errors
Related: Service layer validation commit
2026-02-09 14:28:58 -05:00
john-okeefe 75cc26d5d1 feat: update handlers to normalize tags and contributors
Update CreateMediaItem handler:
- Normalize tags for display using NormalizeTags()
- Normalize contributors for display using NormalizeContributors()
- Generate tags_search using NormalizeTagsSearch()
- Generate contributors_search using NormalizeContributorsSearch()
- Pass search fields to database

Update UpdateMediaItem handler:
- Same normalization logic as CreateMediaItem
- Regenerate search fields on updates

Update HandleBulkUpdate handler:
- Add tag normalization with punctuation preference
- Regenerate search fields when tags/contributors updated

All handlers now populate both display and search fields, ensuring
consistent normalization throughout the application.

Relates to Tags & Contributors Migration Phase 6
2026-02-08 11:05:22 -05:00
john-okeefe 5ccbc8cb2c fix(handlers): improve tags validation in bulk update
Changed HandleBulkUpdate to check tags array length before assignment
instead of checking for nil, improving consistency with array handling
and preparing for dual-field normalization implementation.
2026-02-08 00:43:18 -05:00
john-okeefe 516cec5a7f feat: migrate tags and contributors from TEXT to TEXT[] arrays
Convert tags and contributors columns from comma-separated strings to PostgreSQL
TEXT[] arrays for better data normalization and query performance.

Database Changes:
- schema.sql: Change tags/contributors from TEXT to TEXT[]
- schema.sql: Add GIN indexes for fast array searches
- queries.sql: Update search queries to use ANY() operator
- queries.sql: Update fuzzy search with unnest() for arrays

Generated Code (sqlc):
- models.go: Auto-generated with []string types for tags/contributors
- queries.sql.go: Auto-generated with proper array handling

Handler Changes:
- media.go: Update request structs to use []string for tags/contributors
- media.go: Remove pgtype.Text wrapping, use direct array assignment
- media.go: Add tag normalization in CreateMediaItemHandler
- collections.go: Update tags evaluation to join arrays for comparison
- collections.go: Add strings import for Join() function

Service Changes:
- ebook_scanner.go: Update EbookMetadata struct to use []string
- ebook_scanner.go: Remove string Join(), assign arrays directly
- collection_service.go: Update tags rule evaluation to join arrays
- collection_service.go: Add strings import

New Utilities:
- internal/utils/tags.go: Create NormalizeTags(), JoinTags(), SplitTags()
- Normalizes tags by trimming, lowercasing, removing duplicates/empties

API Documentation:
- bruno/media-items/Create Media Item.bru: Update examples to use arrays
- bruno/media-items/Update Media Item.bru: Update examples to use arrays
- Update docs: tags/contributors now array of string

Breaking Change:
- JSON format changes from "tags": "tag1,tag2" to "tags": ["tag1", "tag2"]
- Tests already use array format (no changes needed)

Benefits:
- GIN indexes enable faster array searches
- Normalization prevents data quality issues (case, duplicates)
- Array operations use PostgreSQL native operators (ANY, &&, unnest)
- Better separation of concerns (no string parsing in application)
2026-02-07 22:53:12 -05:00
john-okeefe eb73e4a9f9 refactor(handlers): Phase 7 - cleanup ebook.go, remove duplicate methods
- Remove 24 duplicate media CRUD methods from ebook.go (884 lines removed)
- Keep 12 scanner/watch/scheduler methods on Handler
- Move request type declarations to media.go:
  * CreateMediaItemRequest
  * UpdateMediaItemRequest
  * CreateMediaNoteRequest
  * UpdateMediaNoteRequest
  * CreateMediaHighlightRequest
  * UpdateMediaHighlightRequest
- Remove unused imports from ebook.go (strconv, pgx)
- Fix library.go to use MediaHandler.ListMediaItems instead of Handler

ebook.go reduced from 1266 lines to 382 lines (70% reduction)
Handler now has focused responsibility: scanner and scheduler operations only

This completes Phase 7 of the ebook.go refactoring plan.

Result: Clean separation of concerns with no duplicate code
2026-02-07 19:56:52 -05:00
john-okeefe f4e06e60c6 refactor(handlers): Phase 2 - create MediaHandler with CRUD operations
- Add worker field to MediaHandler struct
- Make NewMediaHandler accept optional worker parameter
- Move 24 media CRUD methods from ebook.go to MediaHandler:
  * Media CRUD: ListMediaItems, GetMediaItem, ListMediaItemsFiltered, CreateMediaItem, UpdateMediaItem, DeleteMediaItem, SearchMediaItems
  * Ratings: CreateMediaRating, GetMediaRating, UpdateMediaRating, DeleteMediaRating
  * Progress: GetMediaReadingProgress, UpdateMediaReadingProgress, DeleteMediaReadingProgress
  * Notes: GetMediaNotes, CreateMediaNote, GetMediaNote, UpdateMediaNote, DeleteMediaNote
  * Highlights: GetMediaHighlights, CreateMediaHighlight, GetMediaHighlight, UpdateMediaHighlight, DeleteMediaHighlight

Methods are copied (not moved) to maintain backward compatibility during refactoring.
Duplicates will be removed in Phase 7.

This is Phase 2 of the ebook.go refactoring plan.
2026-02-07 19:49:35 -05:00
john-okeefe 00a083b60b Rename backend code references: Bookmann → Bookhoard
Backend changes:
- Update import paths: bookmann/internal → bookhoard/internal
- Rename struct fields: BookmannUUID → BookhoardUUID
- Update handler function names: mapContentIdToBookmannUUID → mapContentIdToBookhoardUUID
- Update HTTP response headers: X-Bookmann-* → X-Bookhoard-*
- Update service and middleware references
- Update main.go imports and references

This is part 2 of the project rename to Bookhoard.
2026-02-01 16:11:54 -05:00
john-okeefe 82ff6356b5 feat(collections,media): add bulk operations for books and collections
Collections:
- HandleBulkAddBooks: add multiple books to multiple collections

Media:
- HandleBulkDelete: delete multiple media items
- HandleBulkUpdate: bulk update book metadata (tags, status)
- Individual result tracking for each operation
- WebSocket broadcasts for collection updates
2026-02-01 12:15:47 -05:00
john-okeefe fef41dc168 feat: add media handler for book download and shelf management 2026-01-31 00:29:38 -05:00