Worker improvements:
- Add strongly-typed result structs for all job types
- Replace map[string]interface{} with specific result types
- Add JSON tags to JobResult for proper API serialization
- Fix processJob to handle different result types correctly
- Improve directory scan job with proper library folder resolution
- Add debug logging for scan operations
Media scanner improvements:
- Add nil checks for database in GetPollInterval and GetAutoScanEnabled
- Fix pdfcpu API call signature (add validateOnly parameter)
- Add debug logging for scanDirectory with file counters
- Improve error handling and reporting
Test fixes:
- Fix default poll interval expectation from 30s to 60s
- Add settingsCache initialization to scanner tests
- Add folders initialization to ProcessDirtyDirectories test
- 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
- Change library_id parameter from interface{} to pgtype.UUID
- Add explicit UUID type casting in SQL queries
- Fix SearchMediaItemsParams to use strongly-typed UUID
- Prevents potential type assertion errors and improves type safety
- Ensures proper NULL handling for optional library_id filter
Add comprehensive test coverage for media scanning functionality:
- fsnotify_integration_test.go: Integration tests for the file system
watcher, testing directory creation, modification, and deletion events
with proper cleanup
- media_scanner_test.go: Unit tests for MediaScanner including:
- Scanner initialization and configuration
- Directory walking and media file detection
- Library management and duplicate detection
- Import job creation and queue processing
These tests verify the core file watching and media scanning behavior
to ensure reliable import operations.
Extract health check logic into GetHealth method on Config struct and
integrate with Worker service for accurate scan status reporting.
Changes:
- Move health check handler from inline function to Config.GetHealth()
- Add Worker field to Config struct for dependency injection
- Wire Worker into main server dependencies
- Report actual scan_in_progress status using Worker.HasActiveScans()
- Report actual active_jobs count using Worker.GetActiveJobCount()
This provides more accurate health monitoring by checking the real state
of background jobs rather than returning static placeholder values.
- Add Priority field to Job struct for future job prioritization
- Add HasActiveScans() method to check if any scans are currently running
- Add GetActiveJobCount() method to count running and pending jobs
- Remove unused JobTypeSync constant
These changes enable more accurate health check reporting and prepare
for future job priority queue implementation.
- Return actual database error message instead of generic "unavailable"
- Add scan status information to healthy response (scan_in_progress, active_jobs)
- Maintain backward compatibility while providing more actionable diagnostics
- Use map[string]interface{} to support nested scan status structure
These changes improve observability by providing administrators with
specific error messages and scan status information, making it easier
to diagnose issues and monitor system state.
- Add SettingsCache with TTL-based invalidation (30 seconds)
- Cache scan_poll_interval_seconds and auto_scan_enabled settings
- Reduce database queries from every poll/check to once per TTL period
- Improve error handling with proper fallback values
- Simplify boolean parsing with strings.ToLower for consistency
This optimization reduces database load when checking scan settings,
which occurs frequently during media scanning operations.
Pass ConnectionManager to Worker constructor to enable WebSocket
broadcasting capabilities. Updated:
- main.go: server initialization
- test_helpers.go: test setup
- commonhandlers.go: handler initialization
This change enables Worker to broadcast job updates to connected clients.
- Add UserID field to Job struct for tracking job ownership
- Broadcast scan progress updates to user's WebSocket connections
- Send real-time updates during scanning (progress, files scanned, new items, errors)
This allows the frontend to display live scan progress without HTTP polling.
Scanner now associates scan jobs with requesting user for targeted updates.
Add new message type constants for real-time scan progress updates:
- MessageTypeScanProgress: broadcast progress during scanning
- MessageTypeScanComplete: notify when scan completes
- MessageTypeScanError: report scan errors
These enable frontend to receive live scan updates instead of polling.
Fix incorrect struct tags for Page, TotalPages, and PageY fields.
Previously used 'int' tag instead of proper JSON field names,
which would cause serialization issues.
- Replace event queue with dirty directories tracking (Jellyfin approach)
- Add file stability checking to wait for file writes to complete
- Add initial scan on startup to detect existing files
- Integrate with Worker job queue for directory scanning
- Change WatchChanges to return error and use atomic.Bool for state
- Add scan_mutex to prevent concurrent scans
- Add Close method with proper cleanup of resources
- Enhance polling with configurable interval
- Add WorkerInstance global singleton for global access
- Add new job types: import, convert, thumbnails, backup, analytics, sync
- Add Enqueue method for non-blocking job submission
- Add job processors for each new job type:
- processImportJob: OPDS and Calibre import support
- processConvertJob: EPUB to KEPUB conversion
- processThumbnailsJob: Cover thumbnail generation
- processBackupJob: Database backup functionality
- processAnalyticsJob: Library and system statistics
- processDirectoryScanJob: Directory scanning for media scanner
- Add helper getTopN function for analytics
- Add JobsHandler with CreateJob and GetJobStatus endpoints
- Add jobs router with POST /api/jobs and GET /api/jobs/:jobId routes
- Integrate JobsHandler into main server and router config
Add real-time synchronization for collection book removal:
- Extract user ID from context for targeted broadcasts
- Broadcast 'collection_updated' message to user's other devices
- Includes collection_id, action, and book_id in message payload
This ensures that when a user removes a book from a collection,
all their connected devices (browser tabs, mobile apps, etc.)
receive real-time updates via WebSocket.
Consistent with existing AddBooks and BulkRemoveBooks operations
which already use BroadcastToUser for synchronization.
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.
The GetPreferences API was returning 404 when no preferences existed
for a library, breaking the dashboard settings modal. Now returns
default preferences (empty hidden_collections, empty collection_order,
20 items_per_section) when no preferences are found, matching the
behavior of the frontend dashboard page.
- Update TestGetViewAllURL_SystemCollections to use collectionID and libraryID parameters
- Test both with and without library_id in URL
- Update TestBuildSections_ConvertsServiceTypesToHandlerTypes expected values
- All collections now link to /collections/{id} (system and user treated equally)
- Add library_id parameter to BuildSections and getViewAllURL functions
- Update dashboard handler to pass libraryID when building sections
- Add library_id query param support to collection detail page handler
- When library_id is provided, filter collection items by that library
- When no library_id, show all books (backward compatible)
- Reuses GetCollectionItemsForDashboard query for filtered results
- Preserves context when navigating from dashboard to collection detail
Ensure consistent JSON responses by converting nil slices to empty arrays
in the GetPreferences handler. This prevents null values from being
returned to the client for hidden_collections and collection_order fields,
making the API response more predictable and easier to consume.
Update RestoreSystemCollection handler to support form-encoded requests from HTMX:
- Add 'form' struct tags to CollectionName and ResetType fields to enable binding
from both JSON payloads and form submissions (required for HTMX compatibility)
- Add conditional HTMX redirect handling that sets HX-Redirect header when
the request originates from HTMX, directing users to /collections after
successful restoration
This change enables the system collection restore functionality to work seamlessly
with HTMX-based modal forms, improving the user experience by providing proper
navigation after the restore operation completes without requiring JavaScript
redirect logic.
Add three new frontend routes to support HTMX-powered modal dialogs:
1. GET /collections/create-modal
- Renders empty collection creation modal
- Uses CollectionModal template with empty CollectionData
2. GET /collections/:id/edit-modal
- Fetches collection by ID from database
- Pre-populates modal with existing collection data
- Returns 400 for invalid UUID, 404 if collection not found
3. GET /collections/restore-modal
- Renders system collection restoration modal
- Allows users to restore deleted system collections
Route registration order:
- /collections/:id/edit-modal must be registered before /collections/:id
to avoid path conflicts in Echo's router
These routes enable the collections page to load modals dynamically via
HTMX (hx-get) instead of embedding modal HTML in the base page.
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.
Update test files to work with recent backend refactoring changes.
Test changes in internal/services/dashboard_service_test.go:
- Fix method name casing for FilterHiddenCollections
- Change from filterHiddenCollections (lowercase 'f')
- Change to FilterHiddenCollections (uppercase 'F')
- Matches exported method signature in DashboardService
- Line 57: Update test call to use correct exported method
Test changes in internal/handlers/dashboard_test.go:
- Update getViewAllURL test to match simplified function signature
- Remove queryType parameter from test call
- Function now only takes collectionName parameter
- Aligns with refactoring to use /collections/{id} routing
- Line 178: Update test call to use new signature
These fixes ensure tests compile and run correctly after the
collection detail page refactoring where:
1. getViewAllURL() was simplified to return /collections/{id}
2. System collections now use the same routing as user collections
Add default library ID functionality to improve library targeting
during media scans.
Service changes in internal/services/media_scanner.go:
- Add defaultLibraryID field to MediaScanner struct
- Add SetLibraryID() method to set default library
- Modify processMediaFile() to use defaultLibraryID when set
- Prioritizes defaultLibraryID over folder-based library detection
- Provides explicit library targeting for scans
Service changes in internal/services/worker.go:
- Add libraryUUID conversion from string to pgtype.UUID
- Call scanner.SetLibraryID() before ScanFolders()
- Ensures scanner respects the job's library ID
These changes enable more precise library targeting during media scans,
allowing scans to be directed to specific libraries rather than relying
solely on folder-based detection.
Change library ordering in dropdown from DESC to ASC to display
libraries in creation order (oldest first).
Database changes in internal/database/queries/queries.sql:
- Modify GetUserLibraries query ORDER BY clause
- Change from ORDER BY l.created_at DESC to ASC
- Displays oldest libraries first in dropdown
This provides a more intuitive ordering where users see their
first-created libraries at the top of the list.
Fix multiple issues with dashboard customization modal and slider
not working correctly per library.
Frontend changes in web/src/dashboard.ts:
- Fix openDashboardSettings() to use current library ID
- Add library_id parameter to dashboard preferences API call
- Show toast error message on API failure instead of opening modal
- Prevent opening modal with stale/inaccurate data
- Fix slider query parameter mismatch
- Change from 'libraryId' to 'library_id' to match backend API
- Fix DOM query from collectionList.querySelector to document.querySelector
- Ensure slider targets correct input element
- Fix saveDashboardSettings() to refresh current library
- Fetch current library data before saving preferences
- Use library_id from current library, not from URL
- Show toast error message on save failure
- Keep modal open on error for user to retry
- Add localStorage persistence for selected library
- Store selectedLibrary in localStorage after switching
- Enables persistence across page refreshes
- Improve switchLibrary() with fade transitions
- Add fade-out (150ms) before data fetch
- Add fade-in (300ms) after rendering new library
- Provide smooth visual feedback during library switches
- Apply preferences dynamically to modal
- Use applyPreferencesToModal() to update slider and toggles
- Ensure modal reflects current library's settings
Backend changes in internal/router/dashboard.go:
- Update GetDashboardPreferences to use library_id query parameter
- Matches frontend API call parameter naming
Template changes in templates/dashboard.templ:
- Remove duplicate renderDashboardCollections() inline script
- Functionality now handled by dashboard.ts
These fixes ensure that:
1. Dashboard settings work correctly per library
2. Slider reflects and updates the correct library's item limit
3. Toggles show accurate visibility state for each library
4. Library switches provide smooth visual feedback
5. Errors are properly surfaced to users via toast messages
Add comprehensive collection detail page that works for both system collections
(continue-reading, recently-added, not-started) and user collections.
Backend changes:
- Add new /collections/:id route in internal/router/frontend.go
- Fetches collection using GetCollection with UUID parameter
- Determines collection type from QueryType field
- Resolves library_id for system collections
- Converts database.MediaItems to handlers.BookInfo for display
- Renders CollectionDetail template with collection and books data
- Update SectionData struct in internal/handlers/collections.go
- Add CollectionID string field for view all links
- Update BuildSections() in internal/handlers/dashboard.go
- Pass CollectionID to SectionData for proper link generation
- Simplify getViewAllURL() in internal/handlers/dashboard.go
- Return /collections/{collectionID} instead of /section/{type}
- Works uniformly for both system and user collections
Frontend changes:
- Fix CollectionDetail template in templates/collections.templ
- Fix broken div nesting causing compilation error
- Add null check for CoverImagePath to prevent broken images
- Update aspect ratio to modern aspect-[3/4] syntax
- Use responsive widths (w-16 sm:w-20) for mobile/desktop
- Improve card layout with horizontal flex structure
- Add placeholder image fallback for books without covers
- Remove erroneous renderBooks() function call
This change aligns with the backend update where system collections are
now pre-made user collections in the database with query_type fields.
All collections can now use the same CollectionDetail template for a
consistent viewing experience.
- Add unit tests for MediaScanner.GetPollInterval and GetAutoScanEnabled
- Add integration tests for scan-settings API endpoints
- Update validation test cases to use seconds (1-3600) instead of minutes
- Fix worker.go to use new NewMediaScanner signature
- Update validation to use scan_poll_interval_seconds field (1-3600 seconds)
- Update all test cases and assertions to use new field name
- Update integration test to reflect new field name
- Add check for auto_scan_enabled setting in router before starting watch mode
- Update StartScanner handler to verify auto-scan is enabled
- Switch scanner to use watchModeCtx/watchModeCancel instead of ctx/cancel
- Update StartWatchModeForLibrary to use new MediaScanner signature
- Add GetPollInterval() method to MediaScanner to read from database
- Add GetAutoScanEnabled() method to check if auto-scan is enabled
- Remove ScanPollIntervalSeconds from config (now DB-driven)
- Update NewMediaScanner signature to not require interval parameter
- Remove SCAN_POLL_INTERVAL_SECONDS from docker-compose env var
- Delete scheduler.go and scheduler_test.go (no longer needed)
- Simplify App struct by removing Handler interface dependency
- Remove StartScheduler/StopScheduler from app lifecycle
- Update main.go to not pass handler to app constructor
- Remove scheduler mock from app tests, simplify test coverage
- Rename SCAN_POLL_INTERVAL_MINUTES to SCAN_POLL_INTERVAL_SECONDS in config
- Update MediaScanner to accept interval in seconds instead of minutes
- Adjust default polling interval from 3 minutes to 30 seconds for faster response
- Add debug logging for fsnotify events to aid troubleshooting file watching
This change improves media file detection responsiveness by reducing the
polling interval from minutes to seconds, while maintaining the file
watcher as the primary detection mechanism.
- 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
Update the DownloadBook function to properly resolve media file paths using
the LibraryService.ResolveMediaPath method instead of directly accessing the
FilePath field. This ensures correct file resolution after the migration to
relative path storage.
The change affects three code paths in the download handler:
- KEPUB conversion path
- Direct file serve path (non-EPUB with conversion service)
- Default EPUB path
Error handling added to return 404 when path resolution fails, preventing
potential errors when accessing non-existent files.
This fixes potential file access issues after the relative path storage
implementation.
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.
- Correct indentation in goroutine leak test setup block
- Align struct field tags in BookMatch and all matching methods for
consistent column-style formatting (media_item_id, bookhoard_uuid,
confidence, match_method)
- Improves code readability and adheres to project indentation guidelines
- 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.
Extract Handler struct, constructor, and shared utilities from scanner.go
into a new commonhandlers.go file for better code organization.
Changes:
- Move Handler struct definition to commonhandlers.go
- Move NewHandler constructor to commonhandlers.go
- Move SetupRoutes function to commonhandlers.go
- Move parseDate utility function to commonhandlers.go
- Remove unused imports from scanner.go
- Create dedicated commonhandlers.go for shared HTTP handler code
This refactoring improves code maintainability by separating
concerns between scanner-specific logic and common handler utilities,
making it easier to understand and extend the handlers package.
Previously, library routes used a generic NewHandler for convenience routes.
Now uses cfg.ScannerHandler directly for consistency with other scanner
endpoints and to ensure proper handler-specific middleware is applied.
Fix 1 - File modification time for created_at:
- Get file.ModTime() in processMediaFile and pass to CreateMediaItem
- Modified SQL INSERT to include created_at column
Fix 2 - Force rescan UPDATE instead of DELETE+INSERT:
- Changed force rescan logic to call updateMediaItem instead of delete + create
- Preserves created_at timestamp on force rescan
Fix 3 - GetMediaItemByFilePath filters by library_id:
- Added library_id to WHERE clause in SQL query
- Created GetMediaItemByFilePathAnyLibrary for cross-library lookups (KOReader)
- Added SetLibraryID method to MediaScanner
- Updated handler to call SetLibraryID for watch mode
Fix 4 - File deletion handling with persistent logging:
- Added fsnotify.Remove handler in WatchChanges
- Added orphan cleanup in ScanFolders after scan completes
- Created scanner_logger.go with daily log rotation (7 days)
- Logs to /app/logs/scanner-deletes-YYYY-MM-DD.log and scanner-errors-YYYY-MM-DD.log
- Individual deletes with enhanced safety logging
Note: Integration tests can now safely scan /app/uploads because
GetMediaItemByFilePath now filters by library_id, preventing
cross-library interference.
- Add Force bool field to ScanLibraryRequest in handlers
- Pass force param through job params to worker
- Add forceRescan field and SetForce method to MediaScanner
- Modify processMediaFile to delete and re-create existing items when force=true
- Default behavior unchanged (force=false maintains skip-if-exists)
- Add extractEPUBCover() to extract embedded covers from EPUB files
- Parse OPF manifest for cover-image properties
- Support meta name="cover" tags
- Fall back to common cover paths (cover.jpg, images/cover.jpg)
- Add findSidecarCover() for sidecar cover detection
- Check cover.jpg, cover.png, cover.webp
- Check folder.jpg, folder.png
- Check {basename}.jpg (same name as media file)
- Add extractPDFCover() to extract first page images from PDFs
- Use pdfcpu API to extract images from page 1
- Save largest image as cover
- Update extractPDFMetadata() to use pdfcpu API
- Extract Title, Author, Subject, Creator, Producer
- Call extractPDFCover for embedded covers
- Fall back to sidecar covers
- Update extractMetadata() for EPUB to call extractEPUBCover
- Try embedded cover first, then sidecar
Features:
- Complete dashboard redesign with improved UI components and layout
- Implement custom section builder for personalized book organization
- Add new events tracking system for user interactions
- Enhance search functionality with better static search.js
- Update TypeScript type definitions for API responses
Backend:
- Update Go dependencies in go.mod
- Add new frontend routes in router
Templates:
- Update admin and dashboard templates with new components
Frontend:
- Refactor analytics, collections, conflicts, and queue modules
- Add new documentation features in docs.ts
- Implement linking between books and collections
- Add toast notifications for user feedback
- Include placeholder book SVG asset
This commit consolidates multiple feature additions and improvements
across the entire stack including backend, templates, and frontend.
Fix 1: Convert int stats to float64 for JSON API consistency
- Issue: scanner.GetStats() returns (int, int, int) but processScanJob
stored them as int in map[string]interface{}, causing type assertion panic
when worker tries to extract them as float64
- Fix: Convert to float64 at source in processScanJob() return statement
- Benefit: Type-consistent JSON API, all numbers are float64 (matches progress field)
Fix 2: Integration test polling improvements
- Issue: Tests waited before first poll, missing fast-completing scans
- Issue: Tests didn't handle 404 "job not found" responses gracefully
- Fix: Poll immediately after getting job_id (no initial sleep)
- Fix: Check for 404 status before parsing JSON body
- Fix: Check for error response before accessing progress fields
- Benefit: Tests catch fast scans and handle all response types safely
Changes:
- internal/services/worker.go: Convert totalFiles, newItems, errors to float64
- cmd/server/tests/scanner_integration_test.go: Add 404/error handling in both tests
Test Results:
- TestScanProgress_BatchingWorks: PASS ✓
- TestScanProgress_TracksStatistics: FAIL due to unrelated db connection issue
(db pool closes mid-scan, not a code issue)
The type conversion fix eliminates the panic and makes the API response type-consistent.
The test improvements make tests more robust against timing issues.