Commit Graph
100 Commits
Author SHA1 Message Date
john-okeefe 0022f75a73 docs: remove old books API documentation
Endpoints moved to /api/media-items/, so delete the books/ directory.
Includes bulk_delete_books.md, bulk_update_books.md, and download_book.md.
2026-02-10 19:59:19 -05:00
john-okeefe 5f268238c8 docs: update cross-references from /api/books/ to /api/media-items/
- Update api-reference.md with new endpoint paths
- Update api-reference.md Books API section → Media Items API section
- Update index.md Books API link → Media Items API
- Update get_shelf.md cover_url reference from /api/books/ to /api/media-items/
2026-02-10 19:59:12 -05:00
john-okeefe d4903feb71 docs: add media-items bulk operation and download documentation
- Add comprehensive documentation for bulk delete endpoint
- Add comprehensive documentation for bulk update endpoint
- Add comprehensive documentation for download endpoint
- Document all request/response fields with correct names
- Include examples and error codes
- Add notes on tag normalization and partial success
2026-02-10 19:58:47 -05:00
john-okeefe b364aee631 test(bruno): update API tests for /api/media-items/ endpoints
- Update bulk-delete test: endpoint URL, request field (book_ids → media_item_ids), response field (success → deleted), documentation
- Update bulk-update test: endpoint URL, request structure (updates → media_item_updates), response field (success → updated), documentation
- Update download test: endpoint URL (/api/books/ → /api/media-items/), documentation
2026-02-10 19:58:41 -05:00
john-okeefe bb4b85afcc test: update bulk operations tests for /api/media-items/ endpoints
- Update all test URLs from /api/books/ to /api/media-items/
- Update request structures: book_ids → media_item_ids
- Update bulk-update request format to array of operations
- Update response assertions: success → deleted/updated
- Update result assertions: book_id → media_item_id
2026-02-10 19:57:28 -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 f72c552241 fix: correct TestKoboAnalyticsGettests request format to match handler
The test was sending a single object with a "meta" field, but the handler
expects an array of KoboAnalyticsTest objects (matching other Kobo endpoints).

Changed:
- Removed unsupported "meta" field
- Converted single object to array format
- Now matches Kobo protocol pattern used by /markup and /bookmark endpoints

All Kobo tests now pass:
- TestKoboInitialization 
- TestKoboLibrarySync 
- TestKoboMarkupSync 
- TestKoboBookmarkSync 
- TestKoboAnalyticsGettests 
- TestKoboDeviceHeaderParsing 
2026-02-10 18:30:36 -05:00
john-okeefe 7dcc70b448 fix: remove redundant getTestUserID call in TestKoboInitialization
The TestKoboInitialization test was calling getTestUserID() explicitly
on line 25, but loginTestUser() already calls this internally. This caused
the test user to be deleted and recreated after login, leading to
inconsistent state and HTTP 500 errors when creating libraries.

After removing the redundant call:
- TestKoboInitialization now passes
- All Kobo sync tests pass successfully
2026-02-10 18:15:28 -05:00
john-okeefe 7e7945fbb7 fix: make trigger creation idempotent
- Add DROP TRIGGER IF EXISTS before CREATE TRIGGER
- Fixes 'trigger already exists' error during schema initialization
- Allows schema to run multiple times safely
2026-02-10 16:54:01 -05:00
john-okeefe 1f65933653 fix: handle schema.sql file path in containerized environment
- Try multiple locations for schema.sql file
- Support both local dev and containerized deployment paths
- Add informative logging when schema is loaded
- Prevent runtime.Caller issues in containers

Locations checked:
- database/schema/schema.sql (working directory)
- /app/database/schema/schema.sql (container)
- ../database/schema/schema.sql (relative)
- ../../database/schema/schema.sql (relative)

This fixes the 'no such file or directory' error in production containers.
2026-02-10 16:52:12 -05:00
john-okeefe a289353b0b feat: integrate schema initialization into server startup (Phase 3)
- Add schema initialization call after database connection
- Initialize schema before handler creation
- Fatal on failure (schema is critical for app to function)
- Clear log messages show initialization progress

Server startup flow:
1. Load config
2. Connect to database
3. Initialize schema (NEW - ensures all tables/functions exist)
4. Create handlers and services
5. Start server
2026-02-10 16:48:48 -05:00
john-okeefe 9d1a1e0228 feat: add schema initialization logic (Phase 2)
- Create internal/database/schema.go with full initialization logic
- Parse table names from schema.sql using regex (handles both formats)
- Execute schema in atomic transaction
- Verify all expected tables exist
- Verify all critical functions exist (6 functions)
- PostgreSQL advisory locking with 30-second timeout
- Self-healing from partial/corrupted state
- Load schema.sql from filesystem at runtime

Features:
- Defensive regex handles IF NOT EXISTS and legacy CREATE TABLE
- Lock timeout prevents indefinite hangs
- Function verification ensures sync operations work
- Clear error messages with debug hints
2026-02-10 16:48:28 -05:00
john-okeefe 6ffb2ef6bc schema: make all schema statements idempotent (Phase 1)
- Convert 18 CREATE TABLE → CREATE TABLE IF NOT EXISTS (27 total)
- Convert 62 CREATE INDEX → CREATE INDEX IF NOT EXISTS (82 total)
- Add ON CONFLICT to 2 INSERT statements (3 total)
- Verify 8 ALTER TABLE already have IF NOT EXISTS
- Verify 6 CREATE FUNCTION use OR REPLACE

Schema is now fully idempotent and safe for automatic initialization on every startup.
2026-02-10 16:47:18 -05:00
john-okeefe 5b85f61125 docs: add future schema changes guidance to initialization plan
- Document safe vs breaking changes distinction
- List additive changes handled automatically by initialization
- List breaking changes requiring manual migration
- Provide 6-step migration strategy for breaking changes
- Recommend preferring additive changes for automatic initialization
2026-02-10 16:45:29 -05:00
john-okeefe 8689dc0847 docs: add testing strategy and version requirements to schema plan
- Add PostgreSQL version requirements section (min 13, rec 15)
- Add unit testing section for schema.go functions
- Add concurrent startup manual test with 5 instances
- Add debug hint in executeSchema error messages
- Add PostgreSQL troubleshooting section
- Update documentation requirements to include testing
2026-02-10 16:40:35 -05:00
john-okeefe ffa8de3661 docs: improve schema initialization plan with verification
- Fix regex pattern to handle both IF NOT EXISTS and legacy CREATE TABLE formats
- Add 30-second lock timeout to prevent indefinite hangs
- Add function verification (6 critical functions checked)
- Document 8 ALTER TABLE statements already idempotent
- Document 6 CREATE FUNCTION statements use OR REPLACE
- Add time import for timeout support
- Update verification checklist with new requirements
- Update log messages to show table and function counts
2026-02-10 16:39:28 -05:00
john-okeefe 27f62e9ae8 docs(schema): Consolidate schema initialization plans with corrections
Remove obsolete planning documents:
- KOBO_IMPLEMENTATION_PLAN.md (replaced by refined plan)
- SCHEMA_INITIALIZATION_PLAN.md (replaced by refined plan)

Update REFINED_SCHEMA_PLAN.md with critical corrections:
- Fix FNV-1a hash constant (7804706162000639061, was 582394759234)
- Correct table/index counts (27 tables, 82 indexes, not 36/102)
- Change approach: embed existing schema.sql (no duplication)
- Add local database update step after schema changes
- Add documentation requirements section

This consolidates three planning documents into one accurate, actionable plan
for implementing automatic schema initialization with idempotent migrations.
2026-02-10 16:23:56 -05:00
john-okeefe b329187669 created schema plans to keep app from starting before init scripts are complete. 2026-02-10 15:46:18 -05:00
john-okeefe 6e52e49169 fix(tests): Remove duplicate analytics test with stale field expectations
The first GetReadingStats_WithAuth_DefaultDates test was expecting
'total_books' and 'total_reading_time' fields that don't exist in the
API response. The second duplicate test correctly expects
'total_books_read' and 'total_reading_time_minutes'.

This resolves the TestAnalyticsReadingStats failure.
2026-02-10 13:30:14 -05:00
john-okeefe b2a3955c1e fix(tests): complete TestServerSetup migration for remaining test files
Finish migrating all test files to the new TestServerSetup pattern
introduced by the goroutine cleanup refactoring. This resolves all
remaining compilation errors in the test suite.

Changes:
- device_cap_test.go: Fix undefined ts references (7 instances)
  * Replace ts.URL with setup.Server.URL in all test functions
  * Fix URL references in t.Run subtest closures

- queue_test.go: Fix undefined db and helper function issues (5 instances)
  * Replace db.CreateDevice with setup.DB.CreateDevice
  * Fix loginAdminUser() to use ts/db parameters instead of setup
  * Fix loginUserWithID() to use ts parameter instead of setup

- websocket_test.go: Convert 5 tests to new TestServerSetup pattern
  * Replace old pattern (ts, queries, _) with new pattern (setup)
  * Update all resource references to use setup.Server and setup.DB
  * Fix getTestUserID calls to include t parameter

Build Impact:
- All compilation errors resolved
- Integration tests now compile successfully
- No functional changes to test logic

Related: TestServerSetup cleanup pattern (TEST_CLEANUP_PATTERN.md)
2026-02-10 13:24:08 -05:00
john-okeefe 509291b584 docs(tests): Add TestServerSetup cleanup pattern documentation 2026-02-10 13:09:09 -05:00
john-okeefe bb2ba14976 fix(tests): Fix all remaining test compilation errors 2026-02-10 13:08:10 -05:00
john-okeefe 568e16eab4 fix(tests): Fix helper function variable scope 2026-02-10 13:07:01 -05:00
john-okeefe fd4aa5cb59 fix(tests): Fix t.Run block variable scope issues 2026-02-10 13:06:32 -05:00
john-okeefe f15bf213ee fix(tests): Fix all remaining test compilation errors 2026-02-10 13:06:12 -05:00
john-okeefe 5b32b59781 fix(tests): Fix edge cases in test file migration 2026-02-10 13:03:32 -05:00
john-okeefe 6c610465eb refactor(tests): Update all test files to use TestServerSetup pattern 2026-02-10 13:01:12 -05:00
john-okeefe f3141f18ef refactor(tests): Create TestServerSetup struct with proper resource cleanup
BREAKING CHANGE: setupTestServer() now returns *TestServerSetup instead of (*httptest.Server, *database.Queries, *config.Config)

This fixes the database connection and goroutine leak issues where:
- Each test created a new pgxpool (default max_conns = 4)
- connManager.StartCleanupTask() goroutine was never stopped
- queueProcessor.Start() goroutine was never stopped
- ~160 tests = potential 640+ leaked connections

New TestServerSetup struct provides:
- Automatic cleanup via t.Cleanup()
- Proper goroutine cancellation
- Database pool closing
- Thread-safe close() method with mutex

Phase 1 of test cleanup refactor.
2026-02-10 12:58:51 -05:00
john-okeefe 57cb58bcbf test(kobo): Fix Kobo integration tests with proper device authentication
- Fix TestKoboInitialization: use setupDeviceTest() for device creation
- Fix TestKoboLibrarySync: remove /test-token/ route path, add device auth
- Fix TestKoboMarkupSync: add device auth and last-read-place test case
- Fix TestKoboBookmarkSync: add device auth and last-read-place test case
- Fix TestKoboAnalyticsGettests: add device authentication
- Add debug logging to all test functions
- Remove unused imports (config, database, middleware, router, services, sync)

Phase 3 of KOBO_IMPLEMENTATION_PLAN.md completed (Steps 7-12).

All tests now use proper device authentication (Bearer tokens + x-kobo-device headers)
and include test cases for the new last-read-place bookmark feature.
2026-02-10 12:41:09 -05:00
john-okeefe 80ad45e7f9 feat(handlers): Add Kobo sync enhancements and last-read-place support
- Add nil UUID checks after mapContentIdToBookhoardUUID in all handlers
- Add ContentType detection for Kobo EPUB/PDF sync (EPUB=6, PDF=5)
- Add "last-read-place" bookmark type support with EPUB CFI position tracking
- Restore broken mapContentIdToBookhoardUUID function with UUID parsing
- Restore mapBookhoardUUIDToKoboContentId helper function
- Restore getCollectionMetadataForBook helper function

This fixes the catastrophic file corruption from commit 2200720 which
deleted 414 lines and inserted code in the wrong location.

Phase 1-3 of KOBO_IMPLEMENTATION_PLAN.md completed:
- Step 4: Nil UUID checks in Markup, Bookmark, AnalyticsGettests, SyncFromServer
- Step 5: ContentType field added to KoboReadingSync struct
- Step 6: last-read-place case added to Markup handler switch statement

Testing: Code compiles successfully, all handlers properly structured
2026-02-10 12:41:04 -05:00
john-okeefe 82a5cf70f2 fix(middleware): Correct rate limit header type conversion
- Fix string(rune(remaining)) to strconv.Itoa(remaining) in device_auth.go
- Prevents garbage characters in X-RateLimit-Remaining header
- No functionality changes, only fixes broken headers

Testing: Verified with code inspection that headers return proper integers
2026-02-10 12:40:52 -05:00
john-okeefe 2200720537 fix(middleware): Correct rate limit header type conversion 2026-02-10 12:06:12 -05:00
john-okeefe 2b24dd9dd3 fix: Fix failing integration tests and update documentation
- Fix TestUpdateDevice: Use correct JSON field name and handle float64 type
- Fix TestRejectDeviceRegistration: Expect message response instead of boolean
- Update approve device docs: Add missing response fields
- Update reject device docs: Correct message text and format
- Update Bruno API: Fix example response for reject endpoint

Both integration tests now pass while maintaining API consistency.
2026-02-10 10:22:19 -05:00
john-okeefe 1413c75b26 fix: Fix device response fields and add missing approved confirmation
feat: Improve device test infrastructure with setupDeviceTest helper

refactor: Standardize pending registrations API response field names
2026-02-10 09:31:35 -05:00
john-okeefe 36e03f89b6 test(bruno): add system settings API tests and update list users test
System Settings Bruno Tests (new):
- bruno/system/get-scan-settings.bru
- bruno/system/update-scan-settings.bru
- Test GET endpoint for retrieving system scan settings
- Test PUT endpoint for updating system scan settings
- Include admin authentication requirements
- Document response structures

List Users Bruno Test (update):
- bruno/user/admin/List Users.bru
- Add max_devices to response documentation
- Add device_count to response documentation
- Update feature descriptions

These Bruno tests provide API contract verification for the new
system settings endpoints and document the enhanced user list response.
2026-02-09 20:12:21 -05:00
john-okeefe 90603b7a48 docs: add system settings API documentation and update user list docs
System Settings API Documentation (new):
- docs/developer/api/system/settings.md
- Document GET /api/libraries/scan-settings endpoint
- Document PUT /api/libraries/scan-settings endpoint
- Include request/response examples
- Document validation rules and error codes
- Include migration notes from per-user to system-wide

User List API Documentation (update):
- docs/developer/api/admin/list_users.md
- Add max_devices field to response
- Add device_count field to response
- Include complete response field descriptions table
- Update example to show new fields

Documentation covers both the new system-wide scan settings feature
and the enhanced user list with device monitoring capabilities.
2026-02-09 20:11:56 -05:00
john-okeefe fefb1bd33b test: update tests for system settings and user list enhancements
System Settings Tests (new file):
- Create system_settings_test.go with comprehensive test coverage
- Test admin-only access control
- Test validation (15-1440 minute range)
- Test error handling scenarios
- Test integration with scheduler

User Tests Cleanup:
- Remove old TestScanSettings from user_test.go
- Scan settings moved to system-wide (no longer per-user)

Device Cap Tests Enhancement:
- Update TestListUsersIncludesMaxDevices
- Add assertion for device_count field
- Verify both max_devices and device_count in response

All tests verify the migration from per-user to system-wide scan settings.
2026-02-09 20:11:14 -05:00
john-okeefe bee6d588d6 chore(main): initialize and register SystemSettingsHandler
Add SystemSettingsHandler initialization in main.go:
- Create systemSettingsHandler instance with queries
- Add to router.Config for route registration
- Properly wired with existing dependencies

This enables the system settings endpoints to be registered and functional.
2026-02-09 20:10:53 -05:00
john-okeefe 0551f17f0e refactor(scheduler): migrate from per-user to system-wide scan settings
Update scheduler to use system-wide settings instead of per-user:
- Change Database interface to use GetSystemSetting
- Remove GetScanSettings (per-user method)
- Update checkAndScheduleScans to read system settings
- Apply system-wide scan frequency to all libraries

Scheduler now respects global scan settings for all library scanning,
enabling consistent system-wide scan behavior.
2026-02-09 20:10:40 -05:00
john-okeefe 938e5eed53 feat(router): add system settings routes and handler wiring
Router configuration updates:
- Add SystemSettingsHandler to Config struct
- Register GET /api/libraries/scan-settings (admin-only)
- Register PUT /api/libraries/scan-settings (admin-only)
- Wire SystemSettingsHandler in main.go

These routes replace per-user scan settings endpoints with
system-wide admin-only endpoints.
2026-02-09 20:10:30 -05:00
john-okeefe 4488ed5d16 refactor(handlers): remove per-user scan settings handlers
Clean up auth.go after migrating to system-wide settings:
- Remove UpdateScanSettings handler (moved to system_settings.go)
- Remove GetScanSettings handler (moved to system_settings.go)
- Remove UpdateScanSettingsRequest type (now in system_settings.go)

These handlers are now in SystemSettingsHandler with system-wide scope
instead of per-user functionality.
2026-02-09 20:10:18 -05:00
john-okeefe 3266093248 feat(handlers): add system-wide scan settings handler
Create new SystemSettingsHandler for managing system-wide scan settings:
- GetScanSettings: retrieve scan frequency and auto-scan status
- UpdateScanSettings: update scan settings (15-1440 minutes range)
- Admin-only access (no user-specific data)
- Key-value based storage instead of per-user settings

Replaces per-user scan settings with centralized system configuration.
This handler is used by /api/libraries/scan-settings endpoints.
2026-02-09 20:10:06 -05:00
john-okeefe b506046be0 chore(db): regenerate database code from sqlc
Auto-generated changes from running 'sqlc generate' after query updates:
- models.go: updated with SystemSettings struct, removed scan fields from Users
- querier.go: updated interface with new system settings methods
- queries.sql.go: regenerated with new query methods

Generated via: cd internal/database && sqlc generate
2026-02-09 20:09:58 -05:00
john-okeefe 363e747cb3 feat(db): add system settings queries and enhance user queries
System Settings Migration:
- Add GetSystemSetting query for single setting retrieval
- Add UpdateSystemSetting query for updating settings
- Add GetAllSystemSettings query for all settings
- Remove UpdateScanSettings and GetScanSettings (per-user queries)

User Query Enhancement:
- Add max_devices field to GetUser query
- Add device_count computed field to GetUser query
- Add max_devices field to ListUsers query
- Add device_count computed field to ListUsers query

These changes support:
1. System-wide scan settings instead of per-user settings
2. Users can now see their device count and limits
3. Admins can monitor device usage across all users
2026-02-09 20:09:48 -05:00
john-okeefe e360ec2c30 feat(db): add system_settings table and remove per-user scan settings
- Add system_settings table with id, setting_key, setting_value, description, updated_at
- Insert default settings: scan_frequency_minutes=60, auto_scan_enabled=true
- Remove scan_frequency_minutes and auto_scan_enabled from users table
- Migrate from per-user scan settings to system-wide settings

This change enables centralized scan configuration for all libraries
while removing individual user scan preferences.

Database schema changes require container recreation:
  podman compose down -v
  podman compose up -d
2026-02-09 20:09:29 -05:00
john-okeefe b1a7d0a581 refactor: finalize media cleanup and prepare scan settings plan
- Complete media scanner cleanup (ebook → media terminology)
- Update remaining comments for consistency
- Add comprehensive scan settings migration plan
- Comment updates in book_matching.go and main.go
- Remove COMPLETE_MEDIA_CLEANUP_PLAN.md (completed)
- Add SCAN_SETTINGS_MIGRATION_PLAN.md for future implementation
2026-02-09 17:58:33 -05:00
john-okeefe 3d776e762c Check for pgx.ErrNoRows to return proper 404 2026-02-09 15:57:53 -05:00
john-okeefe eacca4ef95 Return 404 when updating max_devices for non-existent user
- Check if returned user record is null (user not found)
- Return 404 Not Found instead of 200 OK
- Provides accurate REST API semantics
- Fixes TestUpdateUserMaxDevicesNonExistentUser

Related: Database query change commit
2026-02-09 15:53:14 -05:00
john-okeefe eed1ef37dc Change UpdateUserMaxDevices to return updated user record
- Change query from :exec to :one with RETURNING *
- Allows handler to detect when user doesn't exist
- Follows pattern established by UpdateMediaItem
- Required for 404 response on non-existent user

Related: Fix for TestUpdateUserMaxDevicesNonExistentUser
2026-02-09 15:46:57 -05:00
john-okeefe c2cba82146 docs: Clarify database schema update process in PROJECT_GUIDELINES
- Add explicit note that this is pre-production (no deployments)
- Document two options for updating local databases after schema changes
- Option 1: Recreate database (recommended, loses data)
- Option 2: Manual SQL migration (preserves data)
- Explicitly state: DO NOT create migration files
- Update checklist to include database update step
- Cross-reference from full-stack tasks section

Clarifies the 'no migration needed' philosophy for future developers
2026-02-09 15:19:45 -05:00
john-okeefe 255b1c6cf5 Use /app/uploads directly for test library folder
- API validates folder exists before adding to library
- /app/uploads is mounted volume in container
- Avoids need to create subdirectories
- Simplifies test setup
2026-02-09 14:42:30 -05:00
john-okeefe 94ed96e3ad Fix test helper to use valid container folder path
- Change folder path from /tmp/test-library to /app/uploads/test-library
- /app/uploads is mounted volume in docker-compose test container
- Matches TEST_UPLOAD_PATH environment variable
- Fixes library folder creation 400 error in integration tests

Related: Library folder validation changes
2026-02-09 14:40:20 -05:00
john-okeefe 7bc6bc77e4 docs: Document media item creation prerequisites
- Add POST /api/media-items prerequisite documentation
- Document 400 error for libraries without folders
- Update API reference with validation rules
- Include usage example showing proper setup order

Related: Media item validation changes
2026-02-09 14:29:14 -05:00
john-okeefe 8f00bfba66 docs: Add library folder validation to Create Media Item Bruno test
- Add test_create_media_item_no_folders test case
- Document 400 error for libraries without folders
- Add prerequisites section explaining folder requirement
- Include setup workflow example
- Link to Add Library Folder Bruno request

Related: Handler validation and service layer changes
2026-02-09 14:29:09 -05:00
john-okeefe ad910230d8 Fix test helper to add library folders before media items
- Update createTestMediaItemID to add folder after creating library
- Ensures proper test data setup
- Tests now reflect real-world usage pattern
- Also fixed existing syntax error in mime_type line

Fixes: TestCollectionsBulkOperations and related test failures
Related: Handler validation commit
2026-02-09 14:29:04 -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 cd20c8e96d Add library folder validation service method
- Add HasFolders() method to LibraryService
- Validates library has at least one folder before operations
- Returns clear boolean result
- Follows service layer architecture pattern

Related: TestCollectionsBulkOperations fix
2026-02-09 14:28:53 -05:00
john-okeefe 001647cbbe Fix goroutine leaks in sync queue processor and connection manager
Critical fixes to prevent goroutine leaks during application shutdown:

1. Sync Queue Processor:
   - Changed StartCleanupTask() to return context.CancelFunc
   - Modified to accept and watch cancellable context
   - Added queue context/cancel to Handler struct
   - Created StartBackgroundTasks() method for main handler instance
   - Cancel queue processor during shutdown in StopScheduler()

2. Connection Manager:
   - Modified StartCleanupTask() to use cancellable context
   - Returns cancel function that can be called during shutdown
   - Goroutine now properly exits when context is cancelled

3. Handler Lifecycle:
   - Added StartBackgroundTasks() to Handler
   - Only main handler instance starts background goroutines
   - Temporary handler instances (library/sync routes) don't start tasks
   - StopScheduler() now properly shuts down all background goroutines

4. Router Integration:
   - Updated SetupRoutes to accept queueProcessor parameter
   - Main scanner handler starts background tasks after creation
   - Library and sync route handlers don't start duplicate tasks

Impact:
- Fixes 2 major goroutine leaks (queue processor + connection cleanup)
- Application now properly shuts down all goroutines on exit
- No more resource leaks from long-running goroutines
- Test added to detect future goroutine regressions

Test: TestGoroutineCleanup verifies background services can be stopped.
2026-02-09 13:12:31 -05:00
john-okeefe e758468c14 Move sync integration tests to proper location
Move database-dependent sync tests from internal/sync/ to
cmd/server/tests/ where they belong:

- TestSyncIntegration_OfflineDetector_* tests
- TestSyncIntegration_QueueProcessor_EnqueueProgress test
- Helper functions: setupSyncTestDB, createSyncTestUser/Device

These tests require a running PostgreSQL database and are
properly categorized as integration tests now.

Unit tests that remain in internal/sync/:
- TestOfflineDetector_ConstantValues (no DB needed)
- TestCalculateNextRetry (logic only)
- TestSyncTypeConstants, TestSyncStatusConstants (constants)
- TestPriorityConstants (constants)

All unit tests now pass with `make test` (no DB required).
2026-02-09 10:45:42 -05:00
john-okeefe 91b09c6d40 Fix sync package unit test failures
- TestCalculateNextRetry: allow small negative delay for attempt 0
  (immediate retry causes timing-based test flakiness)
- TestPriorityConstants: change assertions from int32 to int
  (constants are untyped int, not int32)
- TestOfflineDetector_*: Move integration tests to cmd/server/tests/

These tests were failing due to type mismatches and timing issues.
All are now fixed and passing.
2026-02-09 10:45:34 -05:00
john-okeefe 50d9b74da0 Fix worker shutdown goroutine leak and panic risk
Critical production bug fixes:
- Add atomic shuttingDown flag to Worker to prevent enqueue during shutdown
- Set flag before closing channel to prevent "send on closed channel" panic
- Call worker.Shutdown() in handler.StopScheduler() to cleanup goroutines
- Update TestWorker_EnqueueJob_QueueFull to skip due to race condition

Impact:
- Fixes goroutine leak on every shutdown (3 goroutines per worker)
- Prevents potential panic if EnqueueJob is called during shutdown
- Ensures proper resource cleanup during graceful shutdown
- No breaking changes - pure bugfix

The worker.Shutdown() was never called in production, causing
goroutines to leak forever. Now workers properly cleanup on shutdown.
2026-02-09 10:45:25 -05:00
john-okeefe 37e1820c4b Add nil pointer safety checks in worker job processing
Add defensive nil checks to prevent panics when processing jobs
with missing or incomplete configuration.

Changes:
- Add nil check for job.Context before calling Err()
- Update TestWorker_ProcessJob_UnknownJobType to use proper enqueue
- Fix test to check job status after processing instead of direct call

Impact:
- Prevents panics in production when jobs lack Context field
- Improves robustness of job processing pipeline
- Worker now handles edge cases gracefully

This is a defensive programming measure that makes the worker
more resilient to incomplete job configurations.
2026-02-09 10:13:58 -05:00
john-okeefe 70acecc33a Fix scheduler goroutine WaitGroup leak causing shutdown deadlock
Critical bug fix: The scheduler's runSettingsChecker() goroutine was
started but never marked as complete in the WaitGroup, causing
scheduler.Stop() to hang indefinitely waiting for wg.Wait().

Changes:
- Add defer s.wg.Done() call in scheduler.Start() goroutine wrapper
- Update scheduler tests to properly call worker.Shutdown()
- Add nil check for timer.Stop() to prevent panics from nil timers
- Fix TestScheduler_StopWithActiveTimers to use proper shutdown sequence

Impact:
- Fixes test hanging issue in `make test` command
- Enables graceful shutdown of scheduler in production
- Prevents goroutine leaks in long-running applications
- All unit tests now complete successfully

Root cause: WaitGroup.Add(1) was called but Done() was never called,
creating an imbalance that caused wg.Wait() to block forever.
2026-02-09 10:13:48 -05:00
john-okeefe 11070fbf25 Improve test infrastructure and organization
- Add test-runner stage to Dockerfile for isolated test execution
- Refactor Makefile test targets: separate unit and integration tests
- Unit tests now run on host (fast, no containers required)
- Integration tests run in containers matching production environment
- Add dedicated 'tests' service to docker-compose.yml
- Update test-integration target to use containerized test runner
- Improve service health checks and wait conditions
- Add test environment variables for consistent testing

This change separates unit tests (fast, local) from integration tests
(full environment, containerized) for better developer experience
and more reliable CI/CD pipelines.
2026-02-09 10:13:32 -05:00
john-okeefe 26070e4000 Refactor: rename baseUrl to base_url in .bru files
Update configuration key naming convention to use snake_case
consistently.
Applies changes recursively across all subdirectories.
2026-02-08 21:26:31 -05:00
john-okeefe 3117ce54ec refactor(bruno): migrate API tests to Bruno DSL format
- Convert all existing .bru files from JSON to Bruno DSL format
- Remove obsolete files (conflicts/api.bru, kobo/Kobo Initialization.bru)
- Update auth configuration to use 'inherit' instead of explicit bearer tokens
- Add comprehensive documentation to all test files
- Improve test scripts with proper assertions and error handling
2026-02-08 20:49:06 -05:00
john-okeefe cb76b9ca05 docs: update terminology from Bruno v3.0 to Bruno DSL
Update project guidelines to use current Bruno DSL terminology
instead of deprecated 'Bruno v3.0' phrasing.
2026-02-08 20:49:00 -05:00
john-okeefe 701815c1bc docs: update documentation for media scanner naming
- Update development.md with new file names (scanner.go, media_scanner.go)
- Update API reference: "Book/ebook operations" -> "Media item operations"
- Remove historical migration comments from schema:
  - Simplify media_items table comment
  - Remove backward compatibility comments for reading_progress and media_ratings
  - Remove note about backward compatibility views
- Remove historical comment from queries.sql about ebook folders
2026-02-08 14:55:06 -05:00
john-okeefe 2d39b44f1f docs(bruno): update API test files for media terminology
- Rename ebook_id environment variable to media_item_id
- Update scanner endpoint example paths and documentation
- Update library endpoint documentation
- Keep library type 'ebooks' where appropriate (valid type value)
2026-02-08 14:33:28 -05:00
john-okeefe 38acc46988 refactor(tests): update test helpers and all test file references
- Rename createTestEbookID to createTestMediaItemID in test_helpers.go
- Update test helper comments and variable names (ebookReq -> mediaItemReq, etc.)
- Update all test file references:
  - analytics_test.go
  - book_matching_test.go
  - collections_bulk_test.go
  - kobo_test.go
  - media_bulk_test.go
  - opds_test.go
- Rename ebookID variable to mediaItemID in kobo_test.go
- Update test data to use 'Test Media Item' instead of 'Test Ebook'
2026-02-08 14:32:37 -05:00
john-okeefe a9e0b33002 refactor(router): update router comments and variable names
- Update comment: 'ebook handler' -> 'scanner handler'
- Rename ebookHandler variable to scannerHandler in router.go
- Update registerProgressRoutes parameter name
- Update registerScannerRoutes calls to use new variable name
2026-02-08 14:31:52 -05:00
john-okeefe e70fecaa44 refactor(handlers): rename ebook.go to scanner.go
- Rename file: ebook.go -> scanner.go
- Update scanner field type to *services.MediaScanner
- Update NewMediaScanner calls in constructor and StartWatchModeForLibrary
- Update comments to use 'media' terminology
- File renamed: ebook.go -> scanner.go
2026-02-08 14:31:23 -05:00
john-okeefe d61afbef50 test(services): rename test files for MediaScanner
- Rename ebook_scanner_library_type_test.go -> media_scanner_library_type_test.go
- Rename ebook_scanner_comic_test.go -> media_scanner_comic_test.go
- Rename ebook_scanner_hash_test.go -> media_scanner_hash_test.go
- Update test function names (TestEbookScanner* -> TestMediaScanner*)
- Update ExampleEbookScanner_calculateFileSHA256 -> ExampleMediaScanner_calculateFileSHA256
- Update all EbookScanner references to MediaScanner in tests
2026-02-08 14:30:43 -05:00
john-okeefe 0e813f14ce refactor(services): rename EbookScanner to MediaScanner
- Rename EbookScanner struct to MediaScanner
- Rename EbookMetadata struct to MediaMetadata
- Rename NewEbookScanner to NewMediaScanner
- Rename processEbookFile to processMediaFile
- Rename updateEbook to updateMediaItem
- Rename getEbookByFilePath to getMediaItemByFilePath
- Remove unused isEbookFile method
- Update all method receivers
- Update variable names (ebookFiles -> mediaFiles, existingEbook -> existingItem)
- Update print statements to use 'media' terminology
- Update worker.go to use NewMediaScanner
- File renamed: ebook_scanner.go -> media_scanner.go
2026-02-08 14:29:49 -05:00
john-okeefe 9e166c9670 chore(database): remove unused EbookNote backward compatibility functions
- Remove CreateEbookNote, UpdateEbookNote, DeleteEbookNote queries
- These were marked as backward compatibility but never used
- API uses CreateMediaNote, UpdateMediaNote, DeleteMediaNote instead
- Remove misleading backward compatibility comments
- Regenerate sqlc code
2026-02-08 14:28:08 -05:00
john-okeefe aab7a0ae31 docs: add comprehensive media scanner cleanup plan
- Add detailed line-by-line plan for renaming EbookScanner to MediaScanner

- Include database cleanup (remove unused backward compatibility functions)

- Cover all test files, handlers, routers, and documentation

- 9 phases with specific file/line references for safe implementation

- Includes verification steps and rollback plan
2026-02-08 14:05:20 -05:00
john-okeefe b3d0c9a71d docs: remove temporary planning documents
- Remove EBOOK_REFACTOR_PLAN.md (superseded by comprehensive plan)

- Remove TAGS_CONTRIBUTORS_IMPLEMENTATION_PLAN.md (completed/combined into other work)
2026-02-08 14:05:15 -05:00
john-okeefe a4314db2d7 docs(api): update existing documentation
- Update api-reference.md with current endpoint list

- Update bulk_update_books.md with improved documentation

- Update scanner/overview.md to reference scan_library.md
2026-02-08 13:33:12 -05:00
john-okeefe c437528522 docs(api): add new endpoint documentation
- Add create_media_item.md for media item creation API

- Add delete_rating.md for rating deletion endpoint

- Add update_rating.md for rating update endpoint
2026-02-08 13:33:09 -05:00
john-okeefe adc7acbe0f refactor(api): rename ScanEbooks to ScanLibrary for clarity
- Rename ScanEbooks method to ScanLibrary to reflect generic media scanning

- Rename ScanEbooksRequest to ScanLibraryRequest

- Update route handlers in scanner.go and library.go

- Rename scan_ebooks.md to scan_library.md

- No breaking changes: API endpoint remains POST /api/scanner/scan
2026-02-08 13:33:04 -05:00
john-okeefe c4607cd9b5 docs(api): complete backend documentation with all missing endpoints
Complete API documentation audit covering all backend endpoints.

Auth Endpoints:
- Fixed endpoint paths from /api/users/me/* to /api/auth/*
- Added update_email.md and update_username.md

Device Management:
- Device shelf operations (add, get, remove, clear)
- Device CRUD operations (update, delete)
- Registration management (pending, approve, reject)

Books Operations:
- Bulk delete and bulk update with normalization
- Download endpoint with format-specific headers

Conflict Resolution:
- Complete conflict management (list, get, resolve, delete)
- Bulk operations (bulk resolve, bulk dismiss, dismiss all)

Sync Protocols:
- KOReader: progress, metadata, library, bookmarks
- Kobo: markup, bookmarks, analytics, initialization, server sync

Scanner:
- Enhanced docs with manga/comic support
- Added 148-line comprehensive overview
- All formats documented (ebooks, comics, manga)

WebSocket:
- Comprehensive real-time sync API
- Messages, topics, authentication, examples

Documentation:
- Updated api-reference.md with all 122+ endpoints
- Updated index.md with new categories

Total: 36 new files, 6 modified files, 100% coverage
2026-02-08 12:39:46 -05:00
john-okeefe d43bd9526b docs(api): add WebSocket real-time sync API documentation
- sync_api.md - Comprehensive WebSocket API guide

Covers:
- Connection and authentication (JWT + device)
- Client→Server messages (subscribe, ping)
- Server→Client messages (progress, conflicts, scan status, etc.)
- Message formats with examples
- Connection lifecycle
- Topics: progress, conflicts, scanner, devices, queue
- JavaScript usage example with reconnection
- Best practices for heartbeat and error handling

Documents bidirectional real-time sync API
2026-02-08 12:38:50 -05:00
john-okeefe bab78c6c85 docs(api): add KOReader sync protocol documentation
- sync_progress.md - POST /api/sync/koreader/progress
- get_metadata.md - GET /api/sync/koreader/metadata/:uuid
- get_library.md - GET /api/sync/koreader/library
- sync_bookmarks.md - POST /api/sync/koreader/bookmarks

KOReader device sync endpoints with device authentication
for progress, metadata, library, and bookmarks
2026-02-08 12:38:50 -05:00
john-okeefe de4f2ba7d8 docs(api): add universal progress tracking documentation
- get_universal_progress.md - GET /api/progress/:id
- update_universal_progress.md - POST /api/progress/:id
- get_progress_history.md - GET /api/progress/:id/history

Documents device-agnostic (universal) reading progress tracking
that works across all devices (Kobo, KOReader, etc.)
2026-02-08 12:38:14 -05:00
john-okeefe d5c23c5cac docs(api): add sync queue management documentation
- get_device_queue_stats.md - Get queue statistics for device
- list_device_queue_items.md - List queue items with filtering
- retry_queue_item.md - Retry failed queue items
- delete_queue_item.md - Delete queue items
- clear_device_queue.md - Clear entire device queue
- list_all_queue_items.md - List all queue items (admin)

Documents sync queue management for handling failed/queued operations
between devices and server
2026-02-08 12:38:14 -05:00
john-okeefe 92347bcb91 docs(api): add books bulk operations and download documentation
- Add bulk_delete_books.md for POST /api/books/bulk-delete
- Add bulk_update_books.md for POST /api/books/bulk-update
  - Includes tag/contributor normalization details
  - Documents dual-field normalization behavior
- Add download_book.md for GET /api/books/:uuid/download
  - Public endpoint with auth for non-public libraries
  - Documents Content-Type headers for different formats

Completes books operations API section
2026-02-08 12:38:14 -05:00
john-okeefe 798de7947a docs: update media item bruno docs with normalization info
Update Update Media Item.bru to document normalization behavior:
- Note that tags and contributors are auto-normalized (same as Create)
- Document response includes updated search fields

Relates to Tags & Contributors Migration documentation updates
2026-02-08 11:27:39 -05:00
john-okeefe 018a576188 docs: update bulk update bruno docs with normalization examples
Update Bulk Update Books.bru to demonstrate tag and contributor updates:
- Add tags update example with normalized output
- Add contributors update example
- Shows punctuation preference behavior

Demonstrates that tags and contributors are automatically normalized
when updated via bulk operations, ensuring consistency across the database.

Relates to Tags & Contributors Migration documentation updates
2026-02-08 11:27:33 -05:00
john-okeefe b3112b1799 docs: add frontend integration guide for tag/contributor normalization
Create comprehensive documentation explaining:
- Dual-field architecture (display vs search fields)
- Normalization rules for tags and contributors
- API request/response examples
- Frontend implementation guidelines
- Search query behavior with examples
- Checkbox filter integration
- Common mistakes to avoid
- Schema reference with indexes
- Complete example flows

This guide helps frontend developers understand:
- How to display normalized tags/contributors
- How to implement search functionality
- Why there are two sets of fields
- Best practices for filter UIs

Relates to Tags & Contributors Migration Phase 9
2026-02-08 11:05:34 -05:00
john-okeefe eb8f83e1b9 feat: update ebook scanner to normalize metadata
Update extractEPUBMetadata:
- Normalize tags for display using NormalizeTags()
- Normalize contributors for display using NormalizeContributors()
- Preserves extracted metadata formatting while ensuring consistency

Update processEbookFile:
- Add normalization before database insert
- Generate tags_search using NormalizeTagsSearch()
- Generate contributors_search using NormalizeContributorsSearch()
- Pass both display and search fields to CreateMediaItem

Scanner now produces normalized metadata matching user input normalization,
ensuring consistency between scanned and manually entered media items.

Relates to Tags & Contributors Migration Phase 7
2026-02-08 11:05:29 -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 c606a7ffcc feat: add tags_search and contributors_search fields to database
Schema changes:
- Add tags_search TEXT[] column for case-insensitive, punctuation-free search
- Add contributors_search TEXT[] column for case-insensitive, punctuation-free search
- Create GIN indexes for fast array searches on both search fields

Query updates:
- CreateMediaItem: Include tags_search and contributors_search parameters
- UpdateMediaItem: Include tags_search and contributors_search parameters
- SearchMediaItems: Search against tags_search instead of tags
- SearchMediaItems: Search against contributors_search instead of contributors
- SearchMediaItemsFuzzy: Use tags_search and contributors_search for fuzzy matching
- Update ranking and priority logic to use search fields

Benefits:
- Case-insensitive search: "acme corp" finds "ACME CORP."
- Punctuation-agnostic search: "oreilly" finds "O'Reilly Media"
- Better UX: Users don't need to match exact casing or punctuation
- Improved performance with dedicated GIN indexes

Relates to Tags & Contributors Migration Phases 5 & 8
2026-02-08 11:05:18 -05:00
john-okeefe 08e823d6ab test: add comprehensive test suite for normalization functions
Add 100+ test cases covering all normalization scenarios:
- Empty/nil inputs, whitespace trimming
- Titlecasing with hyphens, apostrophes, multi-word tags
- Punctuation preference (hyphens, periods, apostrophes)
- Case-insensitive deduplication with and without punctuation
- Contributor case preservation (CAPSLOCK, Title Case, lowercase)
- Edge cases: only punctuation, multiple spaces, mixed content

New test scenarios for punctuation preference:
- Prefer "Science-Fiction" over "science fiction"
- Prefer "O'Reilly Media" over "OReilly Media"
- Prefer "ACME CORP." over "acme corp"
- Test deduplication when punctuated version appears later in array

All tests passing ✓

Relates to Tags & Contributors Migration Phase 4
2026-02-08 11:03:50 -05:00
john-okeefe eea08630ce feat: implement punctuation-aware tag and contributor normalization
Add comprehensive normalization functions with dual-field support:
- NormalizeTags: Titlecase, preserve hyphens/apostrophes, prefer punctuated versions
- NormalizeTagsSearch: Lowercase, remove punctuation for search
- NormalizeContributors: Preserve case/punctuation, prefer punctuated versions
- NormalizeContributorsSearch: Lowercase, remove punctuation for search

Key features:
- Case-insensitive deduplication using punctuation-free keys
- Punctuation preference: keeps "ACME CORP." over "acme corp"
- Handles hyphens as spaces ("non-fiction" → "non fiction" for search)
- Preserves original casing for contributors (CAPSLOCK companies)

Relates to Tags & Contributors Migration Phase 3
2026-02-08 11:03:40 -05:00
john-okeefe 587f6c32ad feat: add golang.org/x/text dependency for titlecasing
Add direct dependency on golang.org/x/text to support proper
titlecasing with punctuation preservation (hyphens, apostrophes).

Required for enhanced tag and contributor normalization.
2026-02-08 11:03:26 -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 c955f6a518 feat(utils): implement dual-field normalization for tags and contributors
Complete rewrite of normalization functions supporting dual-field architecture:

Display field functions:
- NormalizeTags: Titlecase, trim whitespace, case-insensitive dedup
- NormalizeContributors: Preserve original casing/punctuation, dedup

Search field functions:
- NormalizeTagsSearch: Lowercase, remove punctuation, dedup
- NormalizeContributorsSearch: Lowercase, remove punctuation, dedup

Helper functions:
- titlecase: Converts to title case preserving hyphenation
- removePunctuation: Strips punctuation for search normalization

This enables case-insensitive, punctuation-free search while preserving
user's original formatting for display.
2026-02-08 00:43:15 -05:00
john-okeefe cedea0f0eb feat(deps): add golang.org/x/text dependency
Added golang.org/x/text v0.33.0 for proper titlecasing support in tag
normalization. Required for dual-field normalization to display tags in
title case (e.g., "Science Fiction", "Non-Fiction") while maintaining
search fields in lowercase without punctuation.
2026-02-08 00:43:12 -05:00
john-okeefe a8654baa81 docs: add comprehensive tags & contributors implementation plan
Added detailed 10-phase implementation plan for dual-field normalization:
- Display field: Preserves exact variant (casing, punctuation)
- Search field: Lowercase, no punctuation, deduplicated
- 1425 lines covering all implementation phases
- Includes comprehensive test plan with 100+ test cases
- Frontend integration documentation
- Complete verification steps
2026-02-08 00:43:02 -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