Commit Graph
145 Commits
Author SHA1 Message Date
john-okeefe 511ae66688 fix(collections): add form binding and HTMX redirect support
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.
2026-03-01 20:59:56 -05:00
john-okeefe 0a0b7f4d2e fix: Update test files to match refactored method signatures
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
2026-03-01 00:33:20 -05:00
john-okeefe 0b666f3fdd feat: Add collection detail page with /collections/:id route
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.
2026-03-01 00:28:54 -05:00
john-okeefe 1242550892 test(system-settings): update tests for scan_poll_interval_seconds
- 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
2026-02-28 12:57:34 -05:00
john-okeefe 5a2e1fda65 refactor(router): check auto_scan_enabled before starting watch mode
- 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
2026-02-28 12:57:27 -05:00
john-okeefe ce72781ec0 refactor(scanner): make poll interval dynamic from database
- 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
2026-02-28 12:57:06 -05:00
john-okeefe 286d0b5e06 feat(scanner): convert scan poll interval from minutes to seconds
- 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.
2026-02-28 01:59:35 -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 524d963a97 fix(handlers): update OPDS download to use path resolution service
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.
2026-02-27 21:50:15 -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 501c898e58 Refactor handlers package to separate common handler logic
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.
2026-02-27 10:32:16 -05:00
john-okeefe d802236874 scanner: fix library isolation, file mtime, force rescan, and deletion handling
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.
2026-02-26 16:39:42 -05:00
john-okeefe a97220e654 backend: add force rescan parameter to scanner
- 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)
2026-02-26 10:11:46 -05:00
john-okeefe 0295bf7a37 Implement backend scan progress tracking (Steps 1-7)
Implements comprehensive progress tracking for scan jobs to provide real-time
statistics to the frontend (files_scanned, new_items, errors).

Changes:
1. Extended JobResult struct with new fields:
   - FilesScanned: total files processed
   - NewItems: books added to database
   - Errors: scan errors encountered

2. Added progress callback mechanism:
   - Job.ProgressCallback function field for real-time updates
   - Job.UpdateProgress() method to trigger callbacks
   - Worker stores callback and updates JobResult during scan

3. MediaScanner now tracks statistics:
   - totalFiles, newItems, errors counters
   - GetStats() method to retrieve statistics
   - First pass counts total files for progress calculation
   - Batches progress updates every 10 files (reduces mutex contention)
   - Final update ensures 100% progress is reported

4. Updated processMediaFile signature:
   - Returns (bool, error) instead of (error)
   - true = new item created, false = existing/updated/error
   - Increments newItems counter when creating database entries
   - Updated WatchChanges to handle new return value

5. Worker job completion extracts stats:
   - Parses result map for files_scanned, new_items, errors
   - Stores in final JobResult for API response

6. GetScanStatus API response includes new fields:
   - files_scanned, new_items, errors now in JSON response
   - Frontend can display real-time progress

Design decisions:
- Batching every 10 files balances performance vs. granularity
- Thread-safe via worker mutex (w.mu.Lock/Unlock)
- Callback pattern decouples scanner from job management
- processMediaFile return type allows tracking new vs. updated items
- Maintains backward compatibility (uses || 0 fallbacks in frontend)

Testing:
- All code compiles successfully
- Follows service layer pattern (no business logic in handlers)
- No database schema changes
- Integration tests to be added in Step 8 (separate commit)

Files modified:
- internal/services/worker.go (JobResult, Job struct, processScanJob, processJob)
- internal/services/media_scanner.go (struct fields, GetStats, ScanFolders, processMediaFile)
- internal/handlers/scanner.go (GetScanStatus response)

Related: TASKS-backend-progress-tracking.md Steps 1-7
2026-02-25 10:52:53 -05:00
john-okeefe 2af035d87f feat(backend): add server-side directory browsing API
- Add BrowseDirectories() to library service with path traversal protection
- Add BrowseDirectories handler with proper error handling
- Register GET /api/libraries/browse endpoint (admin-only)
- Returns current path, parent path, and list of subdirectories
- Security: blocks "..", validates path exists, checks is directory

Fixes: Issue 2 (backend)
2026-02-23 17:02:52 -05:00
john-okeefe 3174ec16bc feat(backend): Add SSR data helper for admin library page
Add ListLibrariesData() method to LibraryHandler to support
server-side rendering of all libraries on the /admin/library page.

This follows the existing pattern of GetUserVisibleLibrariesData() and
GetLibraryTypeData() methods, which return data structures instead of
JSON for template rendering.

Changes:
- Add ListLibrariesData() method (3 lines)
- Returns []database.ListLibrariesRow for template consumption
- Called by frontend route handler for SSR
2026-02-22 21:05:57 -05:00
john-okeefe 9e420b63ad chore(deps): upgrade core dependencies
- pgx/v5: v5.4.3 → v5.8.0
- golang-jwt/jwt/v5: v5.3.0 → v5.3.1
- echo/v4: v4.13.4 → v4.15.1
- google/uuid: v1.4.0 → v1.6.0
- golang.org/x/crypto: v0.46.0 → v0.48.0
- golang.org/x/text: v0.33.0 → v0.34.0

Also updates indirect dependencies including puddle/v2, brotli,
regexp2, and other transitive deps.

Build and tests passing with pgx v5.8.0 (internal/anynil package
removed but not used by our code).
2026-02-22 13:11:06 -05:00
john-okeefe 31e286a14b fix(handlers): check user existence before deletion in DeleteUser
Add explicit check to verify target user exists in database before
attempting deletion. Previously, the handler would return 200 OK when
trying to delete non-existent users.

Changes:
- Add userFound flag to track if target user was found in user list
- Explicitly check pgtype.UUID.Bytes against all users' IDs
- Return 404 Not Found if user doesn't exist (before last admin check)
- Supports both JSON and HTML (HTMX) response formats

This fixes the failing test:
- TestDeleteUserConsolidated/DELETE_/api/auth/profile/:id_-_Delete_non-existent_user

The check uses the existing ListUsers result, so no additional database
query is required. The pgtype.UUID.Bytes comparison ensures exact
16-byte UUID matching.
2026-02-22 12:17:50 -05:00
john-okeefe bb0970dfd0 feat(handlers): implement consolidated user profile endpoints
Implement DeleteUser, ResetUserPassword, and UpdateUserAdmin handlers.
Update collections handler to check soft-deleted users. Update dashboard
service to exclude deleted users from statistics.
2026-02-22 01:57:49 -05:00
john-okeefe 66e0b61200 test(collections): replace broken unit tests with integration tests
Removed unit tests that couldn't work without a database (nil db would
panic). Added comprehensive integration tests for the PreviewCollection
endpoint covering:
- Authentication (no auth, valid auth)
- Input validation (missing/invalid library ID, invalid JSON)
- Manual book selection
- Rule-based filtering
- Limit parameter handling
- Duplicate and invalid book ID handling
2026-02-20 17:04:15 -05:00
john-okeefe 33fcc416b9 fix(collections): add authentication check to PreviewCollection handler
The PreviewCollection endpoint was missing authentication verification,
allowing unauthenticated access to the preview functionality. Added
check for user in context, returning 401 Unauthorized if missing.
2026-02-20 17:04:02 -05:00
john-okeefe ef94c124f1 test: add comprehensive test coverage for dashboard
Phase 11 - Unit and Integration Tests

Service Layer Tests (dashboard_service_test.go):
- Test filterHiddenCollections with multiple scenarios
- Test reorderCollections with custom orders
- Test sortByPriority sorting logic
- All 6 tests passing

Handler Tests (dashboard_test.go):
- Test BuildSections type conversion
- Test textToString helper function
- Test getViewAllURL mapping
- All 7 tests passing

Preview Tests (collections_preview_test.go):
- Test preview endpoint validation
- Test limit validation
- Test rule validation
- 6 test scenarios

Integration Tests (dashboard_integration_test.go):
- Test GET /api/dashboard/sections end-to-end
- Test PUT /api/dashboard/preferences
- Test POST /api/dashboard/restore-system-collection
- Test authentication and validation
- 9 test scenarios total

Part of Carousel Dashboard Plan completion
2026-02-20 10:20:57 -05:00
john-okeefe 29e9f66c71 feat(dashboard): implement Phase 4.5 Collections Preview Endpoint
Add preview endpoint for custom section builder and rule evaluation:

Handler Implementation (internal/handlers/collections.go):
- PreviewCollection method: Evaluates filter rules and returns matching items without saving
  * Accepts library_id, rules array, manual_book_ids array, and limit
  * Evaluates rules against all library items using collectionService.EvaluateRules
  * Adds manually selected books to results
  * Deduplicates manual books (avoids adding same book twice)
  * Applies limit (default: 20, max: 100)
  * Returns array of BookInfo with matching items
- Helper function: mediaItemsToListMediaItemsRow
  * Converts database.MediaItems to database.ListMediaItemsRow
  * Required for EvaluateRules which expects ListMediaItemsRow type

Route Registration (internal/router/collections.go):
- POST /api/collections/preview
- Protected by JWT middleware
- Part of collections API group

Why This Endpoint is Necessary:
- Allows users to see what books match their filter rules BEFORE saving
- Avoids creating incorrect collections
- Enables testing different rule combinations quickly
- Reuses existing service logic (collectionService.EvaluateRules)
- Client-side preview would require downloading entire library (10,000+ books)
- Would duplicate 500+ lines of rule evaluation logic in TypeScript
- Would create maintenance nightmare keeping Go and TypeScript in sync

Bruno Test (bruno/collections/preview-collection.bru):
- Tests POST /api/collections/preview endpoint
- Validates status 200 response
- Validates items array in response
- Example request with genre filter rule

This endpoint is required for both the web UI Custom Section Builder and future mobile apps.
2026-02-19 21:15:03 -05:00
john-okeefe 810316694a feat(dashboard): implement Phase 4 API handlers for Carousel-style dashboard
Add dashboard API endpoints with handler layer:

Step 1: Add SectionData to collections.go
- SectionData struct represents dashboard section (carousel of books)
- Used by: Dashboard handler, Templates (SSR), API JSON responses
- Shared type from collections.go (no duplicate definitions)
- Fields: ID, IsSystem, Title, Description, Icon, Items, ViewAllURL, Priority

Step 2: Create dashboard.go handler
- DashboardHandler struct with injected database and dashboard service
- GetSections: Returns dashboard sections as JSON (mobile apps, web UI TypeScript, plugins)
  * Validates library_id parameter
  * Fetches user dashboard preferences
  * Configurable limit (default 20, max 100)
  * Calls service layer for business logic
  * Converts service types to handler types for JSON serialization
- UpdatePreferences: Saves dashboard preferences
  * Validates library_id
  * Upserts user dashboard preferences
- RestoreSystemCollection: Resets system collection to defaults
  * Validates collection_name against allowed system collections
  * Deletes user's copy (system collection reappears automatically)
- BuildSections: Converts service DashboardSection to handler SectionData
  * Converts database.MediaItems to handlers.BookInfo
  * Uses shared types from collections.go
- getViewAllURL: Maps system collections to their view-all URLs
- Reuses existing textToString helper from collections.go

Architecture Compliance:
- Generic API handler for reuse by SSR, mobile, plugins
- Uses shared types from collections.go (SectionData, BookInfo)
- IsSystem bool matches database field (no string conversion)
- Single service method returns structured data (simpler, less bugs)
- Handler just converts types (no matching logic needed)
- Reusable by mobile apps, web UI, plugins
2026-02-19 20:59:19 -05:00
john-okeefe fce16b53f7 fix(auth): return JSON for HTMX login failures instead of HTML 2026-02-16 21:08:15 -05:00
john-okeefe 2e1af8d20b feat(auth): extend session duration to 7 days using constants
- Add SessionDuration constant (7 days) and SessionDurationSec computed value
- Update JWT token expiration to use SessionDuration instead of 1 hour
- Update register/login cookie MaxAge to use SessionDurationSec (604800)
- Update register/login API response ExpiresIn to use SessionDurationSec
- Update refresh token endpoint ExpiresIn to use SessionDurationSec
- Remove redundant client-side document.cookie lines from login/register
- Add TODO comment for HTTPS cookie Secure flag

This provides Google-like persistent sessions with a single source of truth
for session duration, eliminating hardcoded values throughout the codebase.
2026-02-16 16:49:43 -05:00
john-okeefe b5156bbe16 feat: add HTTP-only cookie for browser authentication
- Set HTTP-only cookie in login handler for SSR authentication
- Set HTTP-only cookie in registration handler
- Change default redirect from /bookshelf to /dashboard
- Cookie enables browser page navigation without JavaScript
2026-02-15 21:36:42 -05:00
john-okeefe 6346e9bc27 test: add new handler test files for analytics, auth, kobo, library, progress, and sidecar 2026-02-14 21:38:08 -05:00
john-okeefe acd194c217 test: add device token validation and text utility tests 2026-02-14 21:38:00 -05:00
john-okeefe d7ab22c399 fix(auth): add jti claim to JWT tokens for unique token identification 2026-02-14 21:37:29 -05:00
john-okeefe 02ff078adf fix: validate UUIDs in OPDS middleware before authentication
- Add UUID validation in device_auth middleware for OPDS routes
- Return 400 Bad Request for invalid device/book IDs instead of 401
- Remove redundant UUID validation from OPDS handlers (middleware handles it)
2026-02-14 00:12:15 -05:00
john-okeefe 030e8c87e3 fix: normalize negative offset to zero in media filter 2026-02-14 00:12:08 -05:00
john-okeefe 2706ae52c1 refactor: remove Phase X terminology from source code comments
Remove planning document phase references from code comments:

app_test.go:
- Remove Phase 5 references from 8 test function comments

querier.go & queries.sql.go:
- Remove Phase 1, 2, 3, 4, 6 references from section headers
- Clean up week numbers (Weeks 5-6, Week 3-4, etc.)

queries.sql:
- Remove Phase 4 references from Kobo queries

kobo.go:
- Remove Phase 6 references from ContentId mapping comments

progress.go:
- Remove Phase 1 reference from route comment

media_scanner.go & media_scanner_library_type_test.go:
- Remove Phase 2 references from library type scanning comments

schema.sql:
- Remove Phase 1, 2, 3, 4, 5, 7 references from table/section comments
- Clean up: Format Detection, Progress Tracking, Device Registry,
  Sync Queue, Conflict Resolution, Reading History, Indexes, etc.

test_helpers.go:
- Remove Phase 6 reference from handler setup comment

These phase numbers were from internal planning documents and have no
meaning in the codebase. Removing them makes the code self-documenting.
2026-02-13 21:50:29 -05:00
john-okeefe 368c790c67 refactor(tests): enhance test infrastructure with library/collection helpers
- Add LibraryTestData struct to TestDeviceSetup
- Implement CreateLibrary() for proper library creation in tests
- Implement CreateCollection() for test collection support
- Improve test isolation with dedicated library creation

This provides a more robust foundation for integration tests that need
proper library management support.
2026-02-13 20:04:47 -05:00
john-okeefe 289284522b test: add test reliability plan and device test coverage
- Add TEST_RELIABILITY_PLAN.md documenting test strategy
- Add devices_test.go with device handler tests
- Add device_auth_test.go with device authentication middleware tests
2026-02-13 16:37:54 -05:00
john-okeefe 81fbcfac11 feat: add RegenerateDeviceToken API endpoint
- Add handler to regenerate device auth tokens
- Add PUT /api/devices/:id/regenerate-token route
- Returns new token and sync URLs for device configuration
2026-02-13 12:12:28 -05:00
john-okeefe 2a64ca423f Refactor: Eliminate duplicate types - Use handler types directly
- Deleted templates.CollectionDetailData - using templates.CollectionData everywhere
- Deleted templates.BookData - using handlers.BookInfo everywhere
- Deleted templates.DeviceData - using handlers.DeviceInfo everywhere
- Deleted templates.ProgressItemData - using handlers.ProgressWithMedia everywhere
- Deleted templates.convertDevices() helper - Use handlers types directly in templates
- Enhanced handlers.ProgressWithMedia with device metadata fields
- Added handlers.getDeviceIcon() helper
- Updated all templates to import handlers package
- Cleaned up unused imports

This aligns codebase with templ's design philosophy (use Go types directly, no parallel type system)
2026-02-12 19:48:07 -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 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 2200720537 fix(middleware): Correct rate limit header type conversion 2026-02-10 12:06:12 -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 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 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